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