summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Wu <lekensteyn@gmail.com>2014-01-12 14:30:14 +0100
committerPeter Wu <lekensteyn@gmail.com>2014-01-12 14:30:14 +0100
commitcc500dd153608caa4c6ada41ee8a2906b714f1fb (patch)
tree2a608cad91fd9ca0910a17a4daf5e35fdac0024b
parent9d2f034864f147f30c9bb823c951348eb572e9f3 (diff)
download2iv60-robots-cc500dd153608caa4c6ada41ee8a2906b714f1fb.tar.gz
Added unmodified robotrace sources
For testing purposes.
-rw-r--r--src/robotrace/Base.java388
-rw-r--r--src/robotrace/GlobalState.java83
-rw-r--r--src/robotrace/MainFrame.java178
-rw-r--r--src/robotrace/Vector.java124
4 files changed, 773 insertions, 0 deletions
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")
+ // <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 + ")";
+ }
+
+}