From 41cd6c47b23975098cd155517790e018670785e7 Mon Sep 17 00:00:00 2001
From: Kenneth Russel <kbrussel@alum.mit.edu>
Date: Mon, 15 Jun 2009 23:12:27 +0000
Subject: Copied JOGL_2_SANDBOX r350 on to trunk; JOGL_2_SANDBOX branch is now
 closed

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

(limited to 'src/demos/proceduralTexturePhysics/Water.java')

diff --git a/src/demos/proceduralTexturePhysics/Water.java b/src/demos/proceduralTexturePhysics/Water.java
new file mode 100644
index 0000000..89c1495
--- /dev/null
+++ b/src/demos/proceduralTexturePhysics/Water.java
@@ -0,0 +1,1862 @@
+/*
+ * 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.proceduralTexturePhysics;
+
+import com.sun.opengl.util.FileUtil;
+import com.sun.opengl.util.texture.Texture;
+import com.sun.opengl.util.texture.TextureData;
+import com.sun.opengl.util.texture.TextureIO;
+import demos.util.Cubemap;
+import gleem.linalg.Mat4f;
+import gleem.linalg.Rotf;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2ES1;
+import javax.media.opengl.GL2;
+import javax.media.opengl.GLAutoDrawable;
+import javax.media.opengl.GLCapabilities;
+import javax.media.opengl.GLDrawableFactory;
+import javax.media.opengl.GLEventListener;
+import javax.media.opengl.GLException;
+import javax.media.opengl.GLPbuffer;
+import javax.media.opengl.glu.GLU;
+import com.sun.opengl.util.BufferUtil;
+
+
+
+/**
+ * Auxiliary Water simulation class used by ProceduralTexturePhysics
+ * main loop. Demonstration by NVidia Corporation.
+ *
+ * <P>
+ *
+ * Ported to Java and ARB_fragment_program by Kenneth Russell
+ */
+
+public class Water {
+  // Note: this class is organized differently than most of the demos
+  // due to the fact that it is used for two purposes: when the
+  // pbuffer's context is current it is used to update the cellular
+  // automata, and when the parent drawable's context is current it is
+  // used to render the water geometry (with the parent drawable's GL
+  // object).
+
+  private GLU glu = new GLU();
+
+  // Rendering modes
+  public static final int CA_FULLSCREEN_REFLECT   = 0;
+  public static final int CA_FULLSCREEN_FORCE     = 1;
+  public static final int CA_FULLSCREEN_HEIGHT    = 2;
+  public static final int CA_FULLSCREEN_NORMALMAP = 3;
+  public static final int CA_TILED_THREE_WINDOWS  = 4;
+  public static final int CA_DO_NOT_RENDER        = 5;
+
+  private int[] initialMapDimensions = new int[2];
+  private TextureData initialMapData;
+
+  private String tmpSpinFilename;
+  private String tmpDropletFilename;
+  private String tmpCubeMapFilenamePrefix;
+  private String tmpCubeMapFilenameSuffix;
+
+  private GLPbuffer pbuffer;
+  private Rotf cameraOrientation = new Rotf();
+
+  // Dynamic texture names
+  private static final int CA_TEXTURE_FORCE_INTERMEDIATE = 0;
+  private static final int CA_TEXTURE_FORCE_TARGET       = 1;
+  private static final int CA_TEXTURE_VELOCITY_SOURCE    = 2;
+  private static final int CA_TEXTURE_VELOCITY_TARGET    = 3;
+  private static final int CA_TEXTURE_HEIGHT_SOURCE      = 4;
+  private static final int CA_TEXTURE_HEIGHT_TARGET      = 5;
+  private static final int CA_TEXTURE_NORMAL_MAP         = 6;
+  private static final int CA_NUM_DYNAMIC_TEXTURES       = 7;
+    
+  // List names
+  private static final int CA_FRAGMENT_PROGRAM_EQ_WEIGHT_COMBINE     = 0;
+  private static final int CA_FRAGMENT_PROGRAM_NEIGHBOR_FORCE_CALC_1 = 1;
+  private static final int CA_FRAGMENT_PROGRAM_NEIGHBOR_FORCE_CALC_2 = 2;
+  private static final int CA_FRAGMENT_PROGRAM_APPLY_FORCE           = 3;
+  private static final int CA_FRAGMENT_PROGRAM_APPLY_VELOCITY        = 4;
+  private static final int CA_FRAGMENT_PROGRAM_CREATE_NORMAL_MAP     = 5;
+  private static final int CA_FRAGMENT_PROGRAM_REFLECT               = 6;
+  private static final int CA_DRAW_SCREEN_QUAD                       = 7;
+  private static final int CA_NUM_LISTS                              = 8;
+
+  // Static textures
+  private Texture initialMapTex;
+  private Texture spinTex;
+  private Texture dropletTex;
+  private Texture cubemap;
+
+  private Texture[] dynamicTextures = new Texture[CA_NUM_DYNAMIC_TEXTURES];
+    
+  private int       texHeightInput;                 // current input height texture ID.
+  private int       texHeightOutput;                // current output height texture ID.
+  private int       texVelocityInput;               // current input velocity texture ID.
+  private int       texVelocityOutput;              // current output velocity texture ID.
+  private int       texForceStepOne;                // intermediate force computation result texture ID.
+  private int       texForceOutput;                 // current output force texture ID.
+
+  private int[]     displayListIDs = new int[CA_NUM_LISTS];
+    
+  private int       vertexProgramID;                // one vertex program is used to choose the texcoord offset
+
+  private int       flipState;                      // used to flip target texture configurations.
+
+  private boolean   wrap;                           // CA can either wrap its borders, or clamp (clamp by default)  
+  private boolean   reset = true;                   // are we resetting this frame? (user hit reset).
+  private boolean   singleStep;                     // animation step on keypress.
+  private boolean   animate = true;                 // continuous animation.
+  private boolean   slow = true;                    // run slow.
+  private boolean   wireframe;                      // render in wireframe mode
+  private boolean   applyInteriorBoundaries = true; // enable / disable "boundary" image drawing.
+  private boolean   spinLogo = true;                // draw spinning logo.
+  private boolean   createNormalMap = true;         // enable / disable normal map creation.
+
+  private float     perTexelWidth;                  // width of a texel (percentage of texture)
+  private float     perTexelHeight;                 // height of a texel
+
+  private float     blurDist = 0.5f;                // distance over which to blur.
+  private boolean   mustUpdateBlurOffsets;          // flag indicating blurDist was set last tick
+
+  private float     normalSTScale = 0.8f;           // scale of normals in normal map.
+  private float     bumpScale = 0.25f;              // scale of bumps in water.
+
+  private float     dropletFrequency = 0.175f;      // frequency at which droplets are drawn in water...
+
+  private int       slowDelay = 1;                  // amount (milliseconds) to delay when running slow.
+  private int       skipInterval;                   // frames to skip simulation.
+  private int       skipCount;                      // frame count for skipping rendering
+
+  private int       angle;                          // angle in degrees for spinning logo
+
+  private List/*<Droplet>*/ droplets = new ArrayList/*<Droplet>*/();             // array of droplets
+
+  private int       renderMode; 
+
+  // Constant memory locations
+  private static final int CV_UV_OFFSET_TO_USE =  0;
+
+  private static final int CV_UV_T0_NO_OFFSET  =  1;
+  private static final int CV_UV_T0_TYPE1      =  2;
+  private static final int CV_UV_T0_TYPE2      =  3;
+  private static final int CV_UV_T0_TYPE3      =  4;
+  private static final int CV_UV_T0_TYPE4      =  5;
+
+  private static final int CV_UV_T1_NO_OFFSET  =  6;
+  private static final int CV_UV_T1_TYPE1      =  7;
+  private static final int CV_UV_T1_TYPE2      =  8;
+  private static final int CV_UV_T1_TYPE3      =  9;
+  private static final int CV_UV_T1_TYPE4      = 10;
+
+  private static final int CV_UV_T2_NO_OFFSET  = 11;
+  private static final int CV_UV_T2_TYPE1      = 12;
+  private static final int CV_UV_T2_TYPE2      = 13;
+  private static final int CV_UV_T2_TYPE3      = 14;
+  private static final int CV_UV_T2_TYPE4      = 15;
+
+  private static final int CV_UV_T3_NO_OFFSET  = 16;
+  private static final int CV_UV_T3_TYPE1      = 17;
+  private static final int CV_UV_T3_TYPE2      = 18;
+  private static final int CV_UV_T3_TYPE3      = 19;
+  private static final int CV_UV_T3_TYPE4      = 20;
+
+  private static final int CV_CONSTS_1         = 21;
+
+  public void initialize(String initialMapFilename,
+                         String spinFilename,
+                         String dropletFilename,
+                         String cubeMapFilenamePrefix,
+                         String cubeMapFilenameSuffix,
+                         GLAutoDrawable parentWindow) {
+    loadInitialTexture(initialMapFilename);
+    tmpSpinFilename           = spinFilename;
+    tmpDropletFilename        = dropletFilename;
+    tmpCubeMapFilenamePrefix  = cubeMapFilenamePrefix;
+    tmpCubeMapFilenameSuffix  = cubeMapFilenameSuffix;
+    
+    // create the pbuffer.  Will use this as an offscreen rendering buffer.
+    // it allows rendering a texture larger than our window.
+    GLCapabilities caps = parentWindow.getChosenGLCapabilities();
+    caps.setDoubleBuffered(false);
+    if (!GLDrawableFactory.getFactory(caps.getGLProfile()).canCreateGLPbuffer()) {
+      throw new GLException("Pbuffers not supported with this graphics card");
+    }
+    pbuffer = GLDrawableFactory.getFactory(caps.getGLProfile()).createGLPbuffer(caps,
+                                                             null,
+                                                             initialMapDimensions[0],
+                                                             initialMapDimensions[1],
+                                                             parentWindow.getContext());
+    pbuffer.addGLEventListener(new Listener());
+  }
+
+  public void destroy() {
+    if (pbuffer != null) {
+      pbuffer.destroy();
+      pbuffer = null;
+    }
+    reset = true;
+  }
+
+  public void tick() { 
+    pbuffer.display();
+  }
+
+  public void draw(GL2 gl, Rotf cameraOrientation) {
+    this.cameraOrientation.set(cameraOrientation);
+
+    if (skipCount >= skipInterval && renderMode != CA_DO_NOT_RENDER) {
+      skipCount = 0;
+      // Display the results of the rendering to texture
+      if (wireframe) {
+        gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_LINE);
+           
+        // chances are the texture will be all dark, so lets not use a texture
+        gl.glDisable(GL2.GL_TEXTURE_2D);
+      } else {
+        gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_FILL);
+            			
+        gl.glActiveTexture(GL2.GL_TEXTURE0);
+        gl.glEnable(GL2.GL_TEXTURE_2D);
+      }
+
+      switch (renderMode) {
+        case CA_FULLSCREEN_REFLECT: {
+          // include bump scale...
+          Mat4f bscale = new Mat4f();
+          bscale.makeIdent();
+          bscale.set(0, 0, bumpScale);
+          bscale.set(1, 1, bumpScale);
+          Mat4f rot = new Mat4f();
+          rot.makeIdent();
+          rot.setRotation(cameraOrientation);
+          Mat4f matRot = rot.mul(bscale);
+
+          gl.glCallList(displayListIDs[CA_FRAGMENT_PROGRAM_REFLECT]);
+
+          // Draw quad over full display
+          gl.glActiveTexture(GL2.GL_TEXTURE0);
+          dynamicTextures[CA_TEXTURE_NORMAL_MAP].bind();
+          dynamicTextures[CA_TEXTURE_NORMAL_MAP].disable();
+          gl.glActiveTexture(GL2.GL_TEXTURE3);
+          cubemap.bind();
+          cubemap.enable();
+
+          gl.glColor4f(1, 1, 1, 1);
+          gl.glBegin(GL2.GL_QUADS);
+                
+          gl.glMultiTexCoord2f(GL2.GL_TEXTURE0, 0,0);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE1, matRot.get(0,0), matRot.get(0,1), matRot.get(0,2),  1);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE2, matRot.get(1,0), matRot.get(1,1), matRot.get(1,2),  1);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE3, matRot.get(2,0), matRot.get(2,1), matRot.get(2,2),  1);
+          gl.glVertex2f(-1,-1);
+                
+          gl.glMultiTexCoord2f(GL2.GL_TEXTURE0, 1,0);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE1, matRot.get(0,0), matRot.get(0,1), matRot.get(0,2), -1);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE2, matRot.get(1,0), matRot.get(1,1), matRot.get(1,2),  1);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE3, matRot.get(2,0), matRot.get(2,1), matRot.get(2,2),  1);
+          gl.glVertex2f( 1,-1);
+                
+          gl.glMultiTexCoord2f(GL2.GL_TEXTURE0, 1,1);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE1, matRot.get(0,0), matRot.get(0,1), matRot.get(0,2), -1);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE2, matRot.get(1,0), matRot.get(1,1), matRot.get(1,2), -1);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE3, matRot.get(2,0), matRot.get(2,1), matRot.get(2,2),  1);
+          gl.glVertex2f( 1, 1);
+                
+          gl.glMultiTexCoord2f(GL2.GL_TEXTURE0, 0,1);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE1, matRot.get(0,0), matRot.get(0,1), matRot.get(0,2),  1);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE2, matRot.get(1,0), matRot.get(1,1), matRot.get(1,2), -1);
+          gl.glMultiTexCoord4f(GL2.GL_TEXTURE3, matRot.get(2,0), matRot.get(2,1), matRot.get(2,2),  1);
+          gl.glVertex2f(-1, 1);
+                
+          gl.glEnd();
+    
+          cubemap.disable();
+          gl.glDisable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+                
+          break;
+        }
+
+        case CA_FULLSCREEN_NORMALMAP: {
+          // Draw quad over full display
+          gl.glActiveTexture(GL2.GL_TEXTURE0);
+          dynamicTextures[CA_TEXTURE_NORMAL_MAP].bind();
+                
+          gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+          break;
+        }
+
+        case CA_FULLSCREEN_HEIGHT: {
+          // Draw quad over full display
+          gl.glActiveTexture(GL2.GL_TEXTURE0);
+          gl.glBindTexture(GL2.GL_TEXTURE_2D, texHeightOutput);
+                
+          gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+          break;
+        }
+
+        case CA_FULLSCREEN_FORCE: {
+          // Draw quad over full display
+          gl.glActiveTexture(GL2.GL_TEXTURE0);
+          dynamicTextures[CA_TEXTURE_FORCE_TARGET].bind();
+			                 
+          gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+          break;
+        }
+
+        case CA_TILED_THREE_WINDOWS: {
+          // Draw quad over full display
+          // lower left
+          gl.glActiveTexture(GL2.GL_TEXTURE0);
+          dynamicTextures[CA_TEXTURE_FORCE_TARGET].bind();
+          gl.glMatrixMode(GL2.GL_MODELVIEW);
+          gl.glPushMatrix();
+			                 
+          gl.glTranslatef(-0.5f, -0.5f, 0);
+          gl.glScalef(0.5f, 0.5f, 1);
+          gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+          gl.glPopMatrix();
+
+          // lower right
+          gl.glBindTexture(GL2.GL_TEXTURE_2D, texVelocityOutput);
+          gl.glPushMatrix();
+			                 
+          gl.glTranslatef(0.5f, -0.5f, 0);
+          gl.glScalef(0.5f, 0.5f, 1);
+          gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+          gl.glPopMatrix();
+
+          // upper left
+          dynamicTextures[CA_TEXTURE_NORMAL_MAP].bind();
+          gl.glMatrixMode(GL2.GL_MODELVIEW);
+          gl.glPushMatrix();
+			                 
+          gl.glTranslatef(-0.5f, 0.5f, 0);
+          gl.glScalef(0.5f, 0.5f, 1);
+          gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+          gl.glPopMatrix();
+
+          // upper right
+          gl.glBindTexture(GL2.GL_TEXTURE_2D, texHeightOutput);
+          gl.glMatrixMode(GL2.GL_MODELVIEW);
+          gl.glPushMatrix();
+			                 
+          gl.glTranslatef(0.5f, 0.5f, 0);
+          gl.glScalef(0.5f, 0.5f, 1);
+          gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+          gl.glPopMatrix();
+			    
+          break;
+        }
+      }
+    } else {
+      // skip rendering this frame
+      skipCount++;
+    }
+  }
+
+  public void singleStep()                               { singleStep  = true;                 }
+  public void enableAnimation(boolean enable)            { animate     = enable;               }
+  public void enableSlowAnimation(boolean enable)        { slow        = enable;               }
+  public void reset()                                    { reset       = true;                 }
+  public void setRenderMode(int mode)                    { renderMode  = mode;                 }
+    
+  public void enableWireframe(boolean enable)            { wireframe   = enable;               }
+  public void enableBorderWrapping(boolean enable)       { wrap        = enable;               }
+    
+  public void enableBoundaryApplication(boolean enable)  { applyInteriorBoundaries = enable;   }
+  public void enableSpinningLogo(boolean enable)         { spinLogo    = enable;               }
+
+  public void  setBlurDistance(float distance)           { blurDist    = distance;
+                                                           mustUpdateBlurOffsets = true;       }
+  public float getBlurDistance()                         { return blurDist;                    }
+
+  public void  setBumpScale(float scale)                 { bumpScale   = scale;                }
+  public float getBumpScale()                            { return bumpScale;                   }
+
+  public void  setDropFrequency(float frequency)         { dropletFrequency = frequency;       }
+  public float getDropFrequency()                        { return dropletFrequency;            }
+
+  public static class Droplet {
+    private float rX;
+    private float rY;
+    private float rScale;
+
+    Droplet(float rX, float rY, float rScale) {
+      this.rX     = rX;
+      this.rY     = rY;
+      this.rScale = rScale;
+    }
+    
+    float rX()     { return rX;     }
+    float rY()     { return rY;     }
+    float rScale() { return rScale; }
+  }
+
+  public synchronized void addDroplet(Droplet drop) {
+    droplets.add(drop);    
+  }
+
+  //----------------------------------------------------------------------
+  // Internals only below this point
+  //
+
+  class Listener implements GLEventListener {
+
+    public void init(GLAutoDrawable drawable) {
+      GL2 gl = drawable.getGL().getGL2();
+
+      initOpenGL(gl);
+    }
+
+    public void dispose(GLAutoDrawable drawable) {
+    }
+
+    public void display(GLAutoDrawable drawable) {
+
+      GL2 gl = drawable.getGL().getGL2();
+      if (mustUpdateBlurOffsets) {
+        updateBlurVertOffset(gl);
+        mustUpdateBlurOffsets = false;
+      }
+      
+      // Take a single step in the cellular automaton
+
+      // Disable culling
+      gl.glDisable(GL2.GL_CULL_FACE);
+
+      if (reset) {
+        reset = false;
+        flipState = 0;
+      }
+
+      if (animate) {
+        // Update the textures for one step of the simulation
+        doSingleTimeStep(gl);
+      } else if (singleStep) {
+        doSingleTimeStep(gl);
+        singleStep = false;
+      }
+	
+      // Force rendering to pbuffer to complete
+      gl.glFlush();
+
+      if (slow && (slowDelay > 0) ) {
+        try {
+          Thread.sleep(slowDelay);
+        } catch (InterruptedException e) {
+        }
+      }
+    }
+
+    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {}
+
+    // Unused routines
+    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {}
+  }
+
+  // We need to load the initial texture file early to get the width
+  // and height for the pbuffer
+  private void loadInitialTexture(String initialMapFilename) {
+    try {
+      initialMapData = TextureIO.newTextureData(getClass().getClassLoader().getResourceAsStream(initialMapFilename),
+                                                false,
+                                                FileUtil.getFileSuffix(initialMapFilename));
+    } catch (IOException e) {
+      throw new GLException(e);
+    }
+    initialMapDimensions[0] = initialMapData.getWidth();
+    initialMapDimensions[1] = initialMapData.getHeight();
+  }
+
+  private void initOpenGL(GL2 gl) {
+    try {
+      loadTextures(gl, tmpSpinFilename, tmpDropletFilename, tmpCubeMapFilenamePrefix, tmpCubeMapFilenameSuffix);
+    } catch (IOException e) {
+      throw new GLException(e);
+    }
+    tmpSpinFilename           = null;
+    tmpDropletFilename        = null;
+    tmpCubeMapFilenamePrefix  = null;
+    tmpCubeMapFilenameSuffix  = null;
+
+    gl.glMatrixMode(GL2.GL_MODELVIEW);
+    gl.glLoadIdentity();
+    gl.glMatrixMode(GL2.GL_PROJECTION);
+    gl.glLoadIdentity();
+    glu.gluOrtho2D(-1, 1, -1, 1);
+    
+    gl.glClearColor(0, 0, 0, 0);
+    gl.glDisable(GL2.GL_LIGHTING);
+    gl.glDisable(GL2.GL_DEPTH_TEST);
+      
+    createAndWriteUVOffsets(gl, initialMapDimensions[0], initialMapDimensions[1]);
+
+    checkExtension(gl, "GL_ARB_vertex_program");
+    checkExtension(gl, "GL_ARB_fragment_program");
+    checkExtension(gl, "GL_ARB_multitexture");
+
+    ///////////////////////////////////////////////////////////////////////////
+    // UV Offset Vertex Program
+    ///////////////////////////////////////////////////////////////////////////
+
+    int[] tmpInt = new int[1];
+    gl.glGenProgramsARB(1, tmpInt, 0);
+    vertexProgramID = tmpInt[0];
+    gl.glBindProgramARB(GL2.GL_VERTEX_PROGRAM_ARB, vertexProgramID);
+
+    String programBuffer = 
+"!!ARBvp1.0\n" +
+"# Constant memory location declarations (must match those in Java sources)\n" +
+"# CV_UV_OFFSET_TO_USE = 0\n" +
+"\n" +
+"# CV_UV_T0_NO_OFFSET  = 1\n" +
+"# CV_UV_T0_TYPE1      = 2\n" +
+"# CV_UV_T0_TYPE2      = 3\n" +
+"# CV_UV_T0_TYPE3      = 4\n" +
+"# CV_UV_T0_TYPE4      = 5\n" +
+"\n" +
+"# CV_UV_T1_NO_OFFSET  = 6\n" +
+"# CV_UV_T1_TYPE1      = 7\n" +
+"# CV_UV_T1_TYPE2      = 8\n" +
+"# CV_UV_T1_TYPE3      = 9\n" +
+"# CV_UV_T1_TYPE4      = 10\n" +
+"\n" +
+"# CV_UV_T2_NO_OFFSET  = 11\n" +
+"# CV_UV_T2_TYPE1      = 12\n" +
+"# CV_UV_T2_TYPE2      = 13\n" +
+"# CV_UV_T2_TYPE3      = 14\n" +
+"# CV_UV_T2_TYPE4      = 15\n" +
+"\n" +
+"# CV_UV_T3_NO_OFFSET  = 16\n" +
+"# CV_UV_T3_TYPE1      = 17\n" +
+"# CV_UV_T3_TYPE2      = 18\n" +
+"# CV_UV_T3_TYPE3      = 19\n" +
+"# CV_UV_T3_TYPE4      = 20\n" +
+"\n" +
+"# CV_CONSTS_1         = 21\n" +
+"\n" +
+"# Parameters\n" +
+"PARAM mvp [4]       = { state.matrix.mvp };     # modelview projection matrix\n" +
+"PARAM uvOffsetToUse = program.env[0];\n" +
+"PARAM uvOffsets[20] = { program.env[1..20] };\n" +
+"\n" +
+"# Addresses\n" +
+"ADDRESS addr;\n" +
+"\n" +
+"# Per vertex inputs\n" +
+"ATTRIB iPos         = vertex.position;          #position\n" +
+"\n" +
+"# Outputs\n" +
+"OUTPUT oPos         = result.position;          #position\n" +
+"\n" +
+"# Transform vertex-position to clip-space\n" +
+"DP4 oPos.x, iPos, mvp[0];\n" +
+"DP4 oPos.y, iPos, mvp[1];\n" +
+"DP4 oPos.z, iPos, mvp[2];\n" +
+"DP4 oPos.w, iPos, mvp[3];\n" +
+"\n" +
+"# Read which set of offsets to use\n" +
+"ARL addr.x, uvOffsetToUse.x;\n" +
+"\n" +
+"#    c[CV_CONSTS_1] = c[28]\n" +
+"#    x = 0\n" +
+"#    y = 0.5\n" +
+"#    z = 1\n" +
+"#    w = 2.0f\n" +
+"\n" +
+"#    Put a scale factor into r0 so the sample points\n" +
+"#    can be moved farther from the texel being written\n" +
+"#    MOV R0, c[28].z;\n" +
+"\n" +
+"# Add the offsets to the input texture\n" +
+"# coordinate, creating 4 sets of independent\n" +
+"# texture coordinates.\n" +
+"ADD result.texcoord[0], uvOffsets[addr.x     ], vertex.texcoord[0];\n" +
+"ADD result.texcoord[1], uvOffsets[addr.x + 5 ], vertex.texcoord[0];\n" +
+"ADD result.texcoord[2], uvOffsets[addr.x + 10], vertex.texcoord[0];\n" +
+"ADD result.texcoord[3], uvOffsets[addr.x + 15], vertex.texcoord[0];\n" +
+"\n" +
+"END\n";
+
+    // set up constants (not currently used in the vertex program, though)
+    float[] rCVConsts = new float[] { 0, 0.5f, 1.0f, 2.0f };
+    gl.glProgramEnvParameter4fvARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_CONSTS_1, rCVConsts, 0);
+
+    loadProgram(gl, GL2.GL_VERTEX_PROGRAM_ARB, programBuffer);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // fragment program setup for equal weight combination of texels
+    ///////////////////////////////////////////////////////////////////////////
+    displayListIDs[CA_FRAGMENT_PROGRAM_EQ_WEIGHT_COMBINE] = gl.glGenLists(1);
+    initEqWeightCombine_PostMult(gl, displayListIDs[CA_FRAGMENT_PROGRAM_EQ_WEIGHT_COMBINE]);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // fragment program setup for computing force from neighbors (step 1)
+    ///////////////////////////////////////////////////////////////////////////
+    displayListIDs[CA_FRAGMENT_PROGRAM_NEIGHBOR_FORCE_CALC_1] = gl.glGenLists(1);
+    initNeighborForceCalcStep1(gl, displayListIDs[CA_FRAGMENT_PROGRAM_NEIGHBOR_FORCE_CALC_1]);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // fragment program setup for computing force from neighbors (step 2)
+    ///////////////////////////////////////////////////////////////////////////
+    displayListIDs[CA_FRAGMENT_PROGRAM_NEIGHBOR_FORCE_CALC_2] = gl.glGenLists(1);
+    initNeighborForceCalcStep2(gl, displayListIDs[CA_FRAGMENT_PROGRAM_NEIGHBOR_FORCE_CALC_2]);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // fragment program setup to apply force
+    ///////////////////////////////////////////////////////////////////////////
+    displayListIDs[CA_FRAGMENT_PROGRAM_APPLY_FORCE] = gl.glGenLists(1);
+    initApplyForce(gl, displayListIDs[CA_FRAGMENT_PROGRAM_APPLY_FORCE]);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // fragment program setup to apply velocity
+    ///////////////////////////////////////////////////////////////////////////
+    displayListIDs[CA_FRAGMENT_PROGRAM_APPLY_VELOCITY] = gl.glGenLists(1);
+    initApplyVelocity(gl, displayListIDs[CA_FRAGMENT_PROGRAM_APPLY_VELOCITY]);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // fragment program setup to create a normal map
+    ///////////////////////////////////////////////////////////////////////////
+    displayListIDs[CA_FRAGMENT_PROGRAM_CREATE_NORMAL_MAP] = gl.glGenLists(1);
+    initCreateNormalMap(gl, displayListIDs[CA_FRAGMENT_PROGRAM_CREATE_NORMAL_MAP]);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // fragment program setup for dot product reflection
+    ///////////////////////////////////////////////////////////////////////////
+    displayListIDs[CA_FRAGMENT_PROGRAM_REFLECT] = gl.glGenLists(1);
+    initDotProductReflect(gl, displayListIDs[CA_FRAGMENT_PROGRAM_REFLECT]);
+
+    ///////////////////////////////////////////////////////////////////////////
+    // display list to render a single screen space quad.
+    ///////////////////////////////////////////////////////////////////////////
+    displayListIDs[CA_DRAW_SCREEN_QUAD] = gl.glGenLists(1);
+    gl.glNewList(displayListIDs[CA_DRAW_SCREEN_QUAD], GL2.GL_COMPILE);
+    gl.glColor4f(1, 1, 1, 1);
+    gl.glBegin(GL2.GL_TRIANGLE_STRIP);
+    gl.glTexCoord2f(0, 1); gl.glVertex2f(-1,  1);
+    gl.glTexCoord2f(0, 0); gl.glVertex2f(-1, -1);
+    gl.glTexCoord2f(1, 1); gl.glVertex2f( 1,  1);
+    gl.glTexCoord2f(1, 0); gl.glVertex2f( 1, -1);
+    gl.glEnd();
+    gl.glEndList();
+  }
+
+  private void checkExtension(GL gl, String extensionName) {
+    if (!gl.isExtensionAvailable(extensionName)) {
+      throw new GLException("Unable to initialize " + extensionName + " OpenGL extension");
+    }
+  }
+
+  private void doSingleTimeStep(GL2 gl) {
+    int temp;
+
+    // Swap texture source & target indices & pointers
+    //  0 = start from initial loaded texture
+    //  1/2 = flip flop back and forth between targets & sources
+
+    switch (flipState) {
+    case 0:
+      texHeightInput    = dynamicTextures[CA_TEXTURE_HEIGHT_SOURCE].getTextureObject();    // initial height map.
+      texHeightOutput   = dynamicTextures[CA_TEXTURE_HEIGHT_TARGET].getTextureObject();    // next height map.
+
+      texVelocityInput  = dynamicTextures[CA_TEXTURE_VELOCITY_SOURCE].getTextureObject();  // initial velocity.
+      texVelocityOutput = dynamicTextures[CA_TEXTURE_VELOCITY_TARGET].getTextureObject();  // next velocity.
+
+      // Clear initial velocity texture to 0x80 == gray
+      gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
+      gl.glClear(GL2.GL_COLOR_BUFFER_BIT);
+
+      // Now we need to copy the resulting pixels into the intermediate force field texture
+      gl.glActiveTexture(GL2.GL_TEXTURE0);
+      gl.glBindTexture(GL2.GL_TEXTURE_2D, texVelocityInput);
+
+      // use CopyTexSubImage for speed (even though we copy all of it) since we pre-allocated the texture
+      gl.glCopyTexSubImage2D(GL2.GL_TEXTURE_2D, 0, 0, 0, 0, 0, initialMapDimensions[0], initialMapDimensions[1]);
+
+      break;  
+        
+    case 1:
+      temp              = texHeightInput;
+      texHeightInput    = texHeightOutput;
+      texHeightOutput   = temp;
+
+      temp              = texVelocityInput;
+      texVelocityInput  = texVelocityOutput;
+      texVelocityOutput = temp;
+
+      break;
+
+    case 2:
+      temp              = texHeightInput;
+      texHeightInput    = texHeightOutput;
+      texHeightOutput   = temp;
+
+      temp              = texVelocityInput;
+      texVelocityInput  = texVelocityOutput;
+      texVelocityOutput = temp;
+      break;
+    }
+	
+    // even if wireframe mode, render to texture as solid
+    gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_FILL);
+	
+    /////////////////////////////////////////////////////////////
+    //  Render first 3 components of force from three neighbors
+    //  Offsets selected are 1 center texel for center height
+    //    and 3 of the 4 nearest neighbors.  Texture selected
+    //    is same for all stages as we're turning height difference
+    //    of nearest neightbor texels into a force value.
+
+    gl.glCallList(displayListIDs[CA_FRAGMENT_PROGRAM_NEIGHBOR_FORCE_CALC_1]);
+
+    // set current source texture for stage 0 texture
+    for (int i = 0; i < 4; i++)
+      {
+        gl.glActiveTexture(GL2.GL_TEXTURE0 + i);
+        gl.glBindTexture(GL2.GL_TEXTURE_2D, texHeightInput);
+        gl.glEnable(GL2.GL_TEXTURE_2D);
+      }
+
+    int wrapMode = wrap ? GL2.GL_REPEAT : GL2.GL_CLAMP_TO_EDGE;
+    gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, wrapMode);
+    gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, wrapMode);
+
+    // disable blending
+    gl.glDisable(GL2.GL_BLEND);
+
+    // render using offset 1 (type 1 -- center + 3 of 4 nearest neighbors).
+    gl.glProgramEnvParameter4fARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_OFFSET_TO_USE, 1, 0, 0, 0);
+
+    // bind the vertex program to be used for this step and the next one.
+    gl.glBindProgramARB(GL2.GL_VERTEX_PROGRAM_ARB, vertexProgramID);
+    gl.glEnable(GL2.GL_VERTEX_PROGRAM_ARB);
+
+    // render a screen quad. with texture coords doing difference of nearby texels for force calc.
+    gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+
+    gl.glDisable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+
+    // Now we need to copy the resulting pixels into the intermediate force field texture
+    gl.glActiveTexture(GL2.GL_TEXTURE2);
+    dynamicTextures[CA_TEXTURE_FORCE_INTERMEDIATE].bind();    
+
+    // use CopyTexSubImage for speed (even though we copy all of it) since we pre-allocated the texture
+    gl.glCopyTexSubImage2D(GL2.GL_TEXTURE_2D, 0, 0, 0, 0, 0, initialMapDimensions[0], initialMapDimensions[1]);
+
+    ////////////////////////////////////////////////////////////////
+    // Now add in last component of force for the 4th neighbor
+    //  that we didn't have enough texture lookups to do in the 
+    //  first pass
+
+    gl.glCallList(displayListIDs[CA_FRAGMENT_PROGRAM_NEIGHBOR_FORCE_CALC_2]);
+    
+    // Cannot use additive blending as the force contribution might
+    //   be negative and would have to subtract from the dest.
+    // We must instead use an additional texture as target and read
+    //   the previous partial 3-neighbor result into the pixel shader
+    //   for possible subtraction
+
+    // Alphablend must be false
+
+    //; t0 = center  (same as last phase)
+    //; t1 = 2nd axis final point (same as last phase)
+    //; t2 = previous partial result texture sampled at center (result of last phase copied to texture)
+    //; t3 = not used (disable now)
+
+    gl.glTexParameterf(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, wrapMode);
+    gl.glTexParameterf(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, wrapMode);
+
+    gl.glActiveTexture(GL2.GL_TEXTURE3);
+    gl.glDisable(GL2.GL_TEXTURE_2D);
+
+    // vertex program already bound.
+    // render using offset 2 (type 2 -- final nearest neighbor plus center of previous result).
+    gl.glProgramEnvParameter4fARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_OFFSET_TO_USE, 2, 0, 0, 0);
+
+    // render a screen quad
+    gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+
+    gl.glDisable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+
+    // Now we need to copy the resulting pixels into the intermediate force field texture
+    gl.glActiveTexture(GL2.GL_TEXTURE1);
+    dynamicTextures[CA_TEXTURE_FORCE_TARGET].bind();
+
+    // use CopyTexSubImage for speed (even though we copy all of it) since we pre-allocated the texture
+    gl.glCopyTexSubImage2D(GL2.GL_TEXTURE_2D, 0, 0, 0, 0, 0, initialMapDimensions[0], initialMapDimensions[1]);
+
+    /////////////////////////////////////////////////////////////////
+    // Apply the force with a scale factor to reduce it's magnitude.
+    // Add this to the current texture representing the water height.
+    
+    gl.glCallList(displayListIDs[CA_FRAGMENT_PROGRAM_APPLY_FORCE]);
+
+    // use offsets of zero
+    gl.glProgramEnvParameter4fARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_OFFSET_TO_USE, 0, 0, 0, 0);
+
+    // bind the vertex program to be used for this step and the next one.
+
+    gl.glActiveTexture(GL2.GL_TEXTURE0);
+    gl.glBindTexture(GL2.GL_TEXTURE_2D, texVelocityInput);
+    gl.glActiveTexture(GL2.GL_TEXTURE1);
+    dynamicTextures[CA_TEXTURE_FORCE_TARGET].bind();    
+    gl.glActiveTexture(GL2.GL_TEXTURE2);
+    gl.glDisable(GL2.GL_TEXTURE_2D);
+    gl.glActiveTexture(GL2.GL_TEXTURE3);
+    gl.glDisable(GL2.GL_TEXTURE_2D);
+
+    // Draw the quad to add in force.
+    gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+
+    gl.glDisable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+
+    ///////////////////////////////////////////////////////////////////
+    // With velocity texture selected, render new excitation droplets
+    //   at random freq.
+
+    float randomFrequency = (float) Math.random();
+
+    if (dropletFrequency > randomFrequency) {
+      // a drop falls - decide where
+      Droplet drop = new Droplet(2 * ((float)Math.random() - 0.5f),
+                                 2 * ((float)Math.random() - 0.5f),
+                                 0.02f +  0.1f * ((float)Math.random()));
+      addDroplet(drop);
+    }
+
+    //  Now draw the droplets:
+    if (!droplets.isEmpty()) {
+      drawDroplets(gl);
+      droplets.clear();
+    }
+
+    // Now we need to copy the resulting pixels into the velocity texture
+    gl.glActiveTexture(GL2.GL_TEXTURE1);
+    gl.glBindTexture(GL2.GL_TEXTURE_2D, texVelocityOutput);
+
+    // use CopyTexSubImage for speed (even though we copy all of it) since we pre-allocated the texture
+    gl.glCopyTexSubImage2D(GL2.GL_TEXTURE_2D, 0, 0, 0, 0, 0, initialMapDimensions[0], initialMapDimensions[1]);
+
+    //////////////////////////////////////////////////////////////////////
+    // Apply velocity to position
+    gl.glCallList(displayListIDs[CA_FRAGMENT_PROGRAM_APPLY_VELOCITY]);
+    gl.glEnable(GL2.GL_VERTEX_PROGRAM_ARB);
+
+    gl.glActiveTexture(GL2.GL_TEXTURE0);
+    gl.glBindTexture(GL2.GL_TEXTURE_2D, texHeightInput);
+    gl.glActiveTexture(GL2.GL_TEXTURE1); // velocity output already bound
+    gl.glEnable(GL2.GL_TEXTURE_2D);
+
+    // use offsets of zero
+    gl.glProgramEnvParameter4fARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_OFFSET_TO_USE, 0, 0, 0, 0);
+
+    // Draw the quad to add in force.
+    gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+
+    gl.glDisable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+
+    // Now we need to copy the resulting pixels into the input height texture
+    gl.glActiveTexture(GL2.GL_TEXTURE0);
+    gl.glBindTexture(GL2.GL_TEXTURE_2D, texHeightInput);
+    
+    // use CopyTexSubImage for speed (even though we copy all of it) since we pre-allocated the texture
+    gl.glCopyTexSubImage2D(GL2.GL_TEXTURE_2D, 0, 0, 0, 0, 0, initialMapDimensions[0], initialMapDimensions[1]);
+
+    ///////////////////////////////////////////////////////////////////
+    //  blur positions to smooth noise & generaly dampen things
+    //  degree of blur is controlled by magnitude of 4 neighbor texel
+    //   offsets with bilinear on
+    
+    for (int i = 1; i < 4; i++) {
+      gl.glActiveTexture(GL2.GL_TEXTURE0 + i);
+      gl.glBindTexture(GL2.GL_TEXTURE_2D, texHeightInput);
+      gl.glEnable(GL2.GL_TEXTURE_2D);
+    }
+
+    // use offsets of 3
+    gl.glProgramEnvParameter4fARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_OFFSET_TO_USE, 3, 0, 0, 0);
+
+    gl.glCallList(displayListIDs[CA_FRAGMENT_PROGRAM_EQ_WEIGHT_COMBINE]);
+
+    gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+    gl.glDisable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+
+    // Draw the logo in the water.
+    if (applyInteriorBoundaries) {
+      gl.glDisable(GL2.GL_VERTEX_PROGRAM_ARB);
+      drawInteriorBoundaryObjects(gl);
+    }
+
+    // Now we need to copy the resulting pixels into the velocity texture
+    gl.glActiveTexture(GL2.GL_TEXTURE0);
+    gl.glBindTexture(GL2.GL_TEXTURE_2D, texHeightOutput);
+
+    // use CopyTexSubImage for speed (even though we copy all of it) since we pre-allocated the texture
+    gl.glCopyTexSubImage2D(GL2.GL_TEXTURE_2D, 0, 0, 0, 0, 0, initialMapDimensions[0], initialMapDimensions[1]);
+      
+    ///////////////////////////////////////////////////////////////////
+    // If selected, create a normal map from the height
+      
+    if (createNormalMap) {
+      createNormalMap(gl);
+    }
+      
+    ///////////////////////////////////////////////////////////
+    // Flip the state variable for the next round of rendering
+    switch (flipState) {
+    case 0:
+      flipState = 1;
+      break;
+    case 1:
+      flipState = 2;
+      break;
+    case 2:
+      flipState = 1;
+      break;
+    }
+  }
+
+  private void createNormalMap(GL2 gl) {
+    // use the height output on all four texture stages
+    for (int i = 0; i < 4; i++) {
+      gl.glActiveTexture(GL2.GL_TEXTURE0 + i);
+      gl.glBindTexture(GL2.GL_TEXTURE_2D, texHeightOutput);
+      gl.glEnable(GL2.GL_TEXTURE_2D);
+    }
+
+    // Set constants for red & green scale factors (also essential color masks)
+    // Red mask first
+    float[] pixMasks = new float[] { normalSTScale, 0.0f, 0.0f, 0.0f };
+
+    gl.glProgramEnvParameter4fvARB(GL2.GL_FRAGMENT_PROGRAM_ARB, 0, pixMasks, 0);
+
+    // Now green mask & scale:
+    pixMasks[0] = 0.0f;
+    pixMasks[1] = normalSTScale;
+    gl.glProgramEnvParameter4fvARB(GL2.GL_FRAGMENT_PROGRAM_ARB, 1, pixMasks, 0);
+
+    gl.glCallList(displayListIDs[CA_FRAGMENT_PROGRAM_CREATE_NORMAL_MAP]);
+
+    // set vp offsets to nearest neighbors
+    gl.glProgramEnvParameter4fARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_OFFSET_TO_USE, 4, 0, 0, 0);
+    gl.glEnable(GL2.GL_VERTEX_PROGRAM_ARB);
+    
+    gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+
+    gl.glDisable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+
+    // Now we need to copy the resulting pixels into the normal map
+    gl.glActiveTexture(GL2.GL_TEXTURE0);
+    dynamicTextures[CA_TEXTURE_NORMAL_MAP].bind();
+    
+    // use CopyTexSubImage for speed (even though we copy all of it) since we pre-allocated the texture
+    gl.glCopyTexSubImage2D(GL2.GL_TEXTURE_2D, 0, 0, 0, 0, 0, initialMapDimensions[0], initialMapDimensions[1]);
+  }
+
+  private void drawInteriorBoundaryObjects(GL2 gl) {
+    
+    gl.glActiveTexture(GL2.GL_TEXTURE0);
+    initialMapTex.bind();
+    initialMapTex.enable();
+
+    gl.glEnable(GL2.GL_ALPHA_TEST);
+
+    // disable other texture units.
+    for (int i = 1; i < 4; i++) {
+      gl.glActiveTexture(GL2.GL_TEXTURE0 + i);
+      gl.glDisable(GL2.GL_TEXTURE_2D);
+    }
+    
+    gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA);
+    gl.glEnable(GL2.GL_BLEND);
+
+    gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+
+    if (spinLogo) {
+      gl.glActiveTexture(GL2.GL_TEXTURE0);
+      spinTex.bind();
+      gl.glMatrixMode(GL2.GL_MODELVIEW);
+      gl.glPushMatrix();
+      gl.glRotatef(angle, 0, 0, 1);
+      angle += 1;
+
+      gl.glCallList(displayListIDs[CA_DRAW_SCREEN_QUAD]);
+
+      gl.glPopMatrix();
+    }
+
+    gl.glDisable(GL2.GL_ALPHA_TEST);
+    gl.glDisable(GL2.GL_BLEND);
+  }
+
+  private void loadTextures(GL gl,
+                            String spinFilename,
+                            String dropletFilename,
+                            String cubeMapFilenamePrefix,
+                            String cubeMapFilenameSuffix) throws IOException {
+    if (initialMapData == null) {
+      throw new GLException("Must call loadInitialTexture ahead of time");
+    }
+
+    initialMapTex = TextureIO.newTexture(initialMapData);
+    spinTex       = TextureIO.newTexture(getClass().getClassLoader().getResourceAsStream(spinFilename), false,
+                                         FileUtil.getFileSuffix(spinFilename));
+    dropletTex    = TextureIO.newTexture(getClass().getClassLoader().getResourceAsStream(dropletFilename), false,
+                                         FileUtil.getFileSuffix(dropletFilename));
+
+    // load the cubemap texture
+    cubemap = Cubemap.loadFromStreams(getClass().getClassLoader(),
+                                      cubeMapFilenamePrefix,
+                                      cubeMapFilenameSuffix,
+                                      true);
+
+    // now create dummy intermediate textures from the initial map texture
+    for (int i = 0; i < CA_NUM_DYNAMIC_TEXTURES; i++) {
+      dynamicTextures[i] = TextureIO.newTexture(initialMapData);
+    }
+
+    initialMapData = null;
+
+    texHeightInput    = initialMapTex.getTextureObject();                               // initial height map.
+    texHeightOutput   = dynamicTextures[CA_TEXTURE_HEIGHT_TARGET].getTextureObject();   // next height map.
+    
+    texVelocityInput  = dynamicTextures[CA_TEXTURE_VELOCITY_SOURCE].getTextureObject(); // initial velocity.
+    texVelocityOutput = dynamicTextures[CA_TEXTURE_VELOCITY_TARGET].getTextureObject(); // next velocity.
+  }
+
+  private void createAndWriteUVOffsets(GL2 gl, int width, int height) {
+    // This sets vertex shader constants used to displace the
+    //  source texture over several additive samples.  This is 
+    //  used to accumulate neighboring texel information that we
+    //  need to run the game - the 8 surrounding texels, and the 
+    //  single source texel which will either spawn or die in the 
+    //  next generation.
+    // Label the texels as follows, for a source texel "e" that
+    //  we want to compute for the next generation:
+    //
+    //          abc
+    //          def
+    //          ghi:
+
+    // first the easy one: no offsets for sampling center
+    //  occupied or unoccupied
+    // Use index offset value 0.0 to access these in the 
+    //  vertex shader.
+    
+    perTexelWidth  = 1.0f / width;
+    perTexelHeight = 1.0f / height;
+
+    // Offset set 0 : center texel sampling
+    float[] noOffsetX = new float[] { 0, 0, 0, 0 };
+    float[] noOffsetY = new float[] { 0, 0, 0, 0 };
+
+    // Offset set 1:  For use with neighbor force pixel shader 1
+    //  samples center with 0, +u, -u, and +v,
+    //  ie the 'e','d', 'f', and 'h' texels
+    float dist = 1.5f;
+    float[] type1OffsetX = new float[] { 0.0f, -dist * perTexelWidth,  dist * perTexelWidth,   dist * perTexelWidth  };
+    float[] type1OffsetY = new float[] { 0.0f,  dist * perTexelHeight, dist * perTexelHeight, -dist * perTexelHeight };
+
+    // Offset set 2:  for use with neighbor force pixel shader 2
+    //  samples center with 0, and -v texels 
+    //  ie the 'e' and 'b' texels
+    // This completes a pattern of sampling center texel and it's
+    //   4 nearest neighbors to run the height-based water simulation
+    // 3rd must be 0 0 to sample texel center from partial result
+    //   texture.
+
+    float[] type2OffsetX = new float[] { 0.0f, -dist * perTexelWidth,  0.0f, 0.0f   };
+    float[] type2OffsetY = new float[] { 0.0f, -dist * perTexelHeight, 0.0f, 0.0f   };
+        
+    // type 3 offsets
+    updateBlurVertOffset(gl);
+
+    /////////////////////////////////////////////////////////////
+    // Nearest neighbor offsets:
+
+    float[] type4OffsetX = new float[] { -perTexelWidth,   perTexelWidth,   0.0f,              0.0f   };
+    float[] type4OffsetY = new float[] { 0.0f,             0.0f,            -perTexelHeight,   perTexelHeight };
+
+    // write all these offsets to constant memory
+    for (int i = 0; i < 4; ++i) {
+      float noOffset[]    = { noOffsetX[i],    noOffsetY[i],    0.0f, 0.0f };
+      float type1Offset[] = { type1OffsetX[i], type1OffsetY[i], 0.0f, 0.0f };
+      float type2Offset[] = { type2OffsetX[i], type2OffsetY[i], 0.0f, 0.0f };
+      float type4Offset[] = { type4OffsetX[i], type4OffsetY[i], 0.0f, 0.0f };
+
+      gl.glProgramEnvParameter4fvARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_T0_NO_OFFSET + 5 * i, noOffset, 0);
+      gl.glProgramEnvParameter4fvARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_T0_TYPE1     + 5 * i, type1Offset, 0);
+      gl.glProgramEnvParameter4fvARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_T0_TYPE2     + 5 * i, type2Offset, 0);
+      gl.glProgramEnvParameter4fvARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_T0_TYPE4     + 5 * i, type4Offset, 0);
+    }
+  }
+
+  private void updateBlurVertOffset(GL2 gl) {
+    float[] type3OffsetX = new float[] { -perTexelWidth * 0.5f, 
+                                         perTexelWidth, 
+                                         perTexelWidth * 0.5f, 
+                                         -perTexelWidth 
+    };
+    float[] type3OffsetY = new float[] { perTexelHeight,
+                                         perTexelHeight * 0.5f,
+                                         -perTexelHeight,
+                                         -perTexelHeight * 0.5f 
+    };
+    float[] offsets = new float[] { 0, 0, 0, 0 };
+
+    for (int i = 0; i < 4; ++i) {
+      offsets[0] = blurDist * ( type3OffsetX[i]);
+      offsets[1] = blurDist * ( type3OffsetY[i]);
+      gl.glProgramEnvParameter4fvARB(GL2.GL_VERTEX_PROGRAM_ARB, CV_UV_T0_TYPE3 + 5 * i, offsets, 0);
+    }
+  }
+
+  private synchronized void drawDroplets(GL2 gl) {
+    gl.glDisable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+    gl.glDisable(GL2.GL_VERTEX_PROGRAM_ARB);
+
+    gl.glActiveTexture(GL2.GL_TEXTURE0);
+    dropletTex.bind();
+    dropletTex.enable();
+
+    gl.glActiveTexture(GL2.GL_TEXTURE1);
+    gl.glDisable(GL2.GL_TEXTURE_2D);
+
+    gl.glBlendFunc(GL2.GL_ONE, GL2.GL_ONE);
+    gl.glEnable(GL2.GL_BLEND);
+
+    gl.glBegin(GL2.GL_QUADS);
+    gl.glColor4f(1, 1, 1, 1);
+    for (Iterator iter = droplets.iterator(); iter.hasNext(); ) {
+      Droplet droplet = (Droplet) iter.next();
+      // coords in [-1,1] range
+
+      // Draw a single quad to the texture render target
+      // The quad is textured with the initial droplet texture, and
+      //   covers some small portion of the render target
+      // Draw the droplet
+       
+      gl.glTexCoord2f(0, 0); gl.glVertex2f(droplet.rX() - droplet.rScale(), droplet.rY() - droplet.rScale());
+      gl.glTexCoord2f(1, 0); gl.glVertex2f(droplet.rX() + droplet.rScale(), droplet.rY() - droplet.rScale());
+      gl.glTexCoord2f(1, 1); gl.glVertex2f(droplet.rX() + droplet.rScale(), droplet.rY() + droplet.rScale());
+      gl.glTexCoord2f(0, 1); gl.glVertex2f(droplet.rX() - droplet.rScale(), droplet.rY() + droplet.rScale());          
+    }
+    gl.glEnd();
+
+    gl.glDisable(GL2.GL_BLEND);
+  }
+
+  //----------------------------------------------------------------------
+  // Inlined register combiner and texture shader programs
+  // (don't want to port nvparse as it's a dead-end; we'll focus on Cg instead)
+
+  private void initEqWeightCombine_PostMult(GL2 gl, int displayListID) {
+    // Take samples of all four texture inputs and average them,
+    // adding on a bias
+    //
+    // Original register combiner program:
+    //
+    // Stage 0
+    // rgb
+    // {
+    //   discard = half_bias(tex0);
+    //   discard = half_bias(tex1);
+    //   spare0 = sum();
+    //   scale_by_one_half();
+    // }
+    // Stage 1
+    // rgb
+    // {
+    //   discard = half_bias(tex2);
+    //   discard = half_bias(tex3);
+    //   spare1 = sum();
+    //   scale_by_one_half();
+    // }
+    // Stage 2
+    // rgb
+    // {
+    //   discard = spare0;
+    //   discard = spare1;
+    //   spare0 = sum();
+    //   scale_by_one_half();
+    // }
+    // Stage 3
+    // rgb
+    // {
+    //   discard = const0;
+    //   discard = spare0;
+    //   spare0 = sum();
+    // }
+
+    float[] const0 = new float[] { 0.5f, 0.5f, 0.5f, 1.0f };
+
+    int[] tmpInt = new int[1];
+    gl.glGenProgramsARB(1, tmpInt, 0);
+    int fragProg = tmpInt[0];
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+
+    String program =
+"!!ARBfp1.0\n" +
+"PARAM const0  = program.env[0];\n" +
+"PARAM oneQtr  = { 0.25, 0.25, 0.25, 0.25 };\n" +
+"PARAM two     = { 2.0, 2.0, 2.0, 2.0 };\n" +
+"TEMP texSamp0, texSamp1, texSamp2, texSamp3;\n" +
+"TEMP spare0, spare1;\n" +
+"\n" +
+"TEX texSamp0, fragment.texcoord[0], texture[0], 2D;\n" +
+"TEX texSamp1, fragment.texcoord[1], texture[1], 2D;\n" +
+"TEX texSamp2, fragment.texcoord[2], texture[2], 2D;\n" +
+"TEX texSamp3, fragment.texcoord[3], texture[3], 2D;\n" +
+"ADD spare0, texSamp0, texSamp1;\n" +
+"ADD spare1, texSamp2, texSamp3;\n" +
+"ADD spare0, spare0, spare1;\n" +
+"SUB spare0, spare0, two;\n" +
+"MAD result.color, oneQtr, spare0, const0;\n" +
+"\n" +
+"END\n";
+
+    loadProgram(gl, GL2.GL_FRAGMENT_PROGRAM_ARB, program);
+
+    gl.glNewList(displayListID, GL2.GL_COMPILE);
+    gl.glProgramEnvParameter4fvARB(GL2.GL_FRAGMENT_PROGRAM_ARB, 0, const0, 0);
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+    gl.glEnable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+    gl.glEndList();
+  }
+
+  private void initNeighborForceCalcStep1(GL2 gl, int displayListID) {
+    // Step one in the nearest-neighbor force calculation for height-based water
+    // simulation.  NeighborForceCalc2 is the second step.
+    //
+    // This step takes the center point and three neighboring points, and computes
+    // the texel difference as the "force" acting to pull the center texel.
+    // 
+    // The amount to which the computed force is applied to the texel is controlled
+    // in a separate shader.
+
+    //  get colors from all 4 texture stages
+    //  tex0 = center texel
+    //  tex1 = 1st neighbor
+    //  tex2 = 2nd neighbor - same axis as 1st neighbor point
+    //       so force for that axis == t1 - t0 + t2 - t0
+    //  tex3 = 3rd neighbor on other axis
+
+    // Original register combiner program:
+    //
+    // Stage 0
+    // rgb
+    // {
+    //   //s0 = t1 - t0;
+    //   discard = -tex0;
+    //   discard = tex1;
+    //   spare0 = sum();  
+    // }
+    // Stage 1
+    // rgb
+    // {
+    //   //s1 = t2 - t0;
+    //   discard = -tex0;
+    //   discard = tex2;
+    //   spare1 = sum();  
+    // }
+    // Stage 2
+    // // 'force' for 1st axis
+    // rgb 
+    // {
+    //   //s0 = s0 + s1 = t1 - t0 + t2 - t0;
+    //   discard = spare0;
+    //   discard = spare1;
+    //   spare0 = sum();  
+    // }
+    // Stage 3
+    // // one more point for 2nd axis
+    // rgb
+    // {
+    //   //s1 = t3 - t0;
+    //   discard = -tex0;
+    //   discard = tex3;
+    //   spare1 = sum();  
+    // }
+    // Stage 4
+    // rgb
+    // {
+    //   //s0 = s0 + s1 = t3 - t0 + t2 - t0 + t1 - t0;
+    //   discard = spare0;
+    //   discard = spare1;
+    //   spare0 = sum();  
+    // }
+    // Stage 5
+    // // Now add in a force to gently pull the center texel's 
+    // //  value to 0.5.  The strength of this is controlled by
+    // //  the PCN_EQ_REST_FAC  - restoration factor
+    // // Without this, the simulation will fade to zero or fly
+    // //  away to saturate at 1.0
+    // rgb 
+    // {
+    //   //s1 = 0.5 - t0;  
+    //   discard = -tex0;
+    //   discard = const0;
+    //   spare1 = sum();  
+    // }
+    // Stage 6
+    // {
+    //   rgb
+    //   {
+    //     discard = spare1 * const0;
+    //     discard = spare0;
+    //     spare0 = sum();
+    //   }
+    // }
+    // Stage 7
+    // rgb
+    // {
+    //   discard = spare0;
+    //   discard = const0;
+    //   spare0 = sum();
+    // }
+
+    float[] const0 = new float[] { 0.5f, 0.5f, 0.5f, 1.0f };
+
+    int[] tmpInt = new int[1];
+    gl.glGenProgramsARB(1, tmpInt, 0);
+    int fragProg = tmpInt[0];
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+
+    String program =
+"!!ARBfp1.0\n" +
+"PARAM const0 = program.env[0];\n" +
+"PARAM three                 = {  3,     3,     3,    1.0 };\n" +
+"TEMP texSamp0, texSamp1, texSamp2, texSamp3;\n" +
+"TEMP spare0, spare1;\n" +
+"\n" +
+"TEX texSamp0, fragment.texcoord[0], texture[0], 2D;\n" +
+"TEX texSamp1, fragment.texcoord[1], texture[1], 2D;\n" +
+"TEX texSamp2, fragment.texcoord[2], texture[2], 2D;\n" +
+"TEX texSamp3, fragment.texcoord[3], texture[3], 2D;\n" +
+"ADD spare0, texSamp1, texSamp2;\n" +
+"MAD spare1, const0, const0, const0;\n" +
+"ADD spare0, texSamp3, spare0;\n" +
+"ADD spare0, spare1, spare0;\n" +
+"ADD spare1, three, const0;\n" +
+"MAD result.color, -spare1, texSamp0, spare0;\n" +
+
+// Faster version which hardcodes in value of const0:
+//"ADD spare0, texSamp1, texSamp2;\n" +
+//"ADD spare1, texSamp3, pointSevenFive;\n" +
+//"ADD spare0, spare0, spare1;\n" +
+//"MAD result.color, minusThreePointFive, texSamp0, spare0;\n" +
+
+// Straightforward port:
+//"SUB spare0, texSamp1, texSamp0;\n" +
+//"SUB spare1, texSamp2, texSamp0;\n" +
+//"ADD spare0, spare0, spare1;\n" +
+//"SUB spare1, texSamp3, texSamp0;\n" +
+//"ADD spare0, spare0, spare1;\n" +
+//"SUB spare1, const0, texSamp0;\n" +
+//"MAD spare0, const0, spare1, spare0;\n" +
+//"ADD result.color, spare0, const0;\n" +
+
+"\n" +
+"END\n";
+
+    loadProgram(gl, GL2.GL_FRAGMENT_PROGRAM_ARB, program);
+
+    gl.glNewList(displayListID, GL2.GL_COMPILE);
+    gl.glProgramEnvParameter4fvARB(GL2.GL_FRAGMENT_PROGRAM_ARB, 0, const0, 0);
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+    gl.glEnable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+    gl.glEndList();
+  }
+
+  private void initNeighborForceCalcStep2(GL2 gl, int displayListID) {
+    // 2nd step of force calc for render-to-texture
+    // water simulation.
+    //
+    // Adds the 4th & final neighbor point to the 
+    // force calc..
+    //
+    // Bias and scale the values so 0 force is 0.5, 
+    // full negative force is 0.0, and full pos is
+    // 1.0
+    //
+    // tex0    Center texel
+    // tex1    2nd axis neighbor point
+    // tex2    previous partial force amount
+    // Result from t1 - t0 is added to this t2
+    //  partial result & output
+
+    // Original register combiner program:
+    //
+    // Stage 0
+    // last element of neighbor force
+    // rgb
+    // {
+    //   discard = -tex0;
+    //   discard = tex1;
+    //   spare0 = sum();
+    // }
+    // Stage 1
+    // add with previous partial force amount
+    // rgb
+    // {
+    //   discard = spare0;
+    //   discard = tex2;
+    //   spare0 = sum();
+    // }
+
+    int[] tmpInt = new int[1];
+    gl.glGenProgramsARB(1, tmpInt, 0);
+    int fragProg = tmpInt[0];
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+
+    String program =
+"!!ARBfp1.0\n" +
+"PARAM const0 = program.env[0];\n" +
+"TEMP texSamp0, texSamp1, texSamp2;\n" +
+"TEMP spare0;\n" +
+"\n" +
+"TEX texSamp0, fragment.texcoord[0], texture[0], 2D;\n" +
+"TEX texSamp1, fragment.texcoord[1], texture[1], 2D;\n" +
+"TEX texSamp2, fragment.texcoord[2], texture[2], 2D;\n" +
+"SUB spare0, texSamp1, texSamp0;\n" +
+"ADD result.color, spare0, texSamp2;\n" +
+"\n" +
+"END\n";
+
+    loadProgram(gl, GL2.GL_FRAGMENT_PROGRAM_ARB, program);
+
+    gl.glNewList(displayListID, GL2.GL_COMPILE);
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+    gl.glEnable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+    gl.glEndList();
+  }
+
+  private void initApplyForce(GL2 gl, int displayListID) {
+    // This shader samples t1, biases its value to a signed number, and applies this
+    // value multiplied by a scale factor to the t0 sample.
+    //
+    // This is used to apply a "force" texture value to a "velocity" state texture
+    // for nearest-neighbor height-based water simulations.  The output pixel is
+    // the new "velocity" value to replace the t0 sample in rendering to a new 
+    // texture which will replace the texture selected into t0.
+    //
+    // A nearly identical shader using a different scaling constant is used to
+    // apply the "velocity" value to a "height" texture at each texel.
+    //
+    // t1 comes in the range [0,1] but needs to hold signed values, so a value of
+    // 0.5 in t1 represents zero force.  This is biased to a signed value in 
+    // computing the new velocity.
+    //
+    // tex0 = previous velocity
+    // tex1 = force
+    //
+    // Bias the force so that 0.5 input = no change in t0 value
+    //  and 0.0 input means -0.5 * scale change in t0 value
+    //
+    // New velocity = force * scale + previous velocity
+
+    // Original register combiner program:
+    //
+    // Stage 0
+    // rgb
+    // {
+    //   discard = expand(tex1) * const0;
+    //   discard = expand(tex0);
+    //   spare0 = sum();
+    //   scale_by_one_half();
+    // }
+    // Stage 1
+    // rgb
+    // {
+    //   discard = spare0;
+    //   discard = const1;
+    //   spare0 = sum();
+    // }
+
+    float[] const0 = new float[] { 0.25f, 0.25f, 0.25f, 1.0f };
+    float[] const1 = new float[] { 0.5f,  0.5f,  0.5f,  1.0f };
+
+    int[] tmpInt = new int[1];
+    gl.glGenProgramsARB(1, tmpInt, 0);
+    int fragProg = tmpInt[0];
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+
+    String program =
+"!!ARBfp1.0\n" +
+"PARAM const0 = program.env[0];\n" +
+"PARAM const1 = program.env[1];\n" +
+"PARAM one     = { 1.0, 1.0, 1.0, 0.0 };\n" +
+"PARAM oneHalf = { 0.5, 0.5, 0.5, 1.0 };\n" +
+"PARAM two     = { 2.0, 2.0, 2.0, 1.0 };\n" +
+"TEMP texSamp0, texSamp1;\n" +
+"TEMP spare0, spare1;\n" +
+"\n" +
+"TEX texSamp0, fragment.texcoord[0], texture[0], 2D;\n" +
+"TEX texSamp1, fragment.texcoord[1], texture[1], 2D;\n" +
+"MAD spare0, two, texSamp1, -one;\n" +
+"MAD spare1, two, texSamp0, -one;\n" +
+"MAD spare0, spare0, const0, spare1;\n" +
+"MAD result.color, oneHalf, spare0, const1;\n" +
+"\n" +
+"END\n";
+
+    loadProgram(gl, GL2.GL_FRAGMENT_PROGRAM_ARB, program);
+
+    gl.glNewList(displayListID, GL2.GL_COMPILE);
+    gl.glProgramEnvParameter4fvARB(GL2.GL_FRAGMENT_PROGRAM_ARB, 0, const0, 0);
+    gl.glProgramEnvParameter4fvARB(GL2.GL_FRAGMENT_PROGRAM_ARB, 1, const1, 0);
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+    gl.glEnable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+    gl.glEndList();
+  }
+
+  private void initApplyVelocity(GL2 gl, int displayListID) {
+    // This shader samples t1, biases its value to a signed number, and applies this
+    // value multiplied by a scale factor to the t0 sample.
+    //
+    // This is used to apply a "velocity" texture value to a "height" state texture
+    // for nearest-neighbor height-based water simulations.  The output pixel is
+    // the new "height" value to replace the t0 sample in rendering to a new 
+    // texture which will replace the texture selected into t0.
+    //
+    // A nearly identical shader using a different scaling constant is used to
+    // apply the "force" value to the "velocity" texture at each texel.
+    //
+    // t1 comes in the range [0,1] but needs to hold signed values, so a value of
+    // 0.5 in t1 represents zero velocity.  This is biased to a signed value in 
+    // computing the new position.                       
+    //
+    // tex0 = height field
+    // tex1 = velocity          
+    //
+    // Bias the force/velocity to a signed value so we can subtract from
+    //   the t0 position sample.
+    //
+    // New height = velocity * scale factor + old height
+
+    // Original register combiner program:
+    //
+    // Stage 0
+    // rgb
+    // {
+    //   discard = expand(tex1) * const0;
+    //   discard = expand(tex0);
+    //   spare0 = sum();
+    //   scale_by_one_half();
+    // }
+    // Stage 1
+    // rgb
+    // {
+    //   discard = spare0;
+    //   discard = const0;
+    //   spare0 = sum();
+    // }
+    // }
+
+    float[] const0 = new float[] { 0.5f,  0.5f,  0.5f,  1.0f };
+
+    int[] tmpInt = new int[1];
+    gl.glGenProgramsARB(1, tmpInt, 0);
+    int fragProg = tmpInt[0];
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+
+    String program =
+"!!ARBfp1.0\n" +
+"PARAM const0 = program.env[0];\n" +
+"PARAM one     = { 1.0, 1.0, 1.0, 0.0 };\n" +
+"PARAM oneHalf = { 0.5, 0.5, 0.5, 1.0 };\n" +
+"PARAM two     = { 2.0, 2.0, 2.0, 1.0 };\n" +
+"TEMP texSamp0, texSamp1;\n" +
+"TEMP spare0, spare1;\n" +
+"\n" +
+"TEX texSamp0, fragment.texcoord[0], texture[0], 2D;\n" +
+"TEX texSamp1, fragment.texcoord[1], texture[1], 2D;\n" +
+"MAD spare0, two, texSamp1, -one;\n" +
+"MAD spare1, two, texSamp0, -one;\n" +
+"MAD spare0, spare0, const0, spare1;\n" +
+"MAD result.color, oneHalf, spare0, const0;\n" +
+"\n" +
+"END\n";
+
+    loadProgram(gl, GL2.GL_FRAGMENT_PROGRAM_ARB, program);
+
+    gl.glNewList(displayListID, GL2.GL_COMPILE);
+    gl.glProgramEnvParameter4fvARB(GL2.GL_FRAGMENT_PROGRAM_ARB, 0, const0, 0);
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+    gl.glEnable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+    gl.glEndList();
+  }
+
+  private void initCreateNormalMap(GL2 gl, int displayListID) {
+    // Neighbor-differencing for RGB normal map creation.  Scale factors for s and t
+    // axis components are set in program code.
+    // This does a crude 1-s^2-t^2 calculation for the blue component in order to
+    // approximately normalize the RGB normal map vector.  For s^2+t^2 close to 1.0,
+    // this is a close approximation to blue = sqrt(1 - s^2 - t^2) which would give a
+    // normalized vector.
+    // An additional pass with a dependent texture lookup (alpha-red or green-blue)
+    // could be used to produce an exactly normalized normal.
+
+    // colors from all 4 texture stages
+    // tex0 = -s,  0
+    // tex1 = +s,  0
+    // tex2 =  0, +t
+    // tex3 =  0, -t
+
+    // Original register combiner program:
+    //
+    // Stage 0
+    // rgb
+    // {
+    //   // (t0 - t1)*4  : 4 for higher scale
+    //   discard = -tex1;
+    //   discard = tex0;
+    //   spare0 = sum();
+    //   scale_by_four();
+    // }
+    // Stage 1
+    // rgb
+    // {
+    //   // (t3 - t2)*4 : 4 for higher scale
+    //   discard = -tex2;
+    //   discard = tex3;
+    //   spare1 = sum();
+    //   scale_by_four();
+    // }
+    // Stage 2
+    // Define const0 in the third general combiner as RGBA = (scale, 0, 0, 0)
+    //  Where scale [0,1] is applied to reduce the magnitude
+    //  of the s axis component of the normal.
+    // Define const1 in the third combiner similarly to affect the t axis component
+    // define these by "ramboing" them in the C++ code that uses this combiner script.
+    // Note: these variables have been renamed to "redMask" and "greenMask" in
+    // the fragment program below.
+    // rgb
+    // {
+    //   // see comment about consts above!
+    //   // t0 = s result in red only
+    //   discard = spare0 * const0;
+    //   discard = spare1 * const1;
+    //   spare0 = sum();
+    // }
+    // Stage 3
+    // rgb
+    // {
+    //   tex1 = spare0 * spare0;
+    //   scale_by_two();
+    // }
+    // Stage 4
+    // const0 = (1, 1, 0, 0);
+    // rgb
+    // {
+    //   spare1 = unsigned_invert(tex1) . const0;
+    //   scale_by_one_half();
+    // }
+    // Stage 5
+    // const0 = (0.5, 0.5, 0, 0);
+    // rgb
+    // {
+    //   discard = spare0;
+    //   discard = const0;
+    //   spare0 = sum();
+    // }
+    // Stage 6
+    // const0 = (0, 0, 1, 1);
+    // rgb 
+    // {
+    //   discard = spare1 * const0;
+    //   discard = spare0;
+    //   spare0 = sum();
+    // }
+
+
+    float[] const0 = new float[] { 0.5f,  0.5f,  0.5f,  1.0f };
+
+    int[] tmpInt = new int[1];
+    gl.glGenProgramsARB(1, tmpInt, 0);
+    int fragProg = tmpInt[0];
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+
+    String program =
+"!!ARBfp1.0\n" +
+"PARAM redMask   = program.env[0];\n" +
+"PARAM greenMask = program.env[1];\n" +
+"PARAM const0    = { 1.0, 1.0, 0.0, 0.0 };\n" +
+"PARAM const1    = { 0.5, 0.5, 0.0, 0.0 };\n" +
+"PARAM const2    = { 0.0, 0.0, 1.0, 1.0 };\n" +
+"PARAM one     = { 1.0, 1.0, 1.0, 0.0 };\n" +
+"PARAM oneHalf = { 0.5, 0.5, 0.5, 1.0 };\n" +
+"PARAM two     = { 2.0, 2.0, 2.0, 1.0 };\n" +
+"PARAM four    = { 4.0, 4.0, 4.0, 1.0 };\n" +
+"TEMP texSamp0, texSamp1, texSamp2, texSamp3;\n" +
+"TEMP spare0, spare1, spare2;\n" +
+"\n" +
+"TEX texSamp0, fragment.texcoord[0], texture[0], 2D;\n" +
+"TEX texSamp1, fragment.texcoord[1], texture[1], 2D;\n" +
+"TEX texSamp2, fragment.texcoord[2], texture[2], 2D;\n" +
+"TEX texSamp3, fragment.texcoord[3], texture[3], 2D;\n" +
+"SUB spare0, texSamp0, texSamp1;\n" +
+"MUL spare0, spare0, four;\n" +
+"SUB spare1, texSamp3, texSamp2;\n" +
+"MUL spare1, spare1, four;\n" +
+"MUL spare0, spare0, redMask;\n" +
+"MAD spare0, greenMask, spare1, spare0;\n" +
+"MUL_SAT spare2, spare0, spare0;\n" +
+"SUB spare2, one, spare2;\n" +
+"DP3 spare1, spare2, const0;\n" +
+"ADD spare0, spare0, const1;\n" +
+"MAD result.color, const2, spare1, spare0;\n" +
+"\n" +
+"END\n";
+
+    loadProgram(gl, GL2.GL_FRAGMENT_PROGRAM_ARB, program);
+
+    gl.glNewList(displayListID, GL2.GL_COMPILE);
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+    gl.glEnable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+    gl.glEndList();
+  }
+
+  private void initDotProductReflect(GL2 gl, int displayListID) {
+    // Pseudocode for this operation, derived from the NVidia
+    // texture_shader.txt documentation at
+    // http://oss.sgi.com/projects/ogl-sample/registry/NV/texture_shader.txt
+
+    // TEX texSamp0, fragment.texcoord[0], texture[0], 2D;
+    // MAD texSamp0, two, texSamp0, minusOne;
+    // TEMP dotPP = texSamp0 . texcoord[1];
+    // TEMP dotP  = texSamp0 . texcoord[2];
+    // TEMP dotC  = texSamp0 . texcoord[3];
+    // TEMP R, N, E;
+    // N = [dotPP, dotP, dotC];
+    // ooNLength = N dot N;
+    // RCP ooNLength, ooNLength;
+    // E = [texcoord[1].w, texcoord[2].w, texcoord[3].w];
+    // nDotE = N dot E;
+    // MUL R, nDotE, N;
+    // MUL R, R, two;
+    // MUL R, R, ooNLength;
+    // SUB R, R, E;
+    // TEX result.color, R, texture[3], CUBE;
+
+    // This fragment program is pretty length-sensitive; making it too
+    // big causes the frame rate to be cut in half on my machine
+    // (Quadro FX Go700) due to sync-to-vertical-refresh. The program
+    // below is more optimized in its use of temporaries. Some of the
+    // scaling operations on the first component of the normal vector
+    // (before subtracting off the E vector) don't appear to make much
+    // of a visual difference so they are skipped as well.
+
+    int[] tmpInt = new int[1];
+    gl.glGenProgramsARB(1, tmpInt, 0);
+    int fragProg = tmpInt[0];
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+
+    String program =
+"!!ARBfp1.0\n" +
+"PARAM minusOne = { -1.0, -1.0, -1.0, 0.0 };\n" +
+"PARAM two      = {  2.0,  2.0,  2.0, 0.0 };\n" +
+"TEMP texSamp0, R, N, E;\n" +
+"\n" +
+"TEX texSamp0, fragment.texcoord[0], texture[0], 2D;\n" +
+"MAD texSamp0, two, texSamp0, minusOne;\n" +
+"DP3 N.x,   texSamp0, fragment.texcoord[1];\n" +
+"DP3 N.y,   texSamp0, fragment.texcoord[2];\n" +
+"DP3 N.z,   texSamp0, fragment.texcoord[3];\n" +
+"MOV E.x, fragment.texcoord[1].w;\n" +
+"MOV E.y, fragment.texcoord[2].w;\n" +
+"MOV E.z, fragment.texcoord[3].w;\n" +
+"MUL N, N, two;\n" +
+"SUB R, N, E;\n" +
+"TEX result.color, R, texture[3], CUBE;\n" +
+"\n" +
+"END";
+
+    loadProgram(gl, GL2.GL_FRAGMENT_PROGRAM_ARB, program);
+
+    gl.glNewList(displayListID, GL2.GL_COMPILE);
+    gl.glBindProgramARB(GL2.GL_FRAGMENT_PROGRAM_ARB, fragProg);
+    gl.glEnable(GL2.GL_FRAGMENT_PROGRAM_ARB);
+    gl.glEndList();
+  }
+
+  private void loadProgram(GL2 gl,
+                           int target,
+                           String programBuffer) {
+
+    gl.glProgramStringARB(target, GL2.GL_PROGRAM_FORMAT_ASCII_ARB, programBuffer.length(), programBuffer);
+
+    int[] errPos = new int[1];
+    gl.glGetIntegerv(GL2.GL_PROGRAM_ERROR_POSITION_ARB, errPos, 0);
+    if (errPos[0] >= 0) {
+      String kind = "Program";
+      if (target == GL2.GL_VERTEX_PROGRAM_ARB) {
+        kind = "Vertex program";
+      } else if (target == GL2.GL_FRAGMENT_PROGRAM_ARB) {
+        kind = "Fragment program";
+      }
+      System.out.println(kind + " failed to load:");
+      String errMsg = gl.glGetString(GL2.GL_PROGRAM_ERROR_STRING_ARB);
+      if (errMsg == null) {
+        System.out.println("[No error message available]");
+      } else {
+        System.out.println("Error message: \"" + errMsg + "\"");
+      }
+      System.out.println("Error occurred at position " + errPos[0] + " in program:");
+      int endPos = errPos[0];
+      while (endPos < programBuffer.length() && programBuffer.charAt(endPos) != '\n') {
+        ++endPos;
+      }
+      System.out.println(programBuffer.substring(errPos[0], endPos));
+      throw new GLException("Error loading " + kind);
+    } else {
+      if (target == GL2.GL_FRAGMENT_PROGRAM_ARB) {
+        int[] isNative = new int[1];
+        gl.glGetProgramivARB(GL2.GL_FRAGMENT_PROGRAM_ARB,
+                             GL2.GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB,
+                             isNative, 0);
+        if (isNative[0] != 1) {
+          System.out.println("WARNING: fragment program is over native resource limits");
+          Thread.dumpStack();
+        }
+      }
+    }
+  }
+}
-- 
cgit v1.2.3