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
---
 .../InfiniteShadowVolumes.java                     | 1302 ++++++++++++++++++++
 1 file changed, 1302 insertions(+)
 create mode 100644 src/demos/infiniteShadowVolumes/InfiniteShadowVolumes.java

(limited to 'src/demos/infiniteShadowVolumes/InfiniteShadowVolumes.java')

diff --git a/src/demos/infiniteShadowVolumes/InfiniteShadowVolumes.java b/src/demos/infiniteShadowVolumes/InfiniteShadowVolumes.java
new file mode 100644
index 0000000..4752dbb
--- /dev/null
+++ b/src/demos/infiniteShadowVolumes/InfiniteShadowVolumes.java
@@ -0,0 +1,1302 @@
+/*
+ * Portions Copyright (C) 2003 Sun Microsystems, Inc.
+ * All rights reserved.
+ */
+
+/*
+ *
+ * COPYRIGHT NVIDIA CORPORATION 2003. ALL RIGHTS RESERVED.
+ * BY ACCESSING OR USING THIS SOFTWARE, YOU AGREE TO:
+ *
+ *  1) ACKNOWLEDGE NVIDIA'S EXCLUSIVE OWNERSHIP OF ALL RIGHTS
+ *     IN AND TO THE SOFTWARE;
+ *
+ *  2) NOT MAKE OR DISTRIBUTE COPIES OF THE SOFTWARE WITHOUT
+ *     INCLUDING THIS NOTICE AND AGREEMENT;
+ *
+ *  3) ACKNOWLEDGE THAT TO THE MAXIMUM EXTENT PERMITTED BY
+ *     APPLICABLE LAW, THIS SOFTWARE IS PROVIDED *AS IS* AND
+ *     THAT NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES,
+ *     EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED
+ *     TO, IMPLIED WARRANTIES OF MERCHANTABILITY  AND FITNESS
+ *     FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS BE LIABLE FOR ANY
+ * SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES
+ * WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS
+ * OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS
+ * INFORMATION, OR ANY OTHER PECUNIARY LOSS), INCLUDING ATTORNEYS'
+ * FEES, RELATING TO THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ */
+
+package demos.infiniteShadowVolumes;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.io.*;
+import java.nio.*;
+import java.util.*;
+import javax.imageio.*;
+import javax.imageio.stream.*;
+
+import net.java.games.jogl.*;
+import net.java.games.jogl.util.*;
+import demos.util.*;
+import gleem.*;
+import gleem.linalg.*;
+
+/**
+  Infinite shadow volumes are described in the paper 
+  "Practical and Robust Stenciled Shadow Volumes for
+   Hardware-Accelerated Rendering" which can be found
+   online at: <P>
+   
+ <a href = "http://developer.nvidia.com/view.asp?IO=robust_shadow_volumes">http://developer.nvidia.com/view.asp?IO=robust_shadow_volumes</a><P>
+
+  This code is intended to illustrate the technique.  It
+  is not optimized for performance. <P>
+
+  Cass Everitt <BR>
+  04-04-2002 <P>
+
+  Ported to Java by Kenneth Russell
+*/
+
+public class InfiniteShadowVolumes {
+  private GLCanvas canvas;
+  private Animator animator;
+  private volatile boolean quit;
+
+  public static void main(String[] args) {
+    new InfiniteShadowVolumes().run(args);
+  }
+
+  public void run(String[] args) {
+    GLCapabilities caps = new GLCapabilities();
+    caps.setStencilBits(16);
+    canvas = GLDrawableFactory.getFactory().createGLCanvas(caps);
+    canvas.addGLEventListener(new Listener());
+    
+    animator = new Animator(canvas);
+
+    Frame frame = new Frame("Infinite Stenciled Shadow Volumes");
+    frame.setLayout(new BorderLayout());
+    canvas.setSize(512, 512);
+    frame.add(canvas, BorderLayout.CENTER);
+    frame.pack();
+    frame.show();
+    canvas.requestFocus();
+
+    frame.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {
+          runExit();
+        }
+      });
+
+    animator.start();
+  }
+
+  static class Model {
+    Model() {
+      frame_num = 0;
+      frame_incr = 0.25f;
+      draw = true;
+      ambient =  new Vec4f(0.1f, 0.1f, 0.1f, 1);
+      diffuse =  new Vec4f(0.8f, 0,    0,    1);
+      specular = new Vec4f(0.6f, 0.6f, 0.6f, 1);
+      shininess = 64;
+    }
+
+    MD2.Model mod;
+    MD2.Frame interp_frame;
+    float frame_num;
+    float frame_incr;
+
+    Vec4f ambient;
+    Vec4f diffuse;
+    Vec4f specular;
+    float shininess;
+    boolean draw;
+  };
+
+  // You can load multiple models and 
+  // position them independently.  If they're
+  // quake2 models you can animate them as well.  
+
+  private static final int MAX_MODELS = 4;
+  private Model[] m = new Model[MAX_MODELS];
+  private int curr_model = 0;
+  private int num_models = 0;
+
+  // selector for the current view mode
+  private static final int CAMERA_VIEW = 0;
+  private static final int SCENE_VIEW  = 1;
+  private static final int CLIP_VIEW   = 2;
+  private int curr_view  = CAMERA_VIEW;
+
+  private GLUT glut = new GLUT();
+
+  private ExaminerViewer viewer;
+  private HandleBoxManip objectManip;
+  private HandleBoxManip lightManip;
+  private Mat4f objectManipXform;
+  private Mat4f lightManipXform;
+  int faceDisplayList;
+  int wallTexObject;
+
+  private boolean[] b = new boolean[256];
+
+  Vec4f light_position = new Vec4f(0,0,0,1);
+  float light_object_scale = 1;
+  float volume_alpha = .1f;
+  float room_ambient = .3f;
+
+  boolean doViewAll = true;
+
+  private boolean enableDepthClampNV;
+  private boolean toggleDepthClampNV;
+  private boolean animateForward;
+  private boolean animateBackward;
+  private boolean hideCurrentModel;
+  private boolean toggleWireframe;
+
+  class Listener implements GLEventListener {
+    public void init(GLDrawable drawable) {
+      GL gl = drawable.getGL();
+      GLU glu = drawable.getGLU();
+
+      gl.glClearStencil(128);
+      //glEnable(GL.GL_DEPTH_CLAMP_NV);
+      gl.glEnable(GL.GL_DEPTH_TEST);
+      gl.glDepthFunc(GL.GL_LESS);
+      gl.glEnable(GL.GL_NORMALIZE);
+      gl.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE, GL.GL_FALSE);
+      float[] ambient = new float[] {0.3f, 0.3f, 0.3f, 1};
+      gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, ambient);
+      faceDisplayList = gl.glGenLists(1);
+      gl.glNewList(faceDisplayList, GL.GL_COMPILE);
+      drawMesh(gl, 20, 40);
+      gl.glEndList();
+
+      int[] tmp = new int[1];
+      gl.glGenTextures(1, tmp);
+      wallTexObject = tmp[0];
+      gl.glBindTexture(GL.GL_TEXTURE_2D, wallTexObject);
+      gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_GENERATE_MIPMAP_SGIS, GL.GL_TRUE);
+      gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_LINEAR);
+      gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
+      float[] tex = new float[32*32];
+      for(int i=0; i < 32; i++) {
+        for(int j=0; j < 32; j++) {
+          if ((i>>4 ^ j>>4) != 0)
+            tex[i+j*32] = 1;
+          else
+            tex[i+j*32] = .9f;
+        }
+      }
+      gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, 32, 32, 0, GL.GL_LUMINANCE, GL.GL_FLOAT, tex);
+      
+      initModel();
+
+      b['S'] = true; // no silhouette outlines
+      b['v'] = true; // no volume drawing
+      b['I'] = true; // use infinite far plane
+      b['L'] = true; // use local light for shadowing
+
+      drawable.addKeyListener(new KeyAdapter() {
+          public void keyTyped(KeyEvent e) {
+            dispatchKey(e.getKeyChar());
+          }
+        });
+
+      // Register the window with the ManipManager
+      ManipManager manager = ManipManager.getManipManager();
+      manager.registerWindow(drawable);
+
+      objectManip = new HandleBoxManip();
+      manager.showManipInWindow(objectManip, drawable);
+      objectManip.setTranslation(new Vec3f(0, 0, -2));
+      objectManip.setRotation(new Rotf(new Vec3f(1, 0, 0), (float) Math.toRadians(-90)));
+
+      lightManip = new HandleBoxManip();
+      manager.showManipInWindow(lightManip, drawable);
+      lightManip.setTranslation(new Vec3f(0.5f, 0.5f, -1));
+      lightManip.setGeometryScale(new Vec3f(0.1f, 0.1f, 0.1f));
+
+      viewer = new ExaminerViewer(MouseButtonHelper.numMouseButtons());
+      viewer.attach(drawable, new BSphereProvider() {
+          public BSphere getBoundingSphere() {
+            return new BSphere(objectManip.getTranslation(), 1.0f);
+          }
+        });
+      viewer.setZNear(1.0f);
+      viewer.setZFar(100.0f);
+      viewer.setOrientation(new Rotf(new Vec3f(0, 1, 0), (float) Math.toRadians(15)));
+
+      // FIXME
+      //      glutAddMenuEntry("mouse controls view [1]", '1');
+      //      glutAddMenuEntry("mouse controls model  [2]", '2');
+      //      glutAddMenuEntry("mouse controls light  [3]", '3');
+      //      glutAddMenuEntry("mouse controls room   [4]", '4');
+      //      glutAddMenuEntry("enable depth clamp [!]", '!');
+      //      glutAddMenuEntry("disable depth clamp [~]", '~');
+      //      glutAddMenuEntry("start animation [ ]", ' ');
+      //      glutAddMenuEntry("step animation forward [a]", 'a');
+      //      glutAddMenuEntry("step animation backward [b]", 'b');
+      //      glutAddMenuEntry("toggle drawing silhouette [S]", 'S');
+      //      glutAddMenuEntry("toggle drawing shadow  [s]", 's');
+      //      glutAddMenuEntry("toggle drawing visible shadow volume [v]", 'v');
+      //      glutAddMenuEntry("toggle drawing model geometry[m]", 'm');
+
+      //      glutAddMenuEntry("increase shadow volume alpha [;]", ';');
+      //      glutAddMenuEntry("decrease shadow volume alpha [:]", ':');
+
+      //      glutAddMenuEntry("next model [,]", ',');
+      //      glutAddMenuEntry("hide current model [.]", '.');
+
+      //      glutAddMenuEntry("toggle view frustum clip planes [X]", 'X');
+
+      //      glutAddMenuEntry("camera view [5]", '5');
+      //      glutAddMenuEntry("scene view [6]", '6');
+      //      glutAddMenuEntry("clipspace view [7]", '7');
+
+      //      glutAddMenuEntry("enable depth clamp [!]", '!');
+      //      glutAddMenuEntry("disable depth clamp [~]", '~');
+
+      //      glutAddMenuEntry("increase light size [n]", 'n');
+      //      glutAddMenuEntry("decrease light size [N]", 'N');
+
+      //      glutAddMenuEntry("move near plane in [[]", '[');
+      //      glutAddMenuEntry("move near plane out []]", ']');
+      //      glutAddMenuEntry("move far plane in [{]", '[');
+      //      glutAddMenuEntry("move far plane out [}]", ']');
+
+      //      glutAddMenuEntry("toggle local/infinite light [L]", 'L');
+
+      //      glutAddMenuEntry("hide room [R]", 'R');
+
+      //      glutAddMenuEntry("view all with camera [c]", 'c');
+
+      //      glutAddMenuEntry("quit [<esc>]", 27);
+    }
+
+    public void display(GLDrawable drawable) {
+      if (quit) {
+        return;
+      }
+
+      GL gl = drawable.getGL();
+      GLU glu = drawable.getGLU();
+
+      gl.glMatrixMode(GL.GL_PROJECTION);
+      gl.glLoadIdentity();
+
+      if (doViewAll) {
+        viewer.viewAll(gl);
+        doViewAll = false;
+      }
+
+      objectManipXform = objectManip.getTransform();
+      lightManipXform  = lightManip.getTransform();
+
+      if (toggleDepthClampNV) {
+        if (enableDepthClampNV) {
+          gl.glEnable(GL.GL_DEPTH_CLAMP_NV);
+        } else {
+          gl.glDisable(GL.GL_DEPTH_CLAMP_NV);
+        }
+        toggleDepthClampNV = false;
+      }
+
+      if (b[' ']) {
+        animateForward = true;
+      }
+
+      if (animateForward) {
+        Model mm = m[curr_model];
+        mm.frame_num += mm.frame_incr;
+        if (mm.frame_num >= mm.mod.f.length)
+          mm.frame_num = 0;
+        interpolate_frame();
+        animateForward = false;
+      }
+
+      if (animateBackward) {
+        Model mm = m[curr_model];
+        mm.frame_num -= mm.frame_incr;
+        if (mm.frame_num < 0)
+          mm.frame_num += mm.mod.f.length;
+        interpolate_frame();
+        animateBackward = false;
+      }
+
+      if (hideCurrentModel) {
+        gl.glNewList(faceDisplayList, GL.GL_COMPILE);
+        drawMesh(gl, 20, 40);
+        gl.glEndList();
+        hideCurrentModel = false;
+      }
+
+      if (toggleWireframe) {
+        if(b['w'])
+          gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
+        else
+          gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
+      }
+
+      if(b['I']) {
+        // push far plane to infinity
+        switch (curr_view) {
+          case CAMERA_VIEW:
+            viewer.update(gl);
+            // Undo perspective effects of ExaminerViewer
+            gl.glMatrixMode(GL.GL_PROJECTION);
+            gl.glLoadIdentity();
+            applyInfinitePerspective(gl, viewer);
+            break;
+
+          case SCENE_VIEW:
+            applyInfinitePerspective(gl, viewer);
+            // FIXME: do we need more primitives in the ExaminerViewer class?
+            //            scenecam.apply_inverse_transform();
+            break;
+
+          case CLIP_VIEW:
+            applyInfinitePerspective(gl, viewer);
+            // FIXME
+            //            clipcam.apply_inverse_transform();
+            gl.glScalef(10,10,-10);
+            applyInfinitePerspective(gl, viewer);
+            break;
+
+          default:
+            break;
+        }
+      } else {
+        switch (curr_view) {
+          case CAMERA_VIEW:
+            viewer.update(gl);
+            break;
+
+          case SCENE_VIEW:
+            applyInfinitePerspective(gl, viewer);
+            // FIXME
+            //            scenecam.apply_inverse_transform();
+            break;
+
+          case CLIP_VIEW:
+            applyInfinitePerspective(gl, viewer);
+            // FIXME
+            //            clipcam.apply_inverse_transform();
+            gl.glScalef(10,10,-10);
+            // FIXME
+            //            reshaper.apply_projection();
+            break;
+
+          default:
+            break;
+        }
+      }
+
+      gl.glMatrixMode(GL.GL_MODELVIEW);
+
+      // FIXME
+      if (b['X']) {
+        gl.glLoadIdentity();
+        if(b['I']) {
+          // FIXME
+          applyInfinitePerspectiveInverse(gl, viewer);
+        } else {
+          // FIXME
+          //          reshaper.apply_projection_inverse();
+        }
+        double[] pos_x = new double[] {-1, 0, 0, 1};
+        double[] neg_x = new double[] { 1, 0, 0, 1};
+        double[] pos_y = new double[] { 0,-1, 0, 1};
+        double[] neg_y = new double[] { 0, 1, 0, 1};
+        double[] pos_z = new double[] { 0, 0,-1, 1};
+        double[] neg_z = new double[] { 0, 0, 1, 1};
+        gl.glClipPlane(GL.GL_CLIP_PLANE0, pos_x);
+        gl.glClipPlane(GL.GL_CLIP_PLANE1, neg_x);
+        gl.glClipPlane(GL.GL_CLIP_PLANE2, pos_y);
+        gl.glClipPlane(GL.GL_CLIP_PLANE3, neg_y);
+        gl.glClipPlane(GL.GL_CLIP_PLANE4, pos_z);
+        gl.glClipPlane(GL.GL_CLIP_PLANE5, neg_z);
+        gl.glEnable(GL.GL_CLIP_PLANE0);
+        gl.glEnable(GL.GL_CLIP_PLANE1);
+        gl.glEnable(GL.GL_CLIP_PLANE2);
+        gl.glEnable(GL.GL_CLIP_PLANE3);
+        gl.glEnable(GL.GL_CLIP_PLANE4);
+        gl.glEnable(GL.GL_CLIP_PLANE5);
+        gl.glLoadIdentity();
+      }
+
+      gl.glPushMatrix();
+      // FIXME
+      //      camera.apply_inverse_transform();
+      //      light.apply_transform();
+      gl.glMultMatrixf(getData(lightManipXform));
+      gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, getData(light_position));
+      gl.glPopMatrix();
+      gl.glEnable(GL.GL_LIGHT0);
+
+      // FIXME
+      gl.glPushMatrix();
+      //      gl.glLoadIdentity();
+      //      camera.apply_inverse_transform();
+
+      gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT | GL.GL_STENCIL_BUFFER_BIT);
+
+      ManipManager.getManipManager().updateCameraParameters(drawable, viewer.getCameraParameters());
+      ManipManager.getManipManager().render(drawable, gl);
+
+      if (!b['R']) {
+        drawRoom(gl, false);
+      }
+
+      if (!b['m']) {
+        for (int i = 0; i < num_models; i++)
+          if (m[i].draw)
+            drawModel(gl, i, false);
+      }
+
+      if (b['X']) {
+        gl.glDisable(GL.GL_CLIP_PLANE0);
+        gl.glDisable(GL.GL_CLIP_PLANE1);
+        gl.glDisable(GL.GL_CLIP_PLANE2);
+        gl.glDisable(GL.GL_CLIP_PLANE3);
+        gl.glDisable(GL.GL_CLIP_PLANE4);
+        gl.glDisable(GL.GL_CLIP_PLANE5);
+      }
+
+      if (!b['s']) {
+        for (int i = 0; i < num_models; i++)
+          if (m[i].draw)
+            drawShadowVolumeToStencil(gl, i);
+      }
+
+      // Be aware that this can cause some multipass artifacts
+      // due to invariance issues.
+      if (b['X']) {
+        gl.glEnable(GL.GL_CLIP_PLANE0);
+        gl.glEnable(GL.GL_CLIP_PLANE1);
+        gl.glEnable(GL.GL_CLIP_PLANE2);
+        gl.glEnable(GL.GL_CLIP_PLANE3);
+        gl.glEnable(GL.GL_CLIP_PLANE4);
+        gl.glEnable(GL.GL_CLIP_PLANE5);
+      }
+      if (!b['d']) {
+        if (!b['R'])
+          drawRoom(gl, true);
+        if (!b['m'])
+          for (int i = 0; i < num_models; i++)
+            if (m[i].draw)
+              drawModel(gl, i, true);
+      }
+
+      if(!b['S']) {
+        for (int i = 0; i < num_models; i++)
+          if (m[i].draw)
+            drawPossibleSilhouette(gl, i);
+      }
+
+      if (!b['v']) {
+        for (int i = 0; i < num_models; i++)
+          if (m[i].draw)
+            drawShadowVolumeToColor(gl, i);
+      }
+
+      // Be aware that this can cause some multipass artifacts
+      // due to invariance issues.
+      if (b['X']) {
+        gl.glDisable(GL.GL_CLIP_PLANE0);
+        gl.glDisable(GL.GL_CLIP_PLANE1);
+        gl.glDisable(GL.GL_CLIP_PLANE2);
+        gl.glDisable(GL.GL_CLIP_PLANE3);
+        gl.glDisable(GL.GL_CLIP_PLANE4);
+        gl.glDisable(GL.GL_CLIP_PLANE5);
+      }
+
+      drawLight(gl, glu);
+
+      gl.glPopMatrix();
+
+      // In an "external" viewing mode, show the camera's view volume
+      // as a yellow wireframe cube or frustum.
+      if (curr_view != CAMERA_VIEW) {
+        gl.glPushMatrix();
+        if (b['I']) {
+          // FIXME
+          applyInfinitePerspectiveInverse(gl, viewer);
+        } else {
+          // FIXME
+          //          reshaper.apply_projection_inverse();
+        }
+        gl.glColor3f(.75f,.75f,0);
+        gl.glLineWidth(3);
+        glut.glutWireCube(gl, 2);
+        gl.glLineWidth(1);
+        gl.glPopMatrix();
+      }
+    }
+
+    // Unused routines
+    public void reshape(GLDrawable drawable, int x, int y, int width, int height) {}
+    public void displayChanged(GLDrawable drawable, boolean modeChanged, boolean deviceChanged) {}
+
+    //----------------------------------------------------------------------
+    // Internals only below this point
+    //
+
+    private void dispatchKey(char k) {
+      b[k] = ! b[k];
+      if (k==27 || k=='q') {
+        runExit();
+        return;
+      }
+
+      if(';' == k) {
+        volume_alpha *= 1.1f;
+      }
+      if(':' == k) {
+        volume_alpha /= 1.1f;
+      }
+
+      if('\'' == k) {
+        room_ambient += .025f;
+      }
+      if('"' == k) {
+        room_ambient -= .025f;
+      }
+
+      if(',' == k) {
+        curr_model++;
+        curr_model %= num_models;
+        // FIXME
+        //        key('2',0,0);
+      }
+      if('.' == k) {
+        m[curr_model].draw = ! m[curr_model].draw;
+      }
+      if('w' == k) {
+        toggleWireframe = true;
+      }
+      if('1' == k) {
+        // FIXME
+        /*
+        curr_manip = 1;
+        camera.disable();
+        clipcam.disable();
+        scenecam.disable();
+        if(curr_view == 0)
+          camera.enable();
+        else if(curr_view == 1)
+          scenecam.enable();
+        else
+          clipcam.enable();
+        for(int i=0; i < num_models; i++)
+          object[i].disable();
+        light.disable();
+        room.disable();
+        */
+      }
+      if('2' == k) {
+        // FIXME
+        /*
+        curr_manip = 2;
+        camera.disable();
+        clipcam.disable();
+        scenecam.disable();
+        light.disable();
+        for(int i=0; i < num_models; i++)
+          object[i].disable();
+        object[curr_model].enable();
+        room.disable();
+        */
+      }
+      if('3' == k) {
+        // FIXME
+        /*
+        curr_manip = 3;
+        camera.disable();
+        clipcam.disable();
+        scenecam.disable();
+        light.enable();
+        for(int i=0; i < num_models; i++)
+          object[i].disable();
+        room.disable();
+        */
+      }
+      if('4' == k) {
+        // FIXME
+        /*
+        curr_manip = 4;
+        camera.disable();
+        clipcam.disable();
+        scenecam.disable();
+        light.disable();
+        for(int i=0; i < num_models; i++)
+          object[i].disable();
+        room.enable();
+        */
+      }
+
+      if('5' == k) {
+        // FIXME
+        /*
+        curr_view = 0;
+        if(curr_manip == 1)
+          key('1',0,0);
+        */
+      }
+
+      if('6' == k) {
+        // FIXME
+        /*
+        curr_view = 1;
+        if(curr_manip == 1)
+          key('1',0,0);
+        */
+      }
+
+      if('7' == k) {
+        // FIXME
+        /*
+        curr_view = 2;
+        if(curr_manip == 1)
+          key('1',0,0);
+        */
+      }
+
+      if('[' == k) {
+        // FIXME: correct?
+        viewer.setZNear(viewer.getZNear() / 2);
+        //        reshaper.zNear /= 2;
+      }
+      if(']' == k) {
+        // FIXME: correct?
+        viewer.setZNear(viewer.getZNear() * 2);
+        //        reshaper.zNear *= 2;
+      }
+
+      if('{' == k) {
+        // FIXME: correct?
+        viewer.setZFar(viewer.getZFar() / 2);
+        //        reshaper.zFar /= 2;
+      }
+      if('}' == k) {
+        // FIXME: correct?
+        viewer.setZFar(viewer.getZFar() * 2);
+        //        reshaper.zFar *= 2;
+      }
+
+      if('!' == k) {
+        enableDepthClampNV = true;
+        toggleDepthClampNV = true;
+      }
+      if('~' == k) {
+        enableDepthClampNV = false;
+        toggleDepthClampNV = true;
+      }
+
+      if('a' == k) {
+        animateForward = true;
+      }
+
+      if('b' == k) {
+        animateBackward = true;
+      }
+
+      if('.' == k) {
+        hideCurrentModel = true;
+      }
+
+      if('n' == k) {
+        light_object_scale *= 1.1f;
+      }
+      if('N' == k) {
+        light_object_scale /= 1.1f;
+      }
+
+      if('L' == k) {
+        if(b[k])
+          light_position.set(0,0,0,1);
+        else
+          light_position.set(0.25f, 0.25f, 1, 0);
+      }
+
+      if ('c' == k) {
+        doViewAll = true;
+      }
+    }
+
+    private void initModel() {
+      int i = 0;
+
+      try {
+        MD2.Model mod = MD2.loadMD2(getClass().getClassLoader().getResourceAsStream("demos/data/models/knight.md2"));
+        m[i] = new Model();
+        m[i].mod = mod;
+        m[i].interp_frame = (MD2.Frame) m[i].mod.f[0].clone();
+        m[i].ambient.componentMul(m[i].diffuse);
+        i++;
+      } catch (IOException e) {
+        e.printStackTrace();
+      }
+
+      num_models = i;
+    }
+
+    // interpolate between keyframes
+    private void interpolate_frame() {
+      float frac =  m[curr_model].frame_num - (float) Math.floor(m[curr_model].frame_num);
+      int f0_index = (int) Math.floor(m[curr_model].frame_num);
+      int f1_index = ((int) Math.ceil(m[curr_model].frame_num)) % m[curr_model].mod.f.length;
+      MD2.Frame f0 = m[curr_model].mod.f[f0_index];
+      MD2.Frame f1 = m[curr_model].mod.f[f1_index];
+
+      for (int i = 0; i < f0.pn.length; i++) {
+        MD2.PositionNormal pn  = m[curr_model].interp_frame.pn[i];
+        MD2.PositionNormal pn0 = f0.pn[i];
+        MD2.PositionNormal pn1 = f1.pn[i];
+
+        pn.x = (1-frac) * pn0.x + frac * pn1.x;
+        pn.y = (1-frac) * pn0.y + frac * pn1.y;
+        pn.z = (1-frac) * pn0.z + frac * pn1.z;
+        pn.nx = (1-frac) * pn0.nx + frac * pn1.nx;
+        pn.ny = (1-frac) * pn0.ny + frac * pn1.ny;
+        pn.nz = (1-frac) * pn0.nz + frac * pn1.nz;
+      }
+    
+      for (int i = 0; i < f0.triplane.length; i++) {
+        MD2.Plane p = m[curr_model].interp_frame.triplane[i];
+
+        MD2.computePlane(m[curr_model].interp_frame.pn[m[curr_model].mod.tri[i].v[0].pn_index], 
+                         m[curr_model].interp_frame.pn[m[curr_model].mod.tri[i].v[1].pn_index],
+                         m[curr_model].interp_frame.pn[m[curr_model].mod.tri[i].v[2].pn_index],
+                         p); 
+      }
+    }
+
+    // This routine draws the end caps (both local and infinite) for an
+    // occluder.  These caps are required for the zfail approach to work.
+    private void drawShadowVolumeEndCaps(GL gl, int mindex) {
+      Vec4f olight = new Vec4f();
+
+      Mat4f ml = new Mat4f(objectManipXform);
+      ml.invertRigid();
+      ml = ml.mul(lightManipXform);
+      ml.xformVec(light_position, olight);
+
+      MD2.PositionNormal[] vpn = m[mindex].interp_frame.pn;
+
+      gl.glPushMatrix();
+      gl.glMultMatrixf(getData(objectManipXform));
+      gl.glBegin(GL.GL_TRIANGLES);
+      for (int i = 0; i < m[mindex].mod.tri.length; i++) {
+        if (m[mindex].mod.tri[i].kill)
+          continue;
+        MD2.Plane p = m[mindex].interp_frame.triplane[i];
+
+        boolean facing_light  = (( p.a * olight.get(0) + 
+                                   p.b * olight.get(1) +
+                                   p.c * olight.get(2) +
+                                   p.d * olight.get(3) ) >= 0 );
+
+        for (int j = 0; j < 3; j++) {
+          MD2.PositionNormal pn = vpn[m[mindex].mod.tri[i].v[j].pn_index];
+          if (facing_light)  // draw locally
+            gl.glVertex4f(pn.x, pn.y, pn.z, 1);
+          else              // draw at infinity
+            gl.glVertex4f(pn.x*olight.get(3) - olight.get(0),
+                          pn.y*olight.get(3) - olight.get(1),
+                          pn.z*olight.get(3) - olight.get(2),
+                          0);
+        }
+      }
+      gl.glEnd();
+      gl.glPopMatrix();
+    }
+
+    private void drawModel(GL gl, int mindex, boolean do_diffuse) {
+      MD2.PositionNormal[] vpn = m[mindex].interp_frame.pn;
+
+      float[] zero = new float[] {  0,  0,  0,  0};
+      float[] dim  = new float[] {.2f,.2f,.2f,.2f};
+      float[] diffuse = new float[4];
+      float[] specular = new float[4];
+      gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, getData(m[mindex].ambient));
+      gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_DIFFUSE, getData(m[mindex].diffuse));
+      gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, getData(m[mindex].specular));
+      gl.glMaterialf(GL.GL_FRONT_AND_BACK, GL.GL_SHININESS, m[mindex].shininess);
+      if (!do_diffuse) {
+        gl.glGetLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, diffuse);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, dim);
+        gl.glGetLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, specular);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, zero);
+      } else {
+        gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE);
+        gl.glEnable(GL.GL_BLEND);
+        gl.glStencilFunc(GL.GL_EQUAL, 128, ~0);
+        gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);
+        gl.glEnable(GL.GL_STENCIL_TEST);
+        gl.glDepthFunc(GL.GL_EQUAL);
+      }
+      gl.glPushMatrix();
+      gl.glMultMatrixf(getData(objectManipXform));
+      gl.glEnable(GL.GL_LIGHTING);
+
+      gl.glPolygonOffset(0,-2);
+      gl.glEnable(GL.GL_POLYGON_OFFSET_FILL);
+
+      gl.glBegin(GL.GL_TRIANGLES);
+      {
+        for (int i = 0; i < m[mindex].mod.tri.length; i++) {
+          for(int j=0; j < 3; j++) {
+            MD2.PositionNormal pn = vpn[m[mindex].mod.tri[i].v[j].pn_index];
+            gl.glNormal3f(pn.nx, pn.ny, pn.nz);
+            gl.glVertex4f(pn.x, pn.y, pn.z, 1);
+          }
+        }
+      }
+      gl.glEnd();
+
+      gl.glDisable(GL.GL_POLYGON_OFFSET_FILL);
+
+      gl.glDisable(GL.GL_LIGHTING);
+      gl.glPopMatrix();
+      gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_DIFFUSE,  new float[] { 0.8f, 0.8f, 0.8f, 1});
+      gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, new float[] { 0.3f, 0.3f, 0.3f, 1});
+
+      if (!do_diffuse) {
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, diffuse);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, specular);
+      } else {
+        gl.glDisable(GL.GL_BLEND);
+        //glDisable(GL.GL_STENCIL_TEST);
+        gl.glStencilFunc(GL.GL_ALWAYS, 128, ~0);
+        gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);
+
+        gl.glDepthFunc(GL.GL_LESS);
+      }
+    }
+
+    // This is for drawing the walls of the room.
+    private void drawMesh(GL gl, float size, int tess) {
+      float hsize = size/2;
+      float delta = size/(tess-1);
+
+      gl.glPushMatrix();
+      gl.glTranslatef(-hsize, -hsize, hsize);
+    
+      gl.glNormal3f(0,0,-1);
+
+      float x = 0;
+      for(int i=0; i < tess-1; i++) {
+        float y = 0;
+        gl.glBegin(GL.GL_QUAD_STRIP);
+        for(int j=0; j < tess; j++) {
+          gl.glTexCoord2f(      x, y);
+          gl.glVertex2f  (      x, y);
+          gl.glTexCoord2f(x+delta, y);
+          gl.glVertex2f  (x+delta, y);
+          y += delta;
+        }
+        gl.glEnd();
+        x += delta;
+      }
+      gl.glPopMatrix();
+    }
+
+    private void drawCube(GL gl) {
+      gl.glBindTexture(GL.GL_TEXTURE_2D, wallTexObject);
+      gl.glEnable(GL.GL_TEXTURE_2D);
+      gl.glPushMatrix();
+      // FIXME
+      //      room.apply_transform();
+      gl.glCallList(faceDisplayList);
+      gl.glRotatef(90, 1, 0, 0);
+      gl.glCallList(faceDisplayList);
+      gl.glRotatef(90, 1, 0, 0);
+      gl.glCallList(faceDisplayList);
+      gl.glRotatef(90, 1, 0, 0);
+      gl.glCallList(faceDisplayList);
+      gl.glRotatef(90, 1, 0, 0);
+      gl.glRotatef(90, 0, 1, 0);
+      gl.glCallList(faceDisplayList);
+      gl.glRotatef(180, 0, 1, 0);
+      gl.glCallList(faceDisplayList);
+      gl.glPopMatrix();
+      gl.glDisable(GL.GL_TEXTURE_2D);
+    }
+
+    private void drawRoom(GL gl, boolean do_diffuse) {
+      float[] zero = new float[] {0,0,0,0};
+      float[] a = new float[4];
+      a[0] = room_ambient;
+      a[1] = room_ambient;
+      a[2] = room_ambient;
+      a[3] = 1;
+
+      float[] d1 = new float[] {.1f,.1f,.1f,.1f};
+      float[] d2 = new float[] {.7f,.7f,.7f,.7f};
+      float[] s  = new float[] {.7f,.7f,.7f,.7f};
+      float[] emission = new float[4];
+      float[] ambient  = new float[4];
+      float[] diffuse  = new float[4];
+      float[] specular = new float[4];
+
+      gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, a);
+      gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_DIFFUSE,  new float[] {0.8f, 0.8f, 0.8f, 1});
+      gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, new float[] {0.4f, 0.4f, 0.4f, 1});
+      gl.glMaterialf(GL.GL_FRONT_AND_BACK, GL.GL_SHININESS, 64.0f);
+
+      if (!do_diffuse) {
+        gl.glGetLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, diffuse);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, d1);
+        gl.glGetLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, specular);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, zero);
+        gl.glStencilFunc(GL.GL_ALWAYS, 128, ~0);
+      } else {
+        gl.glGetLightfv(GL.GL_LIGHT0, GL.GL_EMISSION, emission);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_EMISSION, zero);
+        gl.glGetLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, ambient);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, zero);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, d2);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, s);
+
+        gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE);
+        gl.glEnable(GL.GL_BLEND);
+        gl.glStencilFunc(GL.GL_EQUAL, 128, ~0);
+        gl.glDepthFunc(GL.GL_EQUAL);
+      }
+      gl.glPushMatrix();
+      gl.glTranslatef(0,9,0);
+      gl.glEnable(GL.GL_LIGHTING);
+      gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);
+      gl.glEnable(GL.GL_STENCIL_TEST);
+
+      drawCube(gl);
+
+      gl.glStencilFunc(GL.GL_ALWAYS, 128, ~0);
+      gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);
+
+      gl.glDisable(GL.GL_LIGHTING);
+      gl.glPopMatrix();
+    
+      if (!do_diffuse) {
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, diffuse);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, specular);
+      } else {
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_EMISSION, emission);
+        gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, ambient);
+
+        gl.glDisable(GL.GL_BLEND);
+        gl.glDepthFunc(GL.GL_LESS);
+      }
+    }
+    
+    // This routine draws the extruded "possible silhouette" edge.  The
+    // edge is extruded to infinity.
+
+    // The paper describes identifying silhouette edge loops.  The approach
+    // in this demo is to visit each edge, determine if it's a "possible silhouette"
+    // or not, and if it is, draw the extruded edge.   This approach is not
+    // as efficient, but it has the benefit of being extremely simple.
+
+    // This routine also doubles as the routine for drawing the local and ininite
+    // silhouette edges (when prim == GL_LINES).
+    private void drawShadowVolumeEdges(GL gl,
+                                       int mindex,
+                                       int prim,
+                                       boolean local,
+                                       boolean infinity) {
+      Vec4f olight = new Vec4f();
+
+      Mat4f ml = new Mat4f(objectManipXform);
+      ml.invertRigid();
+      ml = ml.mul(lightManipXform);
+      ml.xformVec(light_position, olight);
+
+      gl.glPushMatrix();
+      gl.glMultMatrixf(getData(objectManipXform));
+
+      MD2.Frame f = m[mindex].interp_frame;
+
+      gl.glBegin(prim);
+      for (int i = 0; i < m[mindex].mod.edge.length; i++) {
+          MD2.WingedEdge we = m[mindex].mod.edge[i];
+          if (we.w[0] == -1 || m[mindex].mod.tri[we.w[0]].kill ||
+              we.w[1] == -1 || m[mindex].mod.tri[we.w[1]].kill )
+            continue;
+
+          MD2.Plane p0 = f.triplane[we.w[0]];
+          float f0 = ( p0.a * olight.get(0) + 
+                       p0.b * olight.get(1) +
+                       p0.c * olight.get(2) +
+                       p0.d * olight.get(3) );
+            
+          float f1 = -f0;
+          if(we.w[1] != -1) {
+            MD2.Plane p1 = f.triplane[we.w[1]];
+
+            f1 = ( p1.a * olight.get(0) + 
+                   p1.b * olight.get(1) +
+                   p1.c * olight.get(2) +
+                   p1.d * olight.get(3) );
+          }
+
+          int[] edge = new int[2];
+
+          if(f0 >= 0 && f1 < 0) {
+            edge[0] = we.e[1];
+            edge[1] = we.e[0];
+          } else if(f1 >= 0 && f0 < 0) {
+            edge[0] = we.e[0];
+            edge[1] = we.e[1];
+          } else {
+            continue;
+          }
+        
+          MD2.PositionNormal pn0 = f.pn[edge[0]];
+          MD2.PositionNormal pn1 = f.pn[edge[1]];
+
+          if(prim == GL.GL_QUADS || local) {
+            // local segment
+            gl.glVertex4f(pn0.x, pn0.y, pn0.z, 1);
+            gl.glVertex4f(pn1.x, pn1.y, pn1.z, 1);
+          }
+          if(prim == GL.GL_QUADS || infinity) {
+            // segment projected to infinity
+            gl.glVertex4f(pn1.x*olight.get(3) - olight.get(0),
+                          pn1.y*olight.get(3) - olight.get(1),
+                          pn1.z*olight.get(3) - olight.get(2),
+                          0);
+            gl.glVertex4f(pn0.x*olight.get(3) - olight.get(0),
+                          pn0.y*olight.get(3) - olight.get(1),
+                          pn0.z*olight.get(3) - olight.get(2),
+                          0);
+          }
+        }
+      gl.glEnd();
+      gl.glPopMatrix();
+    }
+
+    private void drawShadowVolumeExtrudedEdges(GL gl, int mindex) {
+      drawShadowVolumeEdges(gl, mindex, GL.GL_QUADS, true, true);
+    }
+
+    private void drawPossibleSilhouette(GL gl, int mindex) {
+      gl.glLineWidth(3);
+      gl.glColor3f(1,1,1);
+      drawShadowVolumeEdges(gl, mindex, GL.GL_LINES, true, !b['-']);
+      gl.glLineWidth(1);
+    }
+
+    // Draw the shadow volume into the stencil buffer.
+    private void drawShadowVolumeToStencil(GL gl, int mindex) {
+      gl.glDepthFunc(GL.GL_LESS);
+      gl.glDepthMask(false);
+
+      gl.glStencilFunc(GL.GL_ALWAYS, 128, ~0);
+      gl.glEnable(GL.GL_STENCIL_TEST);
+
+      gl.glEnable(GL.GL_CULL_FACE);
+      gl.glCullFace(GL.GL_FRONT);
+      gl.glStencilOp(GL.GL_KEEP, GL.GL_INCR, GL.GL_KEEP);
+      gl.glColorMask(false, false, false, false);
+
+      drawShadowVolumeExtrudedEdges(gl, mindex);
+      drawShadowVolumeEndCaps(gl, mindex);
+
+      gl.glCullFace(GL.GL_BACK);
+      gl.glStencilOp(GL.GL_KEEP, GL.GL_DECR, GL.GL_KEEP);
+
+      drawShadowVolumeExtrudedEdges(gl, mindex);
+      drawShadowVolumeEndCaps(gl, mindex);
+
+      gl.glColorMask(true, true, true, true);
+      gl.glDisable(GL.GL_CULL_FACE);
+
+      gl.glStencilFunc(GL.GL_ALWAYS, 128, ~0);
+      gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);
+
+      gl.glDepthMask(true);
+      gl.glDepthFunc(GL.GL_LESS);
+    }
+
+    // Draw the shadow volume into the color buffer.
+    private void drawShadowVolumeToColor(GL gl, int mindex) {
+      gl.glDepthFunc(GL.GL_LESS);
+      gl.glDepthMask(false);
+
+      gl.glEnable(GL.GL_BLEND);
+      gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+
+      gl.glColor4f(1,1,1,.7f * volume_alpha);
+      drawShadowVolumeEndCaps(gl, mindex);
+      gl.glColor4f(1,1,.7f,.15f * volume_alpha);
+      drawShadowVolumeExtrudedEdges(gl, mindex);
+
+      gl.glDepthMask(true);
+      gl.glDepthFunc(GL.GL_LESS);
+      gl.glDisable(GL.GL_BLEND);
+    }
+
+    // Draw an icon to show where the local light is
+    // or in what direction the infinite light is pointing. 
+    private void drawLight(GL gl, GLU glu) {
+      gl.glColor3f(1,1,0);
+      gl.glPushMatrix();
+      gl.glMultMatrixf(getData(lightManipXform));
+      gl.glScalef(light_object_scale, light_object_scale, light_object_scale);
+      if (b['L']) {
+        glut.glutSolidSphere(glu, .01f, 20, 10);
+      } else {
+        Vec3f ldir = new Vec3f(light_position.get(0),
+                               light_position.get(1),
+                               light_position.get(2));
+        Rotf r = new Rotf(new Vec3f(0,0,1), ldir);
+        Mat4f m = new Mat4f();
+        m.makeIdent();
+        m.setRotation(r);
+        m = m.mul(perspectiveInverse(30, 1, 0.001f, 0.04f));
+        gl.glRotatef(180, 1, 0, 0);
+        gl.glTranslatef(0,0,-0.02f);
+        gl.glMultMatrixf(getData(m));
+        glut.glutSolidCube(gl, 2);
+      }
+      gl.glPopMatrix();
+    }
+
+    // The infinite frustum set-up code.
+    private Mat4f infiniteFrustum(float left, float right,
+                                  float bottom, float top,
+                                  float zNear) {
+      Mat4f m = new Mat4f();
+      m.makeIdent();
+	
+      m.set(0,0, (2*zNear) / (right - left));
+      m.set(0,2, (right + left) / (right - left));
+	
+      m.set(1,1, (2*zNear) / (top - bottom));
+      m.set(1,2, (top + bottom) / (top - bottom));
+	
+      // nudge infinity in just slightly for lsb slop
+      float nudge = 1 - 1.0f / (1<<23);
+
+      m.set(2,2, -1  * nudge);
+      m.set(2,3, -2*zNear * nudge);
+	
+      m.set(3,2, -1);
+      m.set(3,3, 0);
+	
+      m.transpose();
+      
+      return m;
+    }
+
+    private Mat4f infiniteFrustumInverse(float left, float right,
+                                         float bottom, float top,
+                                         float zNear) {
+      Mat4f m = new Mat4f();
+      m.makeIdent();
+	
+      m.set(0,0, (right - left) / (2 * zNear));
+      m.set(0,3, (right + left) / (2 * zNear));
+	
+      m.set(1,1, (top - bottom) / (2 * zNear));
+      m.set(1,3, (top + bottom) / (2 * zNear));
+	
+      m.set(2,2, 0);
+      m.set(2,3, -1);
+	
+      m.set(3,2, -1 / (2 * zNear));
+      m.set(3,3, 1 / (2 * zNear));
+	
+      return m;
+    }
+
+    private Mat4f infinitePerspective(float fovy, float aspect, float zNear) {
+      float tangent = (float) Math.tan(fovy / 2.0);
+      float y = tangent * zNear;
+      float x = aspect * y;
+      return infiniteFrustum(-x, x, -y, y, zNear);
+    }
+
+    private Mat4f infinitePerspectiveInverse(float fovy, float aspect, float zNear) {
+      float tangent = (float) Math.tan(fovy / 2.0);
+      float y = tangent * zNear;
+      float x = aspect * y;
+      return infiniteFrustumInverse(-x, x, -y, y, zNear);
+    }
+
+    private void applyInfinitePerspective(GL gl, ExaminerViewer v) {
+      CameraParameters parms = v.getCameraParameters();
+      float aspect = parms.getImagePlaneAspectRatio();
+      gl.glMultMatrixf(getData(infinitePerspective(parms.getVertFOV(), aspect, v.getZNear())));
+    }
+
+    private void applyInfinitePerspectiveInverse(GL gl, ExaminerViewer v) {
+      CameraParameters parms = v.getCameraParameters();
+      float aspect = parms.getImagePlaneAspectRatio();
+      gl.glMultMatrixf(getData(infinitePerspectiveInverse(parms.getVertFOV(), aspect, v.getZNear())));
+    }
+
+    private Mat4f perspectiveInverse(float fovy, float aspect, float zNear, float zFar) {
+      float tangent = (float) Math.tan(Math.toRadians(fovy / 2.0));
+      float y = tangent * zNear;
+      float x = aspect * y;
+      return frustumInverse(-x, x, -y, y, zNear, zFar);
+    }
+
+    private Mat4f frustumInverse(float left, float right,
+                                 float bottom, float top,
+                                 float zNear, float zFar) {
+      Mat4f m = new Mat4f();
+      m.makeIdent();
+
+      m.set(0, 0, (right - left) / (2 * zNear));
+      m.set(0, 3, (right + left) / (2 * zNear));
+	
+      m.set(1, 1, (top - bottom) / (2 * zNear));
+      m.set(1, 3, (top + bottom) / (2 * zNear));
+
+      m.set(2, 2,  0);
+      m.set(2, 3, -1);
+	
+      m.set(3, 2, -(zFar - zNear) / (2 * zFar * zNear));
+      m.set(3, 3,  (zFar + zNear) / (2 * zFar * zNear));
+
+      return m;
+    }
+
+    private float[] getData(Vec4f v) {
+      return new float[] { v.x(), v.y(), v.z(), v.w() };
+    }
+
+    private float[] getData(Mat4f m) {
+      float[] res = new float[16];
+      m.getColumnMajorData(res);
+      return res;
+    }
+  }
+
+  private void runExit() {
+    quit = true;
+    // Note: calling System.exit() synchronously inside the draw,
+    // reshape or init callbacks can lead to deadlocks on certain
+    // platforms (in particular, X11) because the JAWT's locking
+    // routines cause a global AWT lock to be grabbed. Instead run
+    // the exit routine in another thread.
+    new Thread(new Runnable() {
+        public void run() {
+          animator.stop();
+          System.exit(0);
+        }
+      }).start();
+  }
+}
-- 
cgit v1.2.3