summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrank v/d Haterd <f.h.a.v.d.haterd@student.tue.nl>2014-01-13 03:06:31 +0100
committerFrank v/d Haterd <f.h.a.v.d.haterd@student.tue.nl>2014-01-13 03:06:31 +0100
commitb8bd3d7bb5b7284879240ab6eb65f2b4619e2d9f (patch)
treede531ae1f8c24a30c69eb81fac682a91ee245a0c
parent646e75208290fe1593df9494591195fd41054c2e (diff)
parentf27ed95f90dbc81c7a89a58fb07ac632f4cfdaf9 (diff)
download2iv60-robots-b8bd3d7bb5b7284879240ab6eb65f2b4619e2d9f.tar.gz
Merge branch 'master' of git.lekensteyn.nl:tue/2iv60-robots
-rwxr-xr-xrun.sh40
-rw-r--r--src/Camera.java1
-rw-r--r--src/DumbWalkAnimation.java53
-rw-r--r--src/Robot.java32
-rw-r--r--src/RobotRace.java96
-rw-r--r--src/WalkAnimation.java65
-rw-r--r--src/robotrace/Base.java395
-rw-r--r--src/robotrace/GlobalState.java83
-rw-r--r--src/robotrace/MainFrame.java178
-rw-r--r--src/robotrace/Vector.java124
10 files changed, 1055 insertions, 12 deletions
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:
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/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());
//<editor-fold>
// 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/RobotRace.java b/src/RobotRace.java
index eac9ed5..093e021 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;
@@ -152,6 +153,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.
*/
@@ -292,6 +308,11 @@ 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();
// Select part of window.
gl.glViewport(0, 0, gs.w, gs.h);
@@ -389,19 +410,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;
@@ -611,6 +647,27 @@ 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;
+ 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;
}
@@ -631,4 +688,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;
+ }
}
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
+ * <code>getLegAngleLeft()</code>.
+ *
+ * @return angle in degrees.
+ */
+ public double getArmAngleLeft();
+
+ /**
+ * Finds the angle between the right arm and the rotated arm. Similar to
+ * <code>getLegAngleRight()</code>.
+ *
+ * @return angle in degrees.
+ */
+ public double getArmAngleRight();
+}
diff --git a/src/robotrace/Base.java b/src/robotrace/Base.java
new file mode 100644
index 0000000..e083afd
--- /dev/null
+++ b/src/robotrace/Base.java
@@ -0,0 +1,395 @@
+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.BorderLayout;
+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.GLCanvas;
+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;
+
+ // 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();
+ 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(glCanvas, 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")
+ // <editor-fold defaultstate="collapsed" desc="Generated Code">//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();
+ }// </editor-fold>//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 + ")";
+ }
+
+}