From 2179980ed660ef66a9c064d153c5de7994d431ab Mon Sep 17 00:00:00 2001
From: Kenneth Russel <kbrussel@alum.mit.edu>
Date: Wed, 25 Jun 2003 09:05:29 +0000
Subject: Split up net.java.games.util; moved GLUT and BufferUtils into
 net.java.games.jogl.util, and moved demo-specific utility classes into
 jogl-demos project under demos.util. Added nearly all JavaOne demos into
 jogl-demos project; rewrote where necessary to use
 ClassLoader.getResourceAsStream() instead of flat files, put into packages
 and added to Makefile. Added gleem to jogl-demos project. Added jogl.jar
 build rule.

git-svn-id: file:///usr/local/projects/SUN/JOGL/git-svn/../svn-server-sync/jogl-demos/trunk@3 3298f667-5e0e-4b4a-8ed4-a3559d26a5f4
---
 src/gleem/ExaminerViewer.java | 605 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 605 insertions(+)
 create mode 100644 src/gleem/ExaminerViewer.java

(limited to 'src/gleem/ExaminerViewer.java')

diff --git a/src/gleem/ExaminerViewer.java b/src/gleem/ExaminerViewer.java
new file mode 100644
index 0000000..2cb2f05
--- /dev/null
+++ b/src/gleem/ExaminerViewer.java
@@ -0,0 +1,605 @@
+/*
+ * gleem -- OpenGL Extremely Easy-To-Use Manipulators.
+ * Copyright (C) 1998-2003 Kenneth B. Russell (kbrussel@alum.mit.edu)
+ *
+ * Copying, distribution and use of this software in source and binary
+ * forms, with or without modification, is permitted provided that the
+ * following conditions are met:
+ *
+ * Distributions of source code must reproduce the copyright notice,
+ * this list of conditions and the following disclaimer in the source
+ * code header files; and Distributions of binary code must reproduce
+ * the copyright notice, this list of conditions and the following
+ * disclaimer in the documentation, Read me file, license file and/or
+ * other materials provided with the software distribution.
+ *
+ * The names of Sun Microsystems, Inc. ("Sun") and/or the copyright
+ * holder may not 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, NON-INTERFERENCE, ACCURACY OF
+ * INFORMATIONAL CONTENT OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. THE
+ * COPYRIGHT HOLDER, SUN AND SUN'S LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL THE
+ * COPYRIGHT HOLDER, SUN OR SUN'S 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 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGES. YOU ACKNOWLEDGE THAT THIS SOFTWARE IS NOT
+ * DESIGNED, LICENSED OR INTENDED FOR USE IN THE DESIGN, CONSTRUCTION,
+ * OPERATION OR MAINTENANCE OF ANY NUCLEAR FACILITY. THE COPYRIGHT
+ * HOLDER, SUN AND SUN'S LICENSORS DISCLAIM ANY EXPRESS OR IMPLIED
+ * WARRANTY OF FITNESS FOR SUCH USES.
+ */
+
+package gleem;
+
+import java.awt.Dimension;
+import java.awt.event.*;
+import java.util.*;
+
+import gleem.linalg.*;
+import net.java.games.jogl.*;
+
+/** <P> This is an application-level class, not part of the
+    manipulator hierarchy. It is an example of how you might integrate
+    gleem with another application which uses the mouse. </P>
+
+    <P> For the given GLDrawable, the ExaminerViewer takes over the
+    setting of the view position. It passes along mouse events it is
+    not interested in to the ManipManager's mouse routines. </P>
+
+    <P> The ExaminerViewer's controls are similar to those of Open
+    Inventor's Examiner Viewer. Alt + Left mouse button causes
+    rotation about the focal point. Alt + Right mouse button causes
+    translation parallel to the image plane. Alt + both mouse buttons,
+    combined with up/down mouse motion, causes zooming out and in
+    along the view vector. (On platforms with a "Meta" key, that key
+    can be substituted in place of the Alt key.) The method
+    <code>setNoAltKeyMode</code> can be used to cause the
+    ExaminerViewer to take control of all mouse interactions in the
+    window, avoiding the need to hold down the Alt key. </P>
+
+    <P>NOTE: the current ExaminerViewer implementation assumes a
+    minimum of two mouse buttons. For the Mac OS, the code needs to be
+    adjusted to use e.g., the Control key as the "right" mouse
+    button. </P> */
+
+public class ExaminerViewer {
+  private GLDrawable window;
+  /** Simple state machine for figuring out whether we are grabbing
+      events */
+  private boolean interactionUnderway;
+  private boolean iOwnInteraction;
+
+  private boolean noAltKeyMode;
+
+  /** Simple state machine for computing distance dragged */
+  private boolean button1Down;
+  private boolean button2Down;
+  private int numMouseButtons;
+  private int oldNumMouseButtons;
+  private int lastX;
+  private int lastY;
+
+  /** Camera parameters */
+  private float minFocalDist = 1.0f;
+  private Vec3f dolly        = new Vec3f(0, 0, 10); // Amount we have "backed up" from focal point
+  private Vec3f center       = new Vec3f(0, 0,  0); // Position of focal point in world coordinates
+  private Rotf  orientation  = new Rotf();
+  private float rotateSpeed       = 1.0f;
+  private float minRotateSpeed    = 0.0001f;
+  private float dollySpeed        = 2.0f;
+  private float minDollySpeed     = 0.0001f;
+  private float zNear             = 1.0f;
+  private float zFar              = 100.0f;
+  private float vertFOVScale      = 1.0f;
+  private CameraParameters params = new CameraParameters();
+
+  /** Our bounding sphere provider (for viewAll()) */
+  private BSphereProvider provider;
+
+  private MouseMotionAdapter mouseMotionListener = new MouseMotionAdapter() {
+      public void mouseDragged(MouseEvent e) {
+        motionMethod(e, e.getX(), e.getY());
+      }
+
+      public void mouseMoved(MouseEvent e) {
+        passiveMotionMethod(e);
+      }
+    };
+
+  private MouseAdapter mouseListener = new MouseAdapter() {
+      public void mousePressed(MouseEvent e) {
+        mouseMethod(e, e.getModifiers(), true, e.getX(), e.getY());
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        mouseMethod(e, e.getModifiers(), false, e.getX(), e.getY());
+      }
+    };
+
+  private GLEventListener glListener = new GLEventListener() {
+      public void init(GLDrawable drawable) {}
+      public void display(GLDrawable drawable) {}
+      public void reshape(GLDrawable drawable, int x, int y, int width, int height) {
+        reshapeMethod(width, height);
+      }
+      public void displayChanged(GLDrawable drawable, boolean modeChanged, boolean deviceChanged) {}
+    };
+
+  /** The constructor takes the number of mouse buttons on this system
+      (couldn't figure out how to determine this internally) */
+  public ExaminerViewer(int numMouseButtons) {
+    this.numMouseButtons = numMouseButtons;
+    oldNumMouseButtons = numMouseButtons;
+  }
+
+  /** <P> Attaches this ExaminerViewer to the given GLDrawable. This
+      causes the ManipManager's mouse routines to be removed from the
+      window (using ManipManager.removeMouseListeners) and the
+      ExaminerViewer's to be installed. The GLDrawable should be
+      registered with the ManipManager before the ExaminerViewer is
+      attached to it. </P>
+
+      <P> In order for the viewer to do anything useful, you need to
+      provide a BSphereProvider to it to allow "view all"
+      functionality. </P> */
+  public void attach(GLDrawable window, BSphereProvider provider) {
+    this.window = window;
+    this.provider = provider;
+    init();
+    setupListeners();
+  }
+
+  /** Detaches from the given window. This causes the ManipManager's
+      mouse listeners to be reinstalled on the GLDrawable and the
+      ExaminerViewer's to be removed. */
+  public void detach() {
+    removeListeners();
+    this.window = null;
+    this.provider = null;
+  }
+  
+  /** Call this at the end of your display() method to cause the
+      Modelview matrix to be recomputed for the next frame. */
+  public void update(GL gl) {
+    recalc(gl);
+  }
+
+  /** Call this to apply the inverse rotation matrix of the camera to
+      the current matrix. This is useful for drawing a skybox. Does
+      not update which OpenGL matrix is currently being modified or
+      the ExaminerViewer's camera parameters. */
+  public void updateInverseRotation(GL gl) {
+    recalcInverseRotation(gl);
+  }
+
+  /** Call this to force the ExaminerViewer to update its
+      CameraParameters without touching the OpenGL state. */
+  public void update() {
+    recalc();
+  }
+
+  /** Call this from within your display() method to cause the
+      ExaminerViewer to recompute its position based on the visible
+      geometry. A BSphereProvider must have already been set or this
+      method has no effect. */
+  public void viewAll(GL gl) {
+    if (provider == null) {
+      return;
+    }
+    // Figure out how far to move
+    float vertFOV, horizFOV, minFOV;
+    float adjustedVertFOV = params.getVertFOV() * vertFOVScale;
+    vertFOV = 2.0f * adjustedVertFOV;
+    horizFOV = 2.0f * (float) Math.atan(params.getImagePlaneAspectRatio() *
+                                        Math.tan(adjustedVertFOV));
+    if (vertFOV < horizFOV)
+      minFOV = vertFOV;
+    else
+      minFOV = horizFOV;
+    if (minFOV == 0.0f) {
+      throw new RuntimeException("Minimum field of view was zero");
+    }
+    BSphere bsph = provider.getBoundingSphere();
+    float dist = bsph.getRadius() / (float) Math.sin(minFOV / 2.0f);
+    dolly.setZ(dist);
+    center.set(bsph.getCenter());
+    recalc(gl);
+  }
+
+  /** Get the camera parameters out of this Examiner Viewer (for
+      example, to pass to ManipManager.updateCameraParameters()). Note
+      that mutating the returned object is not recommended but
+      regardless will have no effect on the ExaminerViewer. */
+  public CameraParameters getCameraParameters() {
+    return params;
+  }
+
+  /** These routines can be hooked into a GUI by calling them from
+      ActionEvent listeners for buttons elsewhere in the application. */
+  public void rotateFaster() {
+    rotateSpeed *= 2.0f;
+  }
+
+  public void rotateSlower() {
+    if (rotateSpeed < minRotateSpeed)
+      return;
+    else
+      rotateSpeed /= 2.0f;
+  }
+
+  public void dollyFaster() {
+    dollySpeed *= 2.0f;
+  }
+
+  public void dollySlower() {
+    if (dollySpeed < minDollySpeed)
+      return;
+    else
+      dollySpeed /= 2.0f;
+  }
+
+  public float getZNear() {
+    return zNear;
+  }
+
+  public void setZNear(float zNear) {
+    this.zNear = zNear;
+  }
+
+  public float getZFar() {
+    return zFar;
+  }
+
+  public void setZFar(float zFar) {
+    this.zFar = zFar;
+  }
+
+  /** Takes HALF of the vertical angular span of the frustum,
+      specified in radians. For example, if your <b>fovy</b> argument
+      to gluPerspective() is 90, then this would be Math.PI / 4. Note
+      that the ExaminerViewer's algorithms break down if the vertical
+      field of view approaches or exceeds 180 degrees, or Math.PI /
+      2. */
+  public void setVertFOV(float vertFOV) {
+    vertFOVScale = (float) (vertFOV / (Math.PI / 4));
+  }
+
+  /** Sets the position of this ExaminerViewer. */
+  public void setPosition(Vec3f position) {
+    Vec3f tmp = orientation.rotateVector(Vec3f.NEG_Z_AXIS);
+    tmp.scale(dolly.z());
+    center.add(position, tmp);
+  }
+
+  /** Sets the orientation of this ExaminerViewer. */
+  public void setOrientation(Rotf orientation) {
+    this.orientation.set(orientation);
+  }
+
+  public void setNoAltKeyMode(boolean noAltKeyMode) {
+    this.noAltKeyMode = noAltKeyMode;
+    if (noAltKeyMode) {
+      // FIXME: this is a hack to work around Windows' apparently
+      // conflating the alt/meta key with one of the mouse buttons
+      oldNumMouseButtons = numMouseButtons;
+      numMouseButtons = 3;
+    } else {
+      numMouseButtons = oldNumMouseButtons;
+    }
+  }
+
+  public boolean getNoAltKeyMode() {
+    return noAltKeyMode;
+  }
+
+  /** Rotates this ExaminerViewer about the focal point by the
+      specified incremental rotation; performs postmultiplication,
+      i.e. the incremental rotation is applied after the current
+      orientation. */
+  public void rotateAboutFocalPoint(Rotf rot) {
+    orientation = rot.times(orientation);
+    orientation.normalize();
+  }
+
+  //----------------------------------------------------------------------
+  // Internals only below this point
+  //
+
+  private static final float EPSILON = 0.0001f;
+
+  private void setupListeners() {
+    ManipManager.getManipManager().removeMouseListeners(window);
+    window.addMouseMotionListener(mouseMotionListener);
+    window.addMouseListener(mouseListener);
+    window.addGLEventListener(glListener);
+  }
+
+  private void removeListeners() {
+    window.removeMouseMotionListener(mouseMotionListener);
+    window.removeMouseListener(mouseListener);
+    window.removeGLEventListener(glListener);
+    ManipManager.getManipManager().setupMouseListeners(window);
+  }
+
+  private void passiveMotionMethod(MouseEvent e) {
+    ManipManager.getManipManager().mouseMoved(e);
+  }
+
+  private boolean modifiersMatch(MouseEvent e, int mods) {
+    if (noAltKeyMode) {
+      if ((mods & MouseEvent.BUTTON1_MASK) != 0 &&
+          (mods & MouseEvent.BUTTON2_MASK) == 0 &&
+          (mods & MouseEvent.BUTTON3_MASK) == 0) {
+        return (!e.isAltDown() && !e.isMetaDown() && !e.isControlDown() && !e.isShiftDown());
+      } else {
+        // At least on Windows, meta seems to be declared to be down on right button presses
+        return !e.isControlDown() && !e.isShiftDown();
+      }
+    } else {
+      return ((e.isAltDown() || e.isMetaDown()) &&
+              (!e.isControlDown() && !e.isShiftDown()));
+    }
+  }
+
+  private void init() {
+    interactionUnderway = false;
+    iOwnInteraction = false;
+    button1Down = false;
+    button2Down = false;
+
+    Dimension size = window.getSize();
+    int xSize = size.width;
+    int ySize = size.height;
+    params.setOrientation(orientation);
+    params.setPosition(computePosition(new Vec3f()));
+    params.setForwardDirection(Vec3f.NEG_Z_AXIS);
+    params.setUpDirection(Vec3f.Y_AXIS);
+    params.setVertFOV((float) Math.PI / 8.0f);
+    params.setImagePlaneAspectRatio((float) xSize / (float) ySize);
+    params.setXSize(xSize);
+    params.setYSize(ySize);
+  }
+
+  private void motionMethod(MouseEvent e, int x, int y) {
+    if (interactionUnderway && !iOwnInteraction) {
+      ManipManager.getManipManager().mouseDragged(e);
+    } else {
+      int dx = x - lastX;
+      int dy = y - lastY;
+  
+      lastX = x;
+      lastY = y;
+
+      if ((button1Down && (!button2Down))) {
+
+        // Rotation functionality
+        float xRads = (float) Math.PI * -1.0f * dy * rotateSpeed / 1000.0f;
+        float yRads = (float) Math.PI * -1.0f * dx * rotateSpeed / 1000.0f;
+        Rotf xRot = new Rotf(Vec3f.X_AXIS, xRads);
+        Rotf yRot = new Rotf(Vec3f.Y_AXIS, yRads);
+        Rotf newRot = yRot.times(xRot);
+        orientation = orientation.times(newRot);
+
+      } else if (button2Down && (!button1Down)) {
+
+        // Translate functionality
+        // Compute the local coordinate system's difference vector
+        Vec3f localDiff = new Vec3f(dollySpeed * -1.0f * dx / 100.0f,
+                                    dollySpeed * dy / 100.0f,
+                                    0.0f);
+        // Rotate this by camera's orientation
+        Vec3f worldDiff = orientation.rotateVector(localDiff);
+        // Add on to center
+        center.add(worldDiff);
+
+      } else if (button1Down && button2Down) {
+
+        float diff = dollySpeed * -1.0f * dy / 100.0f;
+        float newDolly = dolly.z() + diff;
+        if (newDolly < minFocalDist) {
+          newDolly = minFocalDist;
+        }
+        dolly.setZ(newDolly);
+
+      }
+
+      // Force redraw if window will not do it automatically
+      if (!window.getNoAutoRedrawMode()) {
+        window.display();
+      }
+    }
+  }
+
+  private void mouseMethod(MouseEvent e, int mods, boolean press,
+                           int x, int y) {
+    if ((interactionUnderway && !iOwnInteraction) ||
+        (!modifiersMatch(e, mods))) {
+      // Update state and pass this event along to the ManipManager
+      if (press) {
+        interactionUnderway = true;
+        iOwnInteraction = false;
+        ManipManager.getManipManager().mousePressed(e);
+      } else {
+        interactionUnderway = false;
+        iOwnInteraction = false;
+        ManipManager.getManipManager().mouseReleased(e);
+      }
+    } else {
+      if ((mods & MouseEvent.BUTTON1_MASK) != 0) {
+        if (press) {
+          button1Down = true;
+        } else {
+          button1Down = false;
+        }
+      } else {
+        if (numMouseButtons != 3) {
+          if ((mods & MouseEvent.BUTTON2_MASK) != 0) {
+            if (press) {
+              button2Down = true;
+            } else {
+              button2Down = false;
+            }
+          }
+        } else {
+          // FIXME: must test this on 3-button system
+          if ((mods & MouseEvent.BUTTON3_MASK) != 0) {
+            if (press) {
+              button2Down = true;
+            } else {
+              button2Down = false;
+            }
+          }
+        }
+      }
+
+      lastX = x;
+      lastY = y;
+
+      if (button1Down || button2Down) {
+        interactionUnderway = true;
+        iOwnInteraction = true;
+      } else {
+        interactionUnderway = false;
+        iOwnInteraction = false;
+      }
+
+      // Force redraw if window will not do it automatically
+      if (!window.getNoAutoRedrawMode()) {
+        window.display();
+      }
+    }
+  }
+
+  private void reshapeMethod(int w, int h) {
+    float aspect, theta;
+    aspect = (float) w / (float) h;
+    if (w >= h)
+      theta = 45;
+    else
+      theta = (float) Math.toDegrees(Math.atan(1 / aspect));
+    theta *= vertFOVScale;
+    params.setVertFOV((float) (Math.toRadians(theta) / 2.0));
+    params.setImagePlaneAspectRatio(aspect);
+    params.setXSize(w);
+    params.setYSize(h);
+  }
+
+  private void recalc() {
+    // Recompute position, forward and up vectors
+    Vec3f tmp = new Vec3f();
+    params.setPosition(computePosition(tmp));
+    orientation.rotateVector(Vec3f.NEG_Z_AXIS, tmp);
+    params.setForwardDirection(tmp);
+    orientation.rotateVector(Vec3f.Y_AXIS, tmp);
+    params.setUpDirection(tmp);
+    params.setOrientation(orientation);
+
+    // Compute modelview matrix based on camera parameters, position and
+    // orientation
+    Mat4f tmpMat = new Mat4f();
+    tmpMat.makeIdent();
+    tmpMat.setRotation(orientation);
+    tmpMat.setTranslation(params.getPosition());
+    tmpMat.invertRigid();
+    params.setModelviewMatrix(tmpMat);
+
+    // Compute perspective matrix given camera parameters
+    float deltaZ = zFar - zNear;
+    float aspect = params.getImagePlaneAspectRatio();
+    float radians = params.getVertFOV();
+    float sine = (float) Math.sin(radians);
+    if ((deltaZ == 0) || (sine == 0) || (aspect == 0)) {
+      tmpMat.makeIdent();
+      params.setProjectionMatrix(tmpMat);
+      return;
+    }
+    float cotangent = (float) Math.cos(radians) / sine;
+    tmpMat.makeIdent();
+    tmpMat.set(0, 0, cotangent / aspect);
+    tmpMat.set(1, 1, cotangent);
+    tmpMat.set(2, 2, -(zFar + zNear) / deltaZ);
+    tmpMat.set(3, 2, -1);
+    tmpMat.set(2, 3, -2 * zNear * zFar / deltaZ);
+    tmpMat.set(3, 3, 0);
+    params.setProjectionMatrix(tmpMat);
+
+
+    /********************
+
+    // Recompute position, forward and up vectors
+    params.setPosition(position);
+    Vec3f tmp = new Vec3f();
+    orientation.rotateVector(Vec3f.NEG_Z_AXIS, tmp);
+    params.setForwardDirection(tmp);
+    orientation.rotateVector(Vec3f.Y_AXIS, tmp);
+    params.setUpDirection(tmp);
+    params.setOrientation(orientation);
+
+    // Compute modelview matrix based on camera parameters, position and
+    // orientation
+    Mat4f tmpMat = new Mat4f();
+    tmpMat.makeIdent();
+    tmpMat.setRotation(orientation);
+    tmpMat.setTranslation(position);
+    tmpMat.invertRigid();
+    params.setModelviewMatrix(tmpMat);
+
+    // Compute perspective matrix given camera parameters
+    float deltaZ = zFar - zNear;
+    float aspect = params.getImagePlaneAspectRatio();
+    float radians = params.getVertFOV();
+    float sine = (float) Math.sin(radians);
+    if ((deltaZ == 0) || (sine == 0) || (aspect == 0)) {
+      tmpMat.makeIdent();
+      params.setProjectionMatrix(tmpMat);
+      return;
+    }
+    float cotangent = (float) Math.cos(radians) / sine;
+    tmpMat.makeIdent();
+    tmpMat.set(0, 0, cotangent / aspect);
+    tmpMat.set(1, 1, cotangent);
+    tmpMat.set(2, 2, -(zFar + zNear) / deltaZ);
+    tmpMat.set(3, 2, -1);
+    tmpMat.set(2, 3, -2 * zNear * zFar / deltaZ);
+    tmpMat.set(3, 3, 0);
+    params.setProjectionMatrix(tmpMat);
+
+    **********************/
+  }
+
+  private void recalc(GL gl) {
+    recalc();
+
+    gl.glMatrixMode(GL.GL_MODELVIEW);
+    float[] data = new float[16];
+    params.getModelviewMatrix().getColumnMajorData(data);
+    gl.glLoadMatrixf(data);
+
+    gl.glMatrixMode(GL.GL_PROJECTION);
+    params.getProjectionMatrix().getColumnMajorData(data);
+    gl.glLoadMatrixf(data);
+  }
+
+  private void recalcInverseRotation(GL gl) {
+    Rotf oriInv = orientation.inverse();
+    Vec3f tmp = new Vec3f();
+    float ang = orientation.get(tmp);
+    gl.glLoadIdentity();
+    if (tmp.lengthSquared() > EPSILON)
+      gl.glRotatef((float) Math.toDegrees(ang), tmp.x(), tmp.y(), tmp.z());
+  }
+
+  private Vec3f computePosition(Vec3f tmp) {
+    orientation.rotateVector(dolly, tmp);
+    tmp.add(center);
+    return tmp;
+  }
+}
-- 
cgit v1.2.3