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) { } } }