3D Graphics Java

/*
 * @(#)HelloUniverse.java 1.17 02/10/21 13:58:47
 * 
 * Copyright (c) 1996-2002 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: -
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer. - Redistribution in binary
 * form must reproduce the above copyright notice, this list of conditions and
 * the following disclaimer in the documentation and/or other materials provided
 * with the distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.
 * 
 * You acknowledge that Software is not designed,licensed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility.
 */
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GridLayout;
import java.awt.Panel;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.util.Enumeration;
import javax.media.j3d.Alpha;
import javax.media.j3d.Behavior;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.InputDevice;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.Sensor;
import javax.media.j3d.SensorRead;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.WakeupOnElapsedFrames;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3f;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.ColorCube;
import com.sun.j3d.utils.universe.SimpleUniverse;
public class HelloUniverse extends Applet {
  private SimpleUniverse u = null;
  public BranchGroup createSceneGraph() {
    BranchGroup objRoot = new BranchGroup();
    TransformGroup objTrans = new TransformGroup();
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    objRoot.addChild(objTrans);
    objTrans.addChild(new ColorCube(0.2));
    Transform3D yAxis = new Transform3D();
    Alpha rotationAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE, 0, 0,
        4000, 0, 0, 0, 0, 0);
    RotationInterpolator rotator = new RotationInterpolator(rotationAlpha,
        objTrans, yAxis, 0.0f, (float) Math.PI * 2.0f);
    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
        100.0);
    rotator.setSchedulingBounds(bounds);
    objTrans.addChild(rotator);
    return objRoot;
  }
  public HelloUniverse() {
  }
  public void init() {
    // These are the string arguments given to the VirtualInputDevice
    // constructor. These are settable parameters. Look in the
    // VirtualInputDevice constructor for a complete list.
    String[] args = new String[10];
    args[0] = "printvalues";
    args[1] = "true";
    args[2] = "yscreeninitloc";
    args[3] = "50";
    args[4] = null;
    InputDevice device = new VirtualInputDevice(args);
    // now create the HelloUniverse Canvas
    setLayout(new BorderLayout());
    GraphicsConfiguration config = SimpleUniverse
        .getPreferredConfiguration();
    Canvas3D c = new Canvas3D(config);
    add("Center", c);
    // Create a simple scene and attach it to the virtual universe
    BranchGroup scene = createSceneGraph();
    u = new SimpleUniverse(c);
    // The InputDevice must be initialized before registering it
    // with the PhysicalEnvironment object.
    device.initialize();
    // Register the VirtualInputDevice with Java 3D
    u.getViewer().getPhysicalEnvironment().addInputDevice(device);
    TransformGroup viewTrans = u.getViewingPlatform()
        .getViewPlatformTransform();
    SensorBehavior s = new SensorBehavior(viewTrans, device.getSensor(0));
    s.setSchedulingBounds(new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
        Float.MAX_VALUE));
    scene.addChild(s);
    u.addBranchGraph(scene);
  }
  public void destroy() {
    u.cleanup();
  }
  public static void main(String[] args) {
    new MainFrame(new HelloUniverse(), 350, 350);
  }
}
class VirtualInputDevice implements InputDevice {
  private Vector3f position = new Vector3f();
  private Transform3D newTransform = new Transform3D();
  Sensor sensors[] = new Sensor[1];
  // The wheel controls control the view platform orientation
  private RotationControls rotControls;
  // The button position controls control the view platform position
  private PositionControls positionControls;
  private Transform3D rotTransX = new Transform3D();
  private Transform3D rotTransY = new Transform3D();
  private Transform3D rotTransZ = new Transform3D();
  private Vector3f initPos = new Vector3f();
  private int processingMode;
  private SensorRead sensorRead = new SensorRead();
  // These are the settable parameters.
  private boolean printvalues;
  private int xscreeninitloc;
  private int yscreeninitloc;
  private int xscreensize;
  private int yscreensize;
  private float xobjinitloc;
  private float yobjinitloc;
  private float zobjinitloc;
  private float xaxisrotinit;
  private float yaxisrotinit;
  private float zaxisrotinit;
  /*
   * Create a device, and use the string arguments in args to construct the
   * device with user preferences.
   */
  public VirtualInputDevice(String[] args) {
    // default user-definable values
    printvalues = false;
    xscreeninitloc = 400;
    yscreeninitloc = 0;
    xscreensize = 400;
    yscreensize = 200;
    xobjinitloc = 0.0f;
    yobjinitloc = 0.0f;
    zobjinitloc = 2.2f;
    xaxisrotinit = 0.0f;
    yaxisrotinit = 0.0f;
    zaxisrotinit = 0.0f;
    for (int i = 0; i < args.length; i += 2) {
      if (args[i] == null)
        break;
      else if (args[i] == "printvalues")
        printvalues = (Boolean.valueOf(args[i + 1])).booleanValue();
      else if (args[i] == "xscreeninitloc")
        xscreeninitloc = (Integer.valueOf(args[i + 1])).intValue();
      else if (args[i] == "yscreeninitloc")
        yscreeninitloc = (Integer.valueOf(args[i + 1])).intValue();
      else if (args[i] == "xscreensize")
        xscreensize = (Integer.valueOf(args[i + 1])).intValue();
      else if (args[i] == "yscreensize")
        yscreensize = (Integer.valueOf(args[i + 1])).intValue();
      else if (args[i] == "xobjinitloc")
        xobjinitloc = (Float.valueOf(args[i + 1])).floatValue();
      else if (args[i] == "yobjinitloc")
        yobjinitloc = (Float.valueOf(args[i + 1])).floatValue();
      else if (args[i] == "zobjinitloc")
        zobjinitloc = (Integer.valueOf(args[i + 1])).floatValue();
    }
    if (printvalues == true) {
      System.out.println("Initial values for VirtualInputDevice:");
      System.out.println("xscreeninitloc = " + xscreeninitloc);
      System.out.println("yscreeninitloc = " + yscreeninitloc);
      System.out.println("xscreeninitsize = " + xscreensize);
      System.out.println("yscreeninitsize = " + yscreensize);
      System.out.println("xobjinitloc = " + xobjinitloc);
      System.out.println("yobjinitloc = " + yobjinitloc);
      System.out.println("zobjinitloc = " + zobjinitloc);
      System.out.println("xaxisrotinit = " + xaxisrotinit);
      System.out.println("yaxisrotinit = " + yaxisrotinit);
      System.out.println("zaxisrotinit = " + zaxisrotinit);
    }
    // initialize the InputDevice GUI
    Frame deviceFrame = new Frame();
    deviceFrame.setSize(xscreensize, yscreensize);
    deviceFrame.setLocation(xscreeninitloc, yscreeninitloc);
    deviceFrame.setTitle("Virtual Input Device");
    ButtonPositionControls positionControls;
    // initialize position with initial x, y, and z position
    positionControls = new ButtonPositionControls(xobjinitloc, yobjinitloc,
        zobjinitloc);
    WheelControls rotControls;
    // initialize rotations with initial angles in radians)
    rotControls = new WheelControls(xaxisrotinit, yaxisrotinit,
        zaxisrotinit);
    positionControls.setDevice(this);
    Panel devicePanel = new Panel();
    devicePanel.setLayout(new BorderLayout());
    devicePanel.add("East", positionControls);
    devicePanel.add("West", rotControls);
    deviceFrame.add(devicePanel);
    deviceFrame.pack();
    deviceFrame.setVisible(true);
    initPos.set(xobjinitloc, yobjinitloc, zobjinitloc);
    this.positionControls = positionControls;
    this.rotControls = rotControls;
    // default processing mode
    processingMode = InputDevice.DEMAND_DRIVEN;
    sensors[0] = new Sensor(this);
  }
  public void close() {
  }
  public int getProcessingMode() {
    return processingMode;
  }
  public int getSensorCount() {
    return sensors.length;
  }
  public Sensor getSensor(int sensorIndex) {
    return sensors[sensorIndex];
  }
  public boolean initialize() {
    return true;
  }
  public void pollAndProcessInput() {
    sensorRead.setTime(System.currentTimeMillis());
    rotTransX.rotX(-rotControls.getXAngle());
    rotTransY.rotY(-rotControls.getYAngle());
    rotTransZ.rotZ(-rotControls.getZAngle());
    positionControls.getPosition(position);
    newTransform.set(position);
    newTransform.mul(rotTransX);
    newTransform.mul(rotTransY);
    newTransform.mul(rotTransZ);
    sensorRead.set(newTransform);
    sensors[0].setNextSensorRead(sensorRead);
  }
  public void processStreamInput() {
  }
  public void setNominalPositionAndOrientation() {
    sensorRead.setTime(System.currentTimeMillis());
    rotTransX.rotX(xaxisrotinit);
    rotTransY.rotY(yaxisrotinit);
    rotTransZ.rotZ(zaxisrotinit);
    position.set(initPos);
    newTransform.set(position);
    newTransform.mul(rotTransX);
    newTransform.mul(rotTransY);
    newTransform.mul(rotTransZ);
    sensorRead.set(newTransform);
    sensors[0].setNextSensorRead(sensorRead);
    rotControls.reset();
    positionControls.setPosition(initPos);
  }
  public void setProcessingMode(int mode) {
    // A typical driver might implement only one of these modes, and
    // throw an exception when there is an attempt to switch modes.
    // However, this example allows one to use any processing mode.
    switch (mode) {
    case InputDevice.DEMAND_DRIVEN:
    case InputDevice.NON_BLOCKING:
    case InputDevice.BLOCKING:
      processingMode = mode;
      break;
    default:
      throw new IllegalArgumentException("Processing mode must "
          + "be one of DEMAND_DRIVEN, NON_BLOCKING, or BLOCKING");
    }
  }
}
class SensorBehavior extends Behavior {
  private WakeupOnElapsedFrames conditions = new WakeupOnElapsedFrames(0);
  private TransformGroup transformGroup;
  private Sensor sensor;
  private Transform3D transform = new Transform3D();
  public SensorBehavior(TransformGroup tg, Sensor sensor) {
    transformGroup = tg;
    this.sensor = sensor;
  }
  public void initialize() {
    wakeupOn(conditions);
  }
  public void processStimulus(Enumeration criteria) {
    sensor.getRead(transform);
    transformGroup.setTransform(transform);
    wakeupOn(conditions);
  }
}
//Classes that implement this interface must be a
//subclass of java.awt.Component
interface PositionControls {
  /**
   * Get the position
   */
  public void getPosition(Vector3f pos);
  /**
   * Set the position
   */
  public void setPosition(Vector3f pos);
  /**
   * Increment added to position each time mouse is pressed or if the mouse is
   * held down each time the Sensor is read
   */
  public void setStepRate(float stepRate);
}
//Classes that implement this interface must be a subclass
//of java.awt.Component
interface RotationControls {
  /**
   * Get the angle of Rotation around the X Axis
   */
  public abstract float getXAngle();
  /**
   * Get the angle or Rotation around the Y Axis
   */
  public abstract float getYAngle();
  /**
   * Get the angle or Rotation around the Z Axis
   */
  public abstract float getZAngle();
  /**
   * Reset angles to original angle.
   */
  public abstract void reset();
}
class WheelControls extends Canvas implements RotationControls,
    MouseMotionListener, MouseListener {
  private final static int NONE = 0;
  private final static int SLIDE_Y = 1;
  private final static int SLIDE_X = 2;
  private final static int SLIDE_Z = 3;
  private int mode = NONE;
  private Dimension size;
  private int thickness;
  private int diameter;
  private int space;
  private int pipSize;
  private int pipOffset; // Amount pip is below wheel
  private int margin; // Margin between edge of Canvas and
  // controls
  private Polygon yPip;
  private Rectangle yBackClip;
  private Polygon xPip;
  private Rectangle xBackClip;
  private Polygon zPip;
  private Rectangle yArea;
  private Rectangle xArea;
  private Rectangle zArea;
  private Point oldMousePos = new Point();
  float yAngle = 0.0f;
  float xAngle = 0.0f;
  float zAngle = 0.0f;
  float yOrigAngle;
  float xOrigAngle;
  float zOrigAngle;
  float angleStep = (float) Math.PI / 30.0f;
  public WheelControls() {
    this(0.0f, 0.0f, 0.0f);
  }
  public WheelControls(float rotX, float rotY, float rotZ) {
    size = new Dimension(200, 200);
    xAngle = constrainAngle(rotX);
    yAngle = constrainAngle(rotY);
    zAngle = constrainAngle(rotZ);
    yOrigAngle = yAngle;
    xOrigAngle = xAngle;
    zOrigAngle = zAngle;
    setSizes();
    yPip = new Polygon();
    yPip.addPoint(0, 0);
    yPip.addPoint(-pipSize / 2, pipSize);
    yPip.addPoint(pipSize / 2, pipSize);
    xPip = new Polygon();
    xPip.addPoint(0, 0);
    xPip.addPoint(pipSize, -pipSize / 2);
    xPip.addPoint(pipSize, pipSize / 2);
    zPip = new Polygon();
    zPip.addPoint(diameter / 2, pipOffset);
    zPip.addPoint(diameter / 2 - pipSize / 2, pipOffset - pipSize);
    zPip.addPoint(diameter / 2 + pipSize / 2, pipOffset - pipSize);
    addMouseListener(this);
    addMouseMotionListener(this);
  }
  private void setSizes() {
    margin = 10;
    int width = size.width - margin * 2;
    thickness = width * 7 / 100;
    diameter = width * 70 / 100;
    space = width * 10 / 100;
    pipSize = width * 7 / 100;
    pipOffset = thickness / 2;
  }
  public void paint(Graphics g) {
    Graphics2D g2 = (Graphics2D) g;
    g.drawOval(margin, margin, diameter, diameter);
    zArea = new Rectangle(margin, margin, diameter, diameter);
    drawZPip(g2, zAngle);
    g.drawRect(margin, margin + diameter + space, diameter, thickness); // Y
                                      // Wheel
    yArea = new Rectangle(margin, margin + diameter + space, margin
        + diameter, thickness + pipOffset);
    yBackClip = new Rectangle(margin - thickness, margin + diameter + space
        + thickness, margin + diameter + thickness * 2, thickness);
    drawYPip(g2, yAngle);
    g.drawRect(margin + diameter + space, margin, thickness, diameter); // X
                                      // Wheel
    xArea = new Rectangle(margin + diameter + space, margin, thickness
        + pipOffset, margin + diameter);
    xBackClip = new Rectangle(margin + diameter + space + thickness, margin
        - thickness, thickness, margin + diameter + thickness * 2);
    drawXPip(g2, xAngle);
  }
  public float getXAngle() {
    return xAngle;
  }
  public float getYAngle() {
    return yAngle;
  }
  public float getZAngle() {
    return zAngle;
  }
  public void reset() {
    // Overwrite the old pip
    drawYPip((Graphics2D) (this.getGraphics()), yAngle);
    yAngle = yOrigAngle;
    // Draw the new Pip
    drawYPip((Graphics2D) (this.getGraphics()), yAngle);
    // Overwrite the old pip
    drawXPip((Graphics2D) (this.getGraphics()), xAngle);
    xAngle = xOrigAngle;
    // Draw the new Pip
    drawXPip((Graphics2D) (this.getGraphics()), xAngle);
    drawZPip((Graphics2D) (this.getGraphics()), zAngle);
    zAngle = zOrigAngle;
    drawZPip((Graphics2D) (this.getGraphics()), zAngle);
    oldMousePos.setLocation(0, 0);
  }
  private void drawXPip(Graphics2D g2, float angle) {
    AffineTransform trans = new AffineTransform();
    int y;
    int xOrig = margin + diameter + space;
    int yOrig = margin;
    Color origColor = g2.getColor();
    if (angle <= Math.PI) {
      y = yOrig
          + diameter
          - (int) ((Math.abs(angle - Math.PI / 2) / (Math.PI / 2))
              * diameter / 2);
    } else
      y = yOrig
          + (int) ((Math.abs((angle - Math.PI * 1.5)) / (Math.PI / 2))
              * diameter / 2);
    if (angle < Math.PI / 2 || angle > Math.PI * 1.5)
      g2.setColor(Color.red); // Infront of wheel
    else {
      g2.setColor(Color.black); // Behind Wheel
      g2.setClip(xBackClip);
    }
    g2.setXORMode(getBackground());
    trans.setToTranslation(xOrig + pipOffset, y);
    g2.setTransform(trans);
    g2.fillPolygon(xPip);
    // Reset graphics context
    trans.setToIdentity();
    g2.setTransform(trans);
    g2.setColor(origColor);
    g2.setPaintMode();
  }
  private void drawYPip(Graphics2D g2, float angle) {
    AffineTransform trans = new AffineTransform();
    int x;
    int xOrig = margin;
    int yOrig = margin + diameter + space;
    Color origColor = g2.getColor();
    if (angle <= Math.PI) {
      x = xOrig
          + diameter
          - (int) ((Math.abs(angle - Math.PI / 2) / (Math.PI / 2))
              * diameter / 2);
    } else
      x = xOrig
          + (int) ((Math.abs((angle - Math.PI * 1.5)) / (Math.PI / 2))
              * diameter / 2);
    if (angle < Math.PI / 2 || angle > Math.PI * 1.5)
      g2.setColor(Color.red); // Infront on wheel
    else {
      g2.setColor(Color.black); // Behind Wheel
      g2.setClip(yBackClip);
    }
    g2.setXORMode(getBackground());
    trans.setToTranslation(x, yOrig + pipOffset);
    g2.setTransform(trans);
    g2.fillPolygon(yPip);
    // Reset graphics context
    trans.setToIdentity();
    g2.setTransform(trans);
    g2.setColor(origColor);
    g2.setPaintMode();
  }
  private void drawZPip(Graphics2D g2, float zAngle) {
    AffineTransform trans = new AffineTransform();
    Color origColor = g2.getColor();
    trans.translate(margin, margin);
    trans.rotate(zAngle, diameter / 2, diameter / 2);
    g2.setXORMode(getBackground());
    g2.setTransform(trans);
    g2.setColor(Color.red);
    g2.fillPolygon(zPip);
    // Reset graphics context
    trans.setToIdentity();
    g2.setTransform(trans);
    g2.setColor(origColor);
    g2.setPaintMode();
  }
  public Dimension getPreferredSize() {
    return size;
  }
  public void setSize(Dimension d) {
    // Set size to smallest dimension
    if (d.width < d.height)
      size.width = size.height = d.width;
    else
      size.width = size.height = d.height;
    setSizes();
  }
  public void mouseClicked(MouseEvent e) {
  }
  public void mouseEntered(MouseEvent e) {
  }
  public void mouseExited(MouseEvent e) {
  }
  public void mousePressed(MouseEvent e) {
    if (yArea.contains(e.getPoint())) {
      mode = SLIDE_Y;
      oldMousePos = e.getPoint();
    } else if (xArea.contains(e.getPoint())) {
      mode = SLIDE_X;
      oldMousePos = e.getPoint();
    } else if (zArea.contains(e.getPoint())) {
      mode = SLIDE_Z;
      oldMousePos = e.getPoint();
    }
  }
  public void mouseReleased(MouseEvent e) {
    mode = NONE;
  }
  public void mouseDragged(MouseEvent e) {
    Point pos = e.getPoint();
    int diffX = pos.x - oldMousePos.x;
    int diffY = pos.y - oldMousePos.y;
    switch (mode) {
    case NONE:
      break;
    case SLIDE_Y:
      // Overwrite the old pip
      drawYPip((Graphics2D) ((Canvas) e.getSource()).getGraphics(),
          yAngle);
      if (diffX < 0)
        yAngle -= angleStep;
      else if (diffX > 0)
        yAngle += angleStep;
      yAngle = constrainAngle(yAngle);
      // Draw the new Pip
      drawYPip((Graphics2D) ((Canvas) e.getSource()).getGraphics(),
          yAngle);
      oldMousePos = pos;
      break;
    case SLIDE_X:
      // Overwrite the old pip
      drawXPip((Graphics2D) ((Canvas) e.getSource()).getGraphics(),
          xAngle);
      if (diffY < 0)
        xAngle -= angleStep;
      else if (diffY > 0)
        xAngle += angleStep;
      xAngle = constrainAngle(xAngle);
      // Draw the new Pip
      drawXPip((Graphics2D) ((Canvas) e.getSource()).getGraphics(),
          xAngle);
      oldMousePos = pos;
      break;
    case SLIDE_Z:
      drawZPip((Graphics2D) ((Canvas) e.getSource()).getGraphics(),
          zAngle);
      if (diffX < 0)
        zAngle -= angleStep;
      else if (diffX > 0)
        zAngle += angleStep;
      zAngle = constrainAngle(zAngle);
      drawZPip((Graphics2D) ((Canvas) e.getSource()).getGraphics(),
          zAngle);
      oldMousePos = pos;
      break;
    default:
      throw (new RuntimeException("Internal Error"));
    }
  }
  public void mouseMoved(MouseEvent e) {
  }
  /**
   * Constrain angle to be 0    */
  private float constrainAngle(float angle) {
    if (angle > (float) Math.PI * 2)
      return angle - (float) Math.PI * 2;
    if (angle < 0.0f)
      return angle + (float) Math.PI * 2;
    return angle;
  }
}
class ButtonPositionControls extends Panel implements PositionControls,
    MouseListener {
  private final static int STILL = 0;
  private final static int MOVING_UP = 1;
  private final static int MOVING_DOWN = 2;
  private final static int MOVING_LEFT = 3;
  private final static int MOVING_RIGHT = 4;
  private final static int MOVING_FORWARD = 5;
  private final static int MOVING_BACK = 6;
  // initial mode
  private int mode = STILL;
  Vector3f position = new Vector3f();
  Vector3f orig_position = new Vector3f();
  private Button leftB = new Button("Move Left");
  private Button rightB = new Button("Move Right");
  private Button upB = new Button("Move Up");
  private Button downB = new Button("Move Down");
  private Button forwardB = new Button("Move Forward");
  private Button backwardB = new Button("Move Back");
  private Button reset = new Button("Reset");
  private InputDevice device;
  private float step_rate = 0.0023f; // movement rate per millisecond
  private long time_last_state_change = System.currentTimeMillis();
  // the constructor arguments are the intitial X, Y, and Z positions
  public ButtonPositionControls(float x, float y, float z) {
    // up, down, right, and left movement buttons
    Panel panPanel = new Panel();
    panPanel.setLayout(new BorderLayout());
    panPanel.add("North", upB);
    panPanel.add("East", rightB);
    panPanel.add("South", downB);
    panPanel.add("West", leftB);
    // forward, backward, and reset buttons
    Panel p = new Panel();
    p.setLayout(new GridLayout(0, 1, 0, 0));
    p.add(forwardB);
    p.add(backwardB);
    p.add(reset);
    // set the initial position
    position.x = x;
    position.y = y;
    position.z = z;
    orig_position.set(position);
    // add a mouse listener to each button
    upB.addMouseListener(this);
    downB.addMouseListener(this);
    leftB.addMouseListener(this);
    rightB.addMouseListener(this);
    forwardB.addMouseListener(this);
    backwardB.addMouseListener(this);
    reset.addMouseListener(this);
    this.setLayout(new BorderLayout());
    add("East", p);
    add("West", panPanel);
  }
  public void setDevice(InputDevice device) {
    this.device = device;
  }
  public void getPosition(Vector3f pos) {
    calculateMotion();
    pos.set(position);
  }
  public void setPosition(Vector3f pos) {
    position.set(pos);
  }
  public void setStepRate(float stepRate) {
    step_rate = stepRate;
  }
  private void calculateMotion() {
    long current_time = System.currentTimeMillis();
    long elapsed_time = current_time - time_last_state_change;
    switch (mode) {
    case STILL:
      break;
    case MOVING_LEFT:
      position.x = orig_position.x - step_rate * elapsed_time;
      break;
    case MOVING_RIGHT:
      position.x = orig_position.x + step_rate * elapsed_time;
      break;
    case MOVING_UP:
      position.y = orig_position.y + step_rate * elapsed_time;
      break;
    case MOVING_DOWN:
      position.y = orig_position.y - step_rate * elapsed_time;
      break;
    case MOVING_FORWARD:
      position.z = orig_position.z - step_rate * elapsed_time;
      break;
    case MOVING_BACK:
      position.z = orig_position.z + step_rate * elapsed_time;
      break;
    default:
      throw (new RuntimeException("Unknown motion"));
    }
  }
  public void mouseClicked(MouseEvent e) {
  }
  public void mouseEntered(MouseEvent e) {
  }
  public void mouseExited(MouseEvent e) {
  }
  public void mousePressed(MouseEvent e) {
    if (e.getSource() == leftB && mode != MOVING_LEFT) {
      time_last_state_change = System.currentTimeMillis();
      mode = MOVING_LEFT;
      orig_position.set(position);
    } else if (e.getSource() == rightB && mode != MOVING_RIGHT) {
      time_last_state_change = System.currentTimeMillis();
      mode = MOVING_RIGHT;
      orig_position.set(position);
    } else if (e.getSource() == upB && mode != MOVING_UP) {
      time_last_state_change = System.currentTimeMillis();
      mode = MOVING_UP;
      orig_position.set(position);
    } else if (e.getSource() == downB && mode != MOVING_DOWN) {
      time_last_state_change = System.currentTimeMillis();
      mode = MOVING_DOWN;
      orig_position.set(position);
    } else if (e.getSource() == forwardB && mode != MOVING_FORWARD) {
      time_last_state_change = System.currentTimeMillis();
      mode = MOVING_FORWARD;
      orig_position.set(position);
    } else if (e.getSource() == backwardB && mode != MOVING_BACK) {
      time_last_state_change = System.currentTimeMillis();
      mode = MOVING_BACK;
      orig_position.set(position);
    } else if (e.getSource() == reset) {
      device.setNominalPositionAndOrientation();
    }
  }
  public void mouseReleased(MouseEvent e) {
    mode = STILL;
  }
}