From 0a90b17ca15694ae2c31b5a839490b0cc5caeb2c Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 9 Jan 2014 23:31:00 +0100 Subject: RobotRace: implement pause functionality --- src/Camera.java | 1 - src/RobotRace.java | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Camera.java b/src/Camera.java index a3e3a43..24ebbc9 100644 --- a/src/Camera.java +++ b/src/Camera.java @@ -186,7 +186,6 @@ class Camera { fastest_pos = Math.max(fastest_pos, robot.getTimePos()); } double distance = Robot.racepost2meter(fastest_pos - slowest_pos); - System.err.println("dist+" + distance); // the helicopter view is more suitable if robots are more distant if (distance > 5) { setHelicopterMode(); diff --git a/src/RobotRace.java b/src/RobotRace.java index 57e0a98..d84ce68 100644 --- a/src/RobotRace.java +++ b/src/RobotRace.java @@ -151,6 +151,21 @@ public class RobotRace extends Base { */ boolean enableTextures; + /** + * Real animation time at which the game is paused (or -1 if not paused). + */ + private float pausedSince = -1; + + /** + * Total number of animation time that was spent in the paused state. + */ + private float pausedTimeTotal; + + /** + * Trigger a pause on the next frame update. + */ + private boolean requestPauseToggle = false; + /** * Constructs this robot race by initializing robots, camera, track, and * terrain. @@ -289,6 +304,8 @@ public class RobotRace extends Base { // function), the FPS update is done in the first accessible function // (here, in setView) updateFPS(); + // similarly, reset the time very early here when paused + applyPausedTime(); // Select part of window. gl.glViewport(0, 0, gs.w, gs.h); @@ -609,6 +626,10 @@ public class RobotRace extends Base { System.err.println("Textures are " + (state ? "enabled" : "disabled")); return true; + case KeyEvent.VK_SPACE: /* pause time */ + System.err.println("Triggering pause..."); + robotRace.requestPauseToggle = true; + return true; default: return false; } @@ -629,4 +650,35 @@ public class RobotRace extends Base { } return enableTextures; } + + /** + * If pause toggle is requested, check state and update. Otherwise, adjust + * global animation time. + */ + private void applyPausedTime() { + if (requestPauseToggle) { + if (pausedSince == -1) { + // just paused, store start time + pausedSince = gs.tAnim; + System.err.println("Paused since gs.tAnim=" + gs.tAnim); + } else { + // continuing, increase paused time and clear pause flag + float pausedTime = gs.tAnim - pausedSince; + pausedTimeTotal += pausedTime; + pausedSince = -1; + System.err.println("Continued at gs.tAnim=" + gs.tAnim + + ". Paused for " + pausedTime + " secs (new total: " + + pausedTimeTotal + " secs)"); + } + requestPauseToggle = false; + } + + // if paused, set clock back. + if (pausedSince != -1) { + gs.tAnim = pausedSince; + } + + // always apply correction for time drift + gs.tAnim -= pausedTimeTotal; + } } -- cgit v1.2.1 From 42ac511fdb0e9385b4bf612df364ce2d81858645 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Thu, 9 Jan 2014 23:38:11 +0100 Subject: Fix reset detection with pause --- src/RobotRace.java | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/RobotRace.java b/src/RobotRace.java index d84ce68..f3d6b8a 100644 --- a/src/RobotRace.java +++ b/src/RobotRace.java @@ -304,6 +304,9 @@ public class RobotRace extends Base { // function), the FPS update is done in the first accessible function // (here, in setView) updateFPS(); + // reset our state when a reset it detected. Must be done before hacking + // with time. + detectReset(); // similarly, reset the time very early here when paused applyPausedTime(); @@ -404,19 +407,34 @@ public class RobotRace extends Base { } /** - * Periodically calculate the robot speed based and update robot position. + * Detect when the reset button is pressed and act on it. */ - private void calculateRobotSpeedAndLocation() { - double current_t = gs.tAnim; - // on reset, position the robots on the begin + private void detectReset() { + float current_t = gs.tAnim; if (current_t < last_speed_update) { + System.err.println("Reset detected..."); + + // on reset, position the robots on the begin last_speed_update = 0; last_t = 0; for (Robot robot : robots) { robot.setSpeed(0); robot.resetPosition(); } + + // pause in beginning if already paused + if (pausedSince != -1) { + pausedSince = current_t; + } + pausedTimeTotal = 0; } + } + + /** + * Periodically calculate the robot speed based and update robot position. + */ + private void calculateRobotSpeedAndLocation() { + double current_t = gs.tAnim; // periodically calculate a new speed double last_speed_update_t_diff = current_t - last_speed_update; -- cgit v1.2.1 From 6e8be3c3ce0a6c1071f6c571322de32031f38b1a Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Fri, 10 Jan 2014 12:57:03 +0100 Subject: Allow camera mode to be toggled with O, H and M FP and auto are not interesting, hence not added. --- src/RobotRace.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/RobotRace.java b/src/RobotRace.java index f3d6b8a..f310997 100644 --- a/src/RobotRace.java +++ b/src/RobotRace.java @@ -16,6 +16,7 @@ import robotrace.Vector; import static java.lang.Math.*; import java.net.URI; import java.net.URISyntaxException; +import java.util.Arrays; import java.util.Locale; import java.util.Random; @@ -648,6 +649,23 @@ public class RobotRace extends Base { System.err.println("Triggering pause..."); robotRace.requestPauseToggle = true; return true; + case KeyEvent.VK_O: /* camera mode: Overview */ + case KeyEvent.VK_H: /* camera mode: Helicopter */ + case KeyEvent.VK_M: /* camera mode: Motorcycle */ + if (robotRace.mainWindow != null) { + System.err.println("Changing camera to: " + e.getKeyChar()); + // map from camera mode (array index) to key codes + Integer cameraModes[] = new Integer[] { + KeyEvent.VK_O, /* 0: default mode (Overview) */ + KeyEvent.VK_H, /* 1: helicopter */ + KeyEvent.VK_M, /* 2: Motor cycle */ + }; + int i = Arrays.asList(cameraModes).indexOf(e.getKeyCode()); + assert i != -1 : "Camera mode not found for key"; + robotRace.gs.camMode = i; + robotRace.mainWindow.updateElements(); + } + return true; default: return false; } -- cgit v1.2.1 From 9d2f034864f147f30c9bb823c951348eb572e9f3 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Fri, 10 Jan 2014 17:15:15 +0100 Subject: Walk animation WIP DumbWalkAnimator implementation is for testing purposes. Its constants are hard-coded. --- src/DumbWalkAnimation.java | 53 +++++++++++++++++++++++++++++++++++++ src/Robot.java | 32 ++++++++++++++++++----- src/WalkAnimation.java | 65 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 src/DumbWalkAnimation.java create mode 100644 src/WalkAnimation.java diff --git a/src/DumbWalkAnimation.java b/src/DumbWalkAnimation.java new file mode 100644 index 0000000..7ff44d1 --- /dev/null +++ b/src/DumbWalkAnimation.java @@ -0,0 +1,53 @@ + +/** + * A WalkAnimation that does not result in a smooth transition. The formula and + * constants are chosen at random. + * + * @author Peter Wu + */ +public class DumbWalkAnimation implements WalkAnimation { + + private double robot_pos_meters; + + /** + * Sets the new position for the robot. + */ + public void updatePosition(double pos) { + this.robot_pos_meters = pos; + } + + private double getTime() { + return robot_pos_meters / 5; + } + + @Override + public double getLegAngleLeft() { + return Math.sin(-getTime()) * 30.0; + } + + @Override + public double getLegAngleRight() { + return -getLegAngleLeft(); + } + + @Override + public double getKneeAngleLeft() { + return 75.0 + Math.abs(Math.cos(getTime()) * 90.0); + } + + public double getKneeAngleRight() { + return getKneeAngleLeft(); + } + + @Override + public double getArmAngleLeft() { + // static non-moving arms. + return 0; + } + + @Override + public double getArmAngleRight() { + // static non-moving arms. + return 0; + } +} diff --git a/src/Robot.java b/src/Robot.java index 3b9c0cb..3eeb1af 100644 --- a/src/Robot.java +++ b/src/Robot.java @@ -63,6 +63,8 @@ class Robot extends BetterBase { */ private final static double TRACK_LENGTH = 76.0; + private final WalkAnimation walkAnim; + /** * Constructs the robot with initial parameters. */ @@ -85,6 +87,7 @@ class Robot extends BetterBase { this.boneSize = 0.02f; this.depth = 0.24f; this.laneNo = laneNo; + this.walkAnim = new DumbWalkAnimation(); } /** @@ -105,6 +108,9 @@ class Robot extends BetterBase { // stands on the XY plane. gl.glTranslatef(0, 0, torsoHeight / 2 + legLength + footHeight); + // calculate rotation angles and positions for movements. + walkAnim.updatePosition(robot_pos_meters); + // Draw the robot, everything is relative to the center of torso. // Static parts (that do not animate): @@ -132,11 +138,11 @@ class Robot extends BetterBase { // Parts that should be animated: // draw left and right legs - drawLeg(false); - drawLeg(true); + drawLeg(false, walkAnim.getLegAngleLeft(), walkAnim.getKneeAngleLeft()); + drawLeg(true, walkAnim.getLegAngleRight(), walkAnim.getKneeAngleRight()); // draw left and right arms - drawArm(false); - drawArm(true); + drawArm(false, walkAnim.getArmAngleLeft()); + drawArm(true, walkAnim.getArmAngleRight()); // // The following function call exist to make a point clear. Adding @@ -369,8 +375,10 @@ class Robot extends BetterBase { /** * Draws the upper and lower legs and feet of the robot. * @param isRight True if at the robot's right (from the robot POV). + * @param hip_angle Angle at the hip in degrees. + * @param knee_angle Angle behind knee in degrees. */ - private void drawLeg(boolean isRight) { + private void drawLeg(boolean isRight, double hip_angle, double knee_angle) { // save center position gl.glPushMatrix(); @@ -382,6 +390,9 @@ class Robot extends BetterBase { gl.glTranslatef(leg_top_x, 0f, -torsoHeight / 2); drawJoint(); + // rotate upper leg around hips + gl.glRotated(hip_angle, 1, 0, 0); + // upper leg half gl.glTranslatef(0, 0, -legLength / 4); drawBeam(legWidth, legWidth, legLength / 2, Direction.Z, Color.DARK_GRAY, false); @@ -390,6 +401,9 @@ class Robot extends BetterBase { gl.glTranslatef(0, 0, -legLength / 4); drawJoint(); + // rotate lower leg around back of knee (clock-wise "180 - knee angle") + gl.glRotated(knee_angle - 180, 1, 0, 0); + // lower leg half gl.glTranslatef(0, 0, -legLength / 4); drawBeam(legWidth, legWidth, legLength / 2, Direction.Z, Color.DARK_GRAY, false); @@ -410,10 +424,11 @@ class Robot extends BetterBase { } /** - * Draw both arms and both hands of the robot. + * Draw both arms and both hands of the robot. * @param isRight True if at the robot's right (from the robot POV). + * @param armAngle angle of the arm in degrees. */ - private void drawArm(boolean isRight) { + private void drawArm(boolean isRight, double armAngle) { // Push the translation matrix so we can return to the origin gl.glPushMatrix(); @@ -426,6 +441,9 @@ class Robot extends BetterBase { gl.glTranslatef(arm_x, 0, torsoHeight / 2); drawJoint(); + // rotate arm around shoulder + gl.glRotated(armAngle, 1, 0, 0); + // here is your arm (start drawing from the elbow) gl.glTranslatef(0, 0, -armLength / 2); drawBeam(armWidth, armWidth, armLength, Direction.Z, Colors.ARM_GRAY_COLOR, false); diff --git a/src/WalkAnimation.java b/src/WalkAnimation.java new file mode 100644 index 0000000..f66e7df --- /dev/null +++ b/src/WalkAnimation.java @@ -0,0 +1,65 @@ + +/** + * Provides details for animating a walk. + * + * @author Peter Wu + */ +interface WalkAnimation { + + /** + * Sets the new position for the robot. + * + * @param pos Position in meters. + */ + void updatePosition(double pos); + + /** + * Finds the angle between the left upper leg and the rotated upper leg. If + * the robot does not move, the angle is probably 0. When the leg is + * positioned behind the robot, the angle is negative. Similarly, when the + * robot is positioned forward, the angle is positive. + * + * @return angle in degrees. + */ + public double getLegAngleLeft(); + + /** + * Finds the angle between the right upper leg and the rotated upper leg. If + * the robot does not move, the angle is probably 0. When the leg is + * positioned behind the robot, the angle is negative. Similarly, when the + * robot is positioned forward, the angle is positive. + * + * @return angle in degrees. + */ + public double getLegAngleRight(); + + /** + * Finds the angle behind the left knee. + * + * @return angle in degrees. + */ + public double getKneeAngleLeft(); + + /** + * Finds the angle behind the right knee. + * + * @return angle in degrees. + */ + public double getKneeAngleRight(); + + /** + * Finds the angle between the left arm and the rotated arm. Similar to + * getLegAngleLeft(). + * + * @return angle in degrees. + */ + public double getArmAngleLeft(); + + /** + * Finds the angle between the right arm and the rotated arm. Similar to + * getLegAngleRight(). + * + * @return angle in degrees. + */ + public double getArmAngleRight(); +} -- cgit v1.2.1 From cc500dd153608caa4c6ada41ee8a2906b714f1fb Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Sun, 12 Jan 2014 14:30:14 +0100 Subject: Added unmodified robotrace sources For testing purposes. --- src/robotrace/Base.java | 388 +++++++++++++++++++++++++++++++++++++++++ src/robotrace/GlobalState.java | 83 +++++++++ src/robotrace/MainFrame.java | 178 +++++++++++++++++++ src/robotrace/Vector.java | 124 +++++++++++++ 4 files changed, 773 insertions(+) create mode 100644 src/robotrace/Base.java create mode 100644 src/robotrace/GlobalState.java create mode 100644 src/robotrace/MainFrame.java create mode 100644 src/robotrace/Vector.java diff --git a/src/robotrace/Base.java b/src/robotrace/Base.java new file mode 100644 index 0000000..0f67ce6 --- /dev/null +++ b/src/robotrace/Base.java @@ -0,0 +1,388 @@ +package robotrace; + +import com.jogamp.opengl.util.FPSAnimator; +import com.jogamp.opengl.util.gl2.GLUT; +import com.jogamp.opengl.util.texture.Texture; +import com.jogamp.opengl.util.texture.TextureIO; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.media.opengl.GL; +import javax.media.opengl.GL2; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.awt.GLJPanel; +import javax.media.opengl.glu.GLU; +import javax.swing.UIManager; + +/** + * Handles all of the RobotRace graphics functionality, + * which should be extended per the Assignments. + */ +abstract public class Base { + + // Library version number. + static public int LIBRARY_VERSION = 3; + + // Minimum distance of camera to center point. + static public float MIN_CAMERA_DISTANCE = 1f; + + // Distance multiplier per mouse wheel tick. + static public float MOUSE_WHEEL_FACTOR = 1.2f; + + // Minimum value of phi. + static public float PHI_MIN = -(float) Math.PI / 2f + 0.01f; + + // Maximum value of phi. + static public float PHI_MAX = (float) Math.PI / 2f - 0.01f; + + // Ratio of distance in pixels dragged and radial change of camera. + static public float DRAG_PIXEL_TO_RADIAN = 0.025f; + + // Minimum value of vWidth. + static public float VWIDTH_MIN = 1f; + + // Maximum value of vWidth. + static public float VWIDTH_MAX = 1000f; + + // Ratio of vertical distance dragged and change of vWidth; + static public float DRAG_PIXEL_TO_VWIDTH = 0.1f; + + // Extent of center point change based on key input. + static public float CENTER_POINT_CHANGE = 1f; + + // Desired frames per second. + static public int FPS = 30; + + + // Global state, created at startup. + protected GlobalState gs; + + // OpenGL reference, continuously updated for correct thread. + protected GL2 gl; + + // OpenGL utility functions. + protected GLU glu; + protected GLUT glut; + + // Start time of animation. + private long startTime; + + // Textures. + protected Texture track, brick, head, torso; + + /** + * Constructs base class. + */ + public Base() { + // Print library version number. + System.out.println("Using RobotRace library version " + LIBRARY_VERSION); + + // Global state. + this.gs = new GlobalState(); + + // Enable fancy GUI theme. + try { + UIManager.setLookAndFeel( + "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); + } catch(Exception ex) { + Logger.getLogger(Base.class.getName()).log(Level.SEVERE, null, ex); + } + + // GUI frame. + MainFrame frame = new MainFrame(gs); + + // OpenGL utility functions. + this.glu = new GLU(); + this.glut = new GLUT(); + + // Redirect OpenGL listener to the abstract render functions. + GLJPanel glPanel = (GLJPanel) frame.glPanel; + glPanel.addGLEventListener(new GLEventDelegate()); + + // Attach mouse and keyboard listeners. + GLListener listener = new GLListener(); + glPanel.addMouseListener(listener); + glPanel.addMouseMotionListener(listener); + glPanel.addMouseWheelListener(listener); + glPanel.addKeyListener(listener); + glPanel.setFocusable(true); + glPanel.requestFocusInWindow(); + + // Attach animator to OpenGL panel and begin refresh + // at the specified number of frames per second. + final FPSAnimator animator = + new FPSAnimator((GLJPanel) frame.glPanel, FPS, true); + animator.setIgnoreExceptions(false); + animator.setPrintExceptions(true); + + animator.start(); + + // Stop animator when window is closed. + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + animator.stop(); + } + }); + + // Show frame. + frame.setVisible(true); + } + + /** + * Called upon the start of the application. + * Primarily used to configure OpenGL. + */ + abstract public void initialize(); + + /** + * Configures the viewing transform. + */ + abstract public void setView(); + + /** + * Draws the entire scene. + */ + abstract public void drawScene(); + + /** + * Pass a vector as a vertex to OpenGL. + */ + public void glVertex(Vector vector) { + gl.glVertex3d(vector.x(), + vector.y(), + vector.z()); + } + + /** + * Delegates OpenGL events to abstract methods. + */ + private final class GLEventDelegate implements GLEventListener { + + /** + * Initialization of OpenGL state. + */ + @Override + public void init(GLAutoDrawable drawable) { + gl = drawable.getGL().getGL2(); + + // Try to load textures. + track = loadTexture("track.jpg"); + brick = loadTexture("brick.jpg"); + head = loadTexture("head.jpg"); + torso = loadTexture("torso.jpg"); + + // Print library version number. + //System.out.println("Using RobotRace library version " + LIBRARY_VERSION); + + initialize(); + } + + /** + * Try to load a texture from the given file. The file + * should be located in the same folder as RobotRace.java. + */ + private Texture loadTexture(String file) { + Texture result = null; + + try { + // Try to load from local folder. + result = TextureIO.newTexture(new File(file), false); + } catch(Exception e1) { + // Try to load from /src folder instead. + try { + result = TextureIO.newTexture(new File("src/" + file), false); + } catch(Exception e2) { + + } + } + + if(result != null) { + System.out.println("Loaded " + file); + result.enable(gl); + } + + return result; + } + + /** + * Render scene. + */ + @Override + public void display(GLAutoDrawable drawable) { + gl = drawable.getGL().getGL2(); + + // Update wall time, and reset if required. + if(gs.tAnim < 0) { + startTime = System.currentTimeMillis(); + } + gs.tAnim = (float) (System.currentTimeMillis() - startTime) / 1000f; + + // Also update view, because global state may have changed. + setView(); + drawScene(); + + // Report OpenGL errors. + int errorCode = gl.glGetError(); + while(errorCode != GL.GL_NO_ERROR) { + System.err.println(errorCode + " " + + glu.gluErrorString(errorCode)); + errorCode = gl.glGetError(); + } + } + + /** + * Canvas reshape. + */ + @Override + public void reshape(GLAutoDrawable drawable, + int x, int y, + int width, int height) { + gl = drawable.getGL().getGL2(); + + // Update state. + gs.w = width; + gs.h = height; + + setView(); + } + + @Override + public void dispose(GLAutoDrawable drawable) { + + } + + } + + /** + * Handles mouse events of the GLJPanel to support the interactive + * change of camera angles and distance in the global state. + */ + private final class GLListener implements MouseMotionListener, + MouseListener, + MouseWheelListener, + KeyListener { + // Position of mouse drag source. + private int dragSourceX, dragSourceY; + + // Last mouse button pressed. + private int mouseButton; + + @Override + public void mouseDragged(MouseEvent e) { + float dX = e.getX() - dragSourceX; + float dY = e.getY() - dragSourceY; + + // Change camera angle when left button is pressed. + if(mouseButton == MouseEvent.BUTTON1) { + gs.theta += dX * DRAG_PIXEL_TO_RADIAN; + gs.phi = Math.max(PHI_MIN, + Math.min(PHI_MAX, + gs.phi + dY * DRAG_PIXEL_TO_RADIAN)); + } + // Change vWidth when right button is pressed. + else if(mouseButton == MouseEvent.BUTTON3) { + gs.vWidth = Math.max(VWIDTH_MIN, + Math.min(VWIDTH_MAX, + gs.vWidth + dY * DRAG_PIXEL_TO_VWIDTH)); + } + + dragSourceX = e.getX(); + dragSourceY = e.getY(); + } + + @Override + public void mouseMoved(MouseEvent e) { + } + + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + gs.vDist = (float) Math.max(MIN_CAMERA_DISTANCE, + gs.vDist * + Math.pow(MOUSE_WHEEL_FACTOR, + e.getWheelRotation())); + } + + @Override + public void mouseClicked(MouseEvent e) { + } + + @Override + public void mousePressed(MouseEvent e) { + dragSourceX = e.getX(); + dragSourceY = e.getY(); + mouseButton = e.getButton(); + } + + @Override + public void mouseReleased(MouseEvent e) { + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + @Override + public void keyTyped(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + // Move center point. + double phiQ = gs.theta + Math.PI / 2.0; + + switch(e.getKeyChar()) { + // Right. + case 'a': gs.cnt = gs.cnt.subtract( + new Vector(Math.cos(phiQ), Math.sin(phiQ), 0) + .scale(CENTER_POINT_CHANGE)); + break; + // Left. + case 'd': gs.cnt = gs.cnt.add( + new Vector(Math.cos(phiQ), Math.sin(phiQ), 0) + .scale(CENTER_POINT_CHANGE)); + break; + // Forwards. + case 'w': gs.cnt = gs.cnt.subtract( + new Vector(Math.cos(gs.theta), Math.sin(gs.theta), 0) + .scale(CENTER_POINT_CHANGE)); + break; + // Backwards. + case 's': gs.cnt = gs.cnt.add( + new Vector(Math.cos(gs.theta), Math.sin(gs.theta), 0) + .scale(CENTER_POINT_CHANGE)); + break; + // Up. + case 'q': gs.cnt = new Vector(gs.cnt.x, + gs.cnt.y, + gs.cnt.z + CENTER_POINT_CHANGE); + break; + // Down. + case 'z': gs.cnt = new Vector(gs.cnt.x, + gs.cnt.y, + gs.cnt.z - CENTER_POINT_CHANGE); + break; + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + + } + +} diff --git a/src/robotrace/GlobalState.java b/src/robotrace/GlobalState.java new file mode 100644 index 0000000..b435b8a --- /dev/null +++ b/src/robotrace/GlobalState.java @@ -0,0 +1,83 @@ +package robotrace; + +/** + * The state variables that describe how the + * scene should be rendered. + */ +public class GlobalState { + + // State variables. + public boolean showAxes; // Show an axis frame if true. + public boolean showStick; // Show robot(s) as stick figures. + public int trackNr; // Track to use: + // 0 -> test track, + // 1 -> O, 2 -> L, 3 -> C, 4 -> custom. + + public float tAnim; // Time since start of animation in seconds. + + public int w; // Width of window in pixels. + public int h; // Height of window in pixels. + + public Vector cnt; // Center point. + public float vDist; // Distance eye point to center point. + public float vWidth; // Width of scene to be shown. + public float theta; // Azimuth angle in radians. + public float phi; // Inclination angle in radians. + //public boolean persp; // Perspective (true) or isometric (false) projection. + + public int camMode; // In race mode: 0 -> overview, + // 1 -> tracking helicopter, + // 2 -> view from the side on the leader, + // 3 -> view from camera on top of + // last robot, + // 4 -> autoswitch. + + //public boolean lightCamera; // Light source is attached to camera (true) + // or world (false). + + /** + * Default settings. + */ + public final void reset() { + showAxes = true; + showStick = false; + trackNr = 0; + tAnim = -1; + cnt = Vector.O; + vDist = 10f; + vWidth = 10f; + theta = 0f; + phi = 0f; + //persp = false; + camMode = 0; + //lightCamera = false; + } + + public GlobalState() { + reset(); + } + + /** + * Textual format. + */ + @Override + public String toString() { + return "GlobalState{" + + "showAxes=" + showAxes + + ", showStick=" + showStick + + ", trackNr=" + trackNr + + ", tAnim=" + tAnim + + ", w=" + w + + ", h=" + h + + ", cnt=" + cnt + + ", vDist=" + vDist + + ", vWidth=" + vWidth + + ", phi=" + theta + + ", theta=" + phi + + //", persp=" + persp + + ", camMode=" + camMode + + //", lightCamera=" + lightCamera + + '}'; + } + +} diff --git a/src/robotrace/MainFrame.java b/src/robotrace/MainFrame.java new file mode 100644 index 0000000..8ba23bb --- /dev/null +++ b/src/robotrace/MainFrame.java @@ -0,0 +1,178 @@ +package robotrace; + +import javax.media.opengl.awt.GLJPanel; + +/** + * + */ +public final class MainFrame extends javax.swing.JFrame { + + // Global state of scene. + private GlobalState gs; + + /** + * Creates new form MainFrame. + */ + public MainFrame(GlobalState globalState) { + this.gs = globalState; + + initComponents(); + updateElements(); + } + + /** + * Update UI elements to match global state. + */ + public void updateElements() { + axesCombo.setSelectedIndex(gs.showAxes ? 0 : 1); + stickCombo.setSelectedIndex(gs.showStick ? 1 : 0); + trackCombo.setSelectedIndex(gs.trackNr); + //perspectiveCombo.setSelectedIndex(gs.persp ? 1 : 0); + cameraCombo.setSelectedIndex(gs.camMode); + //lightCombo.setSelectedIndex(gs.lightCamera ? 1 : 0); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + glPanel = new GLJPanel(); + toolBar = new javax.swing.JToolBar(); + filler6 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); + axesCombo = new javax.swing.JComboBox(); + filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(10, 0), new java.awt.Dimension(10, 0), new java.awt.Dimension(10, 32767)); + stickCombo = new javax.swing.JComboBox(); + filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(10, 0), new java.awt.Dimension(10, 0), new java.awt.Dimension(10, 32767)); + trackCombo = new javax.swing.JComboBox(); + filler4 = new javax.swing.Box.Filler(new java.awt.Dimension(10, 0), new java.awt.Dimension(10, 0), new java.awt.Dimension(10, 32767)); + cameraCombo = new javax.swing.JComboBox(); + filler5 = new javax.swing.Box.Filler(new java.awt.Dimension(10, 0), new java.awt.Dimension(10, 0), new java.awt.Dimension(10, 32767)); + resetButton = new javax.swing.JButton(); + filler7 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + setTitle("RobotRace"); + + glPanel.setPreferredSize(new java.awt.Dimension(800, 800)); + glPanel.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + glPanelMouseClicked(evt); + } + }); + + javax.swing.GroupLayout glPanelLayout = new javax.swing.GroupLayout(glPanel); + glPanel.setLayout(glPanelLayout); + glPanelLayout.setHorizontalGroup( + glPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 1093, Short.MAX_VALUE) + ); + glPanelLayout.setVerticalGroup( + glPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 600, Short.MAX_VALUE) + ); + + getContentPane().add(glPanel, java.awt.BorderLayout.CENTER); + + toolBar.setFloatable(false); + toolBar.setRollover(true); + toolBar.add(filler6); + + axesCombo.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Visible axes", "Invisible axes" })); + axesCombo.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + axesComboItemStateChanged(evt); + } + }); + toolBar.add(axesCombo); + toolBar.add(filler1); + + stickCombo.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Full robots", "Stick figures" })); + stickCombo.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + stickComboItemStateChanged(evt); + } + }); + toolBar.add(stickCombo); + toolBar.add(filler2); + + trackCombo.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Test track", "O track", "L track", "C track", "Custom track" })); + trackCombo.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + trackComboItemStateChanged(evt); + } + }); + toolBar.add(trackCombo); + toolBar.add(filler4); + + cameraCombo.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Overview mode", "Helicopter mode", "Motorcycle mode", "First person mode", "Auto mode" })); + cameraCombo.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + cameraComboItemStateChanged(evt); + } + }); + toolBar.add(cameraCombo); + toolBar.add(filler5); + + resetButton.setText("Reset"); + resetButton.setFocusable(false); + resetButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + resetButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + resetButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + resetButtonActionPerformed(evt); + } + }); + toolBar.add(resetButton); + toolBar.add(filler7); + + getContentPane().add(toolBar, java.awt.BorderLayout.PAGE_START); + + pack(); + }// //GEN-END:initComponents + + private void axesComboItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_axesComboItemStateChanged + gs.showAxes = axesCombo.getSelectedIndex() == 0; + }//GEN-LAST:event_axesComboItemStateChanged + + private void stickComboItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_stickComboItemStateChanged + gs.showStick = stickCombo.getSelectedIndex() == 1; + }//GEN-LAST:event_stickComboItemStateChanged + + private void trackComboItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_trackComboItemStateChanged + gs.trackNr = trackCombo.getSelectedIndex(); + }//GEN-LAST:event_trackComboItemStateChanged + + private void cameraComboItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cameraComboItemStateChanged + gs.camMode = cameraCombo.getSelectedIndex(); + }//GEN-LAST:event_cameraComboItemStateChanged + + private void resetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resetButtonActionPerformed + gs.reset(); + updateElements(); + }//GEN-LAST:event_resetButtonActionPerformed + + private void glPanelMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_glPanelMouseClicked + glPanel.requestFocusInWindow(); + }//GEN-LAST:event_glPanelMouseClicked + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JComboBox axesCombo; + private javax.swing.JComboBox cameraCombo; + private javax.swing.Box.Filler filler1; + private javax.swing.Box.Filler filler2; + private javax.swing.Box.Filler filler4; + private javax.swing.Box.Filler filler5; + private javax.swing.Box.Filler filler6; + private javax.swing.Box.Filler filler7; + protected javax.swing.JPanel glPanel; + private javax.swing.JButton resetButton; + private javax.swing.JComboBox stickCombo; + private javax.swing.JToolBar toolBar; + private javax.swing.JComboBox trackCombo; + // End of variables declaration//GEN-END:variables +} diff --git a/src/robotrace/Vector.java b/src/robotrace/Vector.java new file mode 100644 index 0000000..eb71191 --- /dev/null +++ b/src/robotrace/Vector.java @@ -0,0 +1,124 @@ +package robotrace; + +/** + * Represents a 3D vector (immutable). + */ +public class Vector { + + // Origin and axis vectors. + public final static Vector O = new Vector(0, 0, 0); + public final static Vector X = new Vector(1, 0, 0); + public final static Vector Y = new Vector(0, 1, 0); + public final static Vector Z = new Vector(0, 0, 1); + + // Components. + protected double x, y, z; + + /** + * Construct from components. + */ + public Vector(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Get X component. + */ + public double x() { + return x; + } + + /** + * Get Y component. + */ + public double y() { + return y; + } + + /** + * Get Z component. + */ + public double z() { + return z; + } + + /** + * Euclidian length squared. + */ + public double lengthSqr() { + return x * x + y * y + z * z; + } + + /** + * Euclidian length. + */ + public double length() { + return Math.sqrt(x * x + y * y + z * z); + } + + /** + * Normalized vector, which is not defined for + * a vector of zero length. + */ + public Vector normalized() { + double length = length(); + + return new Vector(x / length, y / length, z / length); + } + + /** + * Dot product with the given vector. + */ + public double dot(Vector that) { + return this.x * that.x + + this.y * that.y + + this.z * that.z; + } + + /** + * Cross product with the given vector. + */ + public Vector cross(Vector that) { + return new Vector(this.y * that.z - this.z * that.y, + this.z * that.x - this.x * that.z, + this.x * that.y - this.y * that.x); + } + + /** + * Add with the given vector. + */ + public Vector add(Vector that) { + return new Vector(this.x + that.x, + this.y + that.y, + this.z + that.z); + } + + /** + * Subtract by the given vector. + */ + public Vector subtract(Vector that) { + return new Vector(this.x - that.x, + this.y - that.y, + this.z - that.z); + } + + /** + * Multiply with the given scalar. + */ + public Vector scale(double scalar) { + return new Vector(scalar * this.x, + scalar * this.y, + scalar * this.z); + } + + /** + * String representation. + */ + @Override + public String toString() { + return "(" + x + "," + y + "," + z + ")"; + } + +} -- cgit v1.2.1 From 1ce5f6dc78e4c1636b89338bd428c2b6830b9189 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Sun, 12 Jan 2014 19:47:46 +0100 Subject: Workaround for slow HW rendering GLJPanel thinks it is a great idea to use glReadPixels. The Intel i965 DRI driver thinks otherwise and does no get more than 2 FPS on a i5-460M CPU (HD Graphics). With Mesa software rendering (LIBGL_ALWAYS_SOFTWARE=1), the performance gets stuck on 20 FPS or something. Unfortunately, the software renderer crashes since Mesa 10[1]. This workaround improves performance by replacing GLJPanel by GLCanvas as mentioned in the Jogamp wiki[2]. FPS now caps on 30 (but this can be bumped to 42 by changing Base.FPS to 60). [1]: https://bugs.freedesktop.org/show_bug.cgi?id=72926 [2]: http://jogamp.org/wiki/index.php/Using_JOGL_in_AWT_SWT_and_Swing --- src/robotrace/Base.java | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/robotrace/Base.java b/src/robotrace/Base.java index 0f67ce6..e083afd 100644 --- a/src/robotrace/Base.java +++ b/src/robotrace/Base.java @@ -4,6 +4,7 @@ import com.jogamp.opengl.util.FPSAnimator; import com.jogamp.opengl.util.gl2.GLUT; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureIO; +import java.awt.BorderLayout; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; @@ -20,6 +21,7 @@ import javax.media.opengl.GL; import javax.media.opengl.GL2; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLEventListener; +import javax.media.opengl.awt.GLCanvas; import javax.media.opengl.awt.GLJPanel; import javax.media.opengl.glu.GLU; import javax.swing.UIManager; @@ -107,21 +109,26 @@ abstract public class Base { // Redirect OpenGL listener to the abstract render functions. GLJPanel glPanel = (GLJPanel) frame.glPanel; - glPanel.addGLEventListener(new GLEventDelegate()); + + // HACK: get better performance + GLCanvas glCanvas = new GLCanvas(); + frame.remove(glPanel); + frame.getContentPane().add(glCanvas, BorderLayout.CENTER); + glCanvas.addGLEventListener(new GLEventDelegate()); // Attach mouse and keyboard listeners. GLListener listener = new GLListener(); - glPanel.addMouseListener(listener); - glPanel.addMouseMotionListener(listener); - glPanel.addMouseWheelListener(listener); - glPanel.addKeyListener(listener); - glPanel.setFocusable(true); - glPanel.requestFocusInWindow(); - + glCanvas.addMouseListener(listener); + glCanvas.addMouseMotionListener(listener); + glCanvas.addMouseWheelListener(listener); + glCanvas.addKeyListener(listener); + glCanvas.setFocusable(true); + glCanvas.requestFocusInWindow(); + // Attach animator to OpenGL panel and begin refresh // at the specified number of frames per second. final FPSAnimator animator = - new FPSAnimator((GLJPanel) frame.glPanel, FPS, true); + new FPSAnimator(glCanvas, FPS, true); animator.setIgnoreExceptions(false); animator.setPrintExceptions(true); -- cgit v1.2.1 From f27ed95f90dbc81c7a89a58fb07ac632f4cfdaf9 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Sun, 12 Jan 2014 19:56:38 +0100 Subject: Add shell script to run the program Includes a hack (disabled by default) that tries to extract an older Mesa package for Arch Linux. --- run.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100755 run.sh diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..cb5cebb --- /dev/null +++ b/run.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# Mesa 10 crashes with software rendering. To get faster speed on Intel, patch +# src/robotrace/Base.java to use GLCanvas. +LIBGL_ALWAYS_SOFTWARE=0 +MESA_ROOT=/tmp/mesa-root + +if [ $LIBGL_ALWAYS_SOFTWARE -ne 0 ] && [ ! -d "$MESA_ROOT" ]; then + mkdir "$MESA_ROOT" + + # Arch Linux: use older versions (note: LLVM-3.4 still gets loaded somehow + # which triggers a crash on close). + for pkg in \ + mesa-9.2.4-1 \ + mesa-libgl-9.2.4-1 \ + llvm-libs-3.3-1 \ + ; do + tar xf /var/cache/pacman/pkg/$pkg-x86_64.pkg.tar.xz -C "$MESA_ROOT" + done +fi + +if [ $# -eq 0 ]; then + set -- java -ea -cp lib/gluegen-rt.jar:lib/jogl-all.jar:build/classes RobotRace +elif [ $1 = gdb ]; then + p="$(dirname "$0")" + set -- "$@" -q --args java -ea -cp "$p/lib/gluegen-rt.jar:$p/lib/jogl-all.jar:$p/build/classes" RobotRace + unset p +fi + +if [ $LIBGL_ALWAYS_SOFTWARE -eq 0 ]; then + "$@" + exit +fi + +#LD_DEBUG=all LD_DEBUG_OUTPUT=/tmp/dbg \ +LIBGL_ALWAYS_SOFTWARE=1 \ +LIBGL_DRIVERS_PATH="$MESA_ROOT/usr/lib/xorg/modules/dri" \ +EGL_DRIVERS_PATH="$MESA_ROOT/usr/lib/egl" \ +LD_LIBRARY_PATH="$MESA_ROOT/usr/lib" \ +"$@" +# vim: set sw=4 et ts=4: -- cgit v1.2.1