/*
 * 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.vertexProgWarp;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.nio.*;
import java.util.*;
import javax.swing.*;

import net.java.games.jogl.*;
import demos.util.*;
import gleem.*;
import gleem.linalg.*;

/**
   Simple space-warp/distortion vertex program demo<br>
   (Press the space bar to switch through programs)<br><p>

   sgreen@nvidia.com 9/2000, based on Cass's vtxprog_silhouette<br><p>

        Ported to Java by Kenneth Russell
*/

public class VertexProgWarp {
  private GLCanvas canvas;
  private Frame    frame;
  private Animator animator;
  private volatile boolean quit;

  private DurationTimer timer = new DurationTimer();
  private boolean  firstRender = true;
  private int      frameCount;

  public static void main(String[] args) {
    new VertexProgWarp().run(args);
  }

  public void run(String[] args) {
    canvas = GLDrawableFactory.getFactory().createGLCanvas(new GLCapabilities());
    canvas.addGLEventListener(new Listener());

    animator = new Animator(canvas);

    frame = new Frame();
    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) {
          animator.stop();
          System.exit(0);
        }
      });

    animator.start();
  }

  class Listener implements GLEventListener {
    // period of 4-term Taylor approximation to sin isn't quite 2*M_PI
    private static final float    SIN_PERIOD   = 3.079f;
    private static final int      NUM_OBJS     = 5;
    private static final int      NUM_PROGS    = 7;
    private              int[]    programs     = new int[NUM_PROGS];
    private float zNear = 0.1f;
    private float zFar  = 10.0f;
    private int program = 2;
    private int obj = 2;
    private boolean[] b = new boolean[256];
    private boolean wire = false;
    private boolean toggleWire = false;
    private boolean animating = true;
    private boolean doViewAll = true;

    private Time  time = new SystemTime();
    private float anim = 0.0f;
    private float animScale = 7.0f;
    private float amp  = 0.05f;
    private float freq = 8.0f;
    private float d    = 4.0f;

    private ExaminerViewer viewer;

    public void init(GLDrawable drawable) {
      GL gl = drawable.getGL();
      GLU glu = drawable.getGLU();

      float cc = 0.0f;
      gl.glClearColor(cc, cc, cc, 1);

      gl.glColor3f(1,1,1);
      gl.glEnable(GL.GL_DEPTH_TEST);
      gl.glDisable(GL.GL_CULL_FACE);

      try {
        initExtension(gl, "GL_NV_vertex_program");
      } catch (RuntimeException e) {
        runExit();
        throw(e);
      }

      for(int i=0; i<NUM_OBJS; i++) {
        gl.glNewList(i+1, GL.GL_COMPILE);
        drawObject(gl, glu, i);
        gl.glEndList();
      }    

      for(int i=0; i<NUM_PROGS; i++) {
        int[] vtxProgTmp = new int[1];
        gl.glGenProgramsNV(1, vtxProgTmp);
        programs[i] = vtxProgTmp[0];
        gl.glBindProgramNV(GL.GL_VERTEX_PROGRAM_NV, programs[i]);
        gl.glLoadProgramNV(GL.GL_VERTEX_PROGRAM_NV, programs[i], programTexts[i].length(), programTexts[i]);
      }

      gl.glTrackMatrixNV(GL.GL_VERTEX_PROGRAM_NV, 0, GL.GL_MODELVIEW_PROJECTION_NV, GL.GL_IDENTITY_NV);
      gl.glTrackMatrixNV(GL.GL_VERTEX_PROGRAM_NV, 4,               GL.GL_MODELVIEW, GL.GL_INVERSE_TRANSPOSE_NV);
      gl.glTrackMatrixNV(GL.GL_VERTEX_PROGRAM_NV, 8,               GL.GL_MODELVIEW, GL.GL_IDENTITY_NV);
      gl.glTrackMatrixNV(GL.GL_VERTEX_PROGRAM_NV, 12,             GL.GL_PROJECTION, GL.GL_IDENTITY_NV);

      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 32, 0.0f, 0.0f, 1.0f, 0.0f);   // light position/direction
      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 35, 0.0f, 1.0f, 0.0f, 0.0f);   // diffuse color
      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 36, 1.0f, 1.0f, 1.0f, 0.0f);   // specular color

      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 64, 0.0f, 1.0f, 2.0f, 3.0f);   // smoothstep constants

      // sin Taylor series constants - 1, 1/3!, 1/5!, 1/7!
      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 63, 1.0f, 1.0f / (3*2), 1.0f / (5*4*3*2), 1.0f / (7*6*5*4*3*2));

      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 62, 1.0f / (2.0f * SIN_PERIOD), 2.0f * SIN_PERIOD, SIN_PERIOD, SIN_PERIOD/2.0f);

      // sin wave frequency, amplitude
      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 61, 1.0f, 0.2f, 0.0f, 0.0f);

      // phase animation
      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 60, 0.0f, 0.0f, 0.0f, 0.0f);

      // fisheye sphere radius
      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 59, 1.0f, 0.0f, 0.0f, 0.0f);

      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 58, 0.0f, 0.0f, -2.0f / (zFar - zNear), -(zFar+zNear)/(zFar-zNear) );
      setWindowTitle();

      b['p'] = true;
      
      drawable.addKeyListener(new KeyAdapter() {
          public void keyPressed(KeyEvent e) {
            dispatchKey(e.getKeyCode(), e.getKeyChar());
          }
        });

      // Register the window with the ManipManager
      ManipManager manager = ManipManager.getManipManager();
      manager.registerWindow(drawable);

      viewer = new ExaminerViewer(MouseButtonHelper.numMouseButtons());
      viewer.setNoAltKeyMode(true);
      viewer.attach(drawable, new BSphereProvider() {
	  public BSphere getBoundingSphere() {
	    return new BSphere(new Vec3f(0, 0, 0), 1.0f);
	  }
	});
      viewer.setVertFOV((float) Math.toRadians(60));
      viewer.setZNear(zNear);
      viewer.setZFar(zFar);
    }

    public void display(GLDrawable drawable) {
      if (!firstRender) {
        if (++frameCount == 30) {
          timer.stop();
          System.err.println("Frames per second: " + (30.0f / timer.getDurationAsSeconds()));
          timer.reset();
          timer.start();
          frameCount = 0;
        }
      } else {
        firstRender = false;
        timer.start();
      }

      time.update();

      GL gl = drawable.getGL();
      GLU glu = drawable.getGLU();

      gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

      if (toggleWire) {
        wire = !wire;
        if (wire)
          gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
        else
          gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
        toggleWire = false;
      }

      gl.glPushMatrix();

      if (doViewAll) {
        viewer.viewAll(gl);
        doViewAll = false;
      }

      if (animating) {
        anim -= (float) (animScale * time.deltaT());
      }

      viewer.update(gl);
      ManipManager.getManipManager().updateCameraParameters(drawable, viewer.getCameraParameters());
      ManipManager.getManipManager().render(drawable, gl);

      gl.glBindProgramNV(GL.GL_VERTEX_PROGRAM_NV, programs[program]);
      gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 60, anim, 0.0f, 0.0f, 0.0f);

      if (program==6)
        gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 61, (float) Math.sin(anim)*amp*50.0f, 0.0f, 0.0f, 0.0f);
      else
        gl.glProgramParameter4fNV(GL.GL_VERTEX_PROGRAM_NV, 61, freq, amp, d, d+1);

      if (b['p'])
        gl.glEnable(GL.GL_VERTEX_PROGRAM_NV);

      gl.glDisable(GL.GL_TEXTURE_2D);
      gl.glCallList(obj+1);

      gl.glDisable(GL.GL_VERTEX_PROGRAM_NV);

      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 initExtension(GL gl, String glExtensionName) {
      if (!gl.isExtensionAvailable(glExtensionName)) {
        throw new RuntimeException("OpenGL extension \"" + glExtensionName + "\" not available");
      }
    }

    private void dispatchKey(int keyCode, char k) {
      if (k < 256)
        b[k] = !b[k];

      switch (keyCode) {
        case KeyEvent.VK_HOME:
        case KeyEvent.VK_R:
          anim = 0.0f;
          amp = 0.05f;
          freq = 8.0f;
          d = 4.0f;
          doViewAll = true;
          break;

        case KeyEvent.VK_LEFT:
        case KeyEvent.VK_KP_LEFT:
          program--;
          if (program < 0)
            program = NUM_PROGS-1;
          setWindowTitle();
          break;

        case KeyEvent.VK_RIGHT:
        case KeyEvent.VK_KP_RIGHT:
          program = (program + 1) % NUM_PROGS;
          setWindowTitle();
          break;

        case KeyEvent.VK_F1:
        case KeyEvent.VK_H:
          String endl = System.getProperty("line.separator");
          endl = endl + endl;
          String msg = ("F1/h - Help" + endl +
                        "Home - Reset" + endl +
                        "Left Button & Mouse - Rotate viewpoint" + endl +
                        "1..5 - Switch object (Sphere, Torus, Triceratop, Cube, Cylinder)" + endl +
                        "- / + - Change amplitude" + endl +
                        "[ / ] - Change frequency" + endl +
                        ", / . - Change square fisheye size" + endl +
                        "Left - Next vertex program" + endl +
                        "Right - Previous vertex program" + endl +
                        "W - Toggle wireframe" + endl +
                        "Space - Toggle animation" + endl +
                        "Esc/q - Exit program" + endl);
          JOptionPane.showMessageDialog(null, msg, "Help", JOptionPane.INFORMATION_MESSAGE);
          break;

        case KeyEvent.VK_ESCAPE:
        case KeyEvent.VK_Q:
          runExit();
          return;

        case KeyEvent.VK_W:
          toggleWire = true;
          break;

        case KeyEvent.VK_EQUALS:
        case KeyEvent.VK_PLUS:
          amp += 0.01;
          break;

        case KeyEvent.VK_MINUS:
          amp -= 0.01;
          break;

        case KeyEvent.VK_CLOSE_BRACKET:
          freq += 0.5;
          break;

        case KeyEvent.VK_OPEN_BRACKET:
          freq -= 0.5;
          break;

        case KeyEvent.VK_PERIOD:
          d += 0.1;
          break;

        case KeyEvent.VK_COMMA:
          d -= 0.1;
          break;

        case KeyEvent.VK_SPACE:
          // Could also start/stop Animator here
          animating = !animating;
          break;

        case KeyEvent.VK_1:
          obj = 0;
          break;

        case KeyEvent.VK_2:
          obj = 1;
          break;

        case KeyEvent.VK_3:
          obj = 2;
          break;

        case KeyEvent.VK_4:
          obj = 3;
          break;

        case KeyEvent.VK_5:
          obj = 4;
          break;
      }
    }

    private void setWindowTitle() {
      frame.setTitle("SpaceWarp - " + programNames[program]);
    }

    private void drawObject(GL gl, GLU glu, int which) {
      switch(which) {
	case 0:
          drawSphere(gl, 0.5f, 100, 100);
          break;

	case 1:
          drawTorus(gl, 0.25f, 0.5f, 100, 100);
          break;

	case 2:
          try {
            Triceratops.drawObject(gl);
          } catch (IOException e) {
            runExit();
            throw new RuntimeException(e);
          }
          break;

	case 3:
          drawCube(gl);
          break;

	case 4:
          drawCylinder(gl, glu);
          break;
      }
    }

    private void drawSphere(GL gl, float radius, int slices, int stacks) {
      int J = stacks;
      int I = slices;
      for(int j = 0; j < J; j++) {
        float v = j/(float) J;
        float phi = (float) (v * 2 * Math.PI);
        float v2 = (j+1)/(float) J;
        float phi2 = (float) (v2 * 2 * Math.PI);

        gl.glBegin(GL.GL_QUAD_STRIP);
        for(int i = 0; i < I; i++) {	
          float u = i/(I-1.0f);
          float theta = (float) (u * Math.PI);
          float x,y,z,nx,ny,nz;

          nx = (float) (Math.cos(theta)*Math.cos(phi));
          ny = (float) (Math.sin(theta)*Math.cos(phi));
          nz = (float) (Math.sin(phi));
          x = radius * nx;
          y = radius * ny;
          z = radius * nz;

          gl.glColor3f ( u,  v, 0.0f);
          gl.glNormal3f(nx, ny, nz);
          gl.glVertex3f( x,  y, z);

          nx = (float) (Math.cos(theta)*Math.cos(phi2));
          ny = (float) (Math.sin(theta)*Math.cos(phi2));
          nz = (float) (Math.sin(phi2));
          x = radius * nx;
          y = radius * ny;
          z = radius * nz;

          gl.glColor3f ( u,  v+(1.0f/(J-1.0f)), 0.0f);
          gl.glNormal3f(nx,                 ny,   nz);
          gl.glVertex3f( x,                  y,    z);
        }
        gl.glEnd();
      }
    }

    private void drawTorus(GL gl, float meridian_radius, float core_radius, 
                           int meridian_slices, int core_slices) {
      int J = meridian_slices;
      int I = core_slices;
      for(int j = 0; j < J-1; j++) {
        float v = j/(J-1.0f);
        float rho = (float) (v * 2.0f * Math.PI);
        float v2 = (j+1)/(J-1.0f);
        float rho2 = (float) (v2 * 2.0f * Math.PI);
        gl.glBegin(GL.GL_QUAD_STRIP);
        for(int i = 0; i < I; i++) {	
          float u = i/(I-1.0f);
          float theta = (float) (u * 2.0f * Math.PI);
          float x,y,z,nx,ny,nz;

          x  = (float) (core_radius*Math.cos(theta) + meridian_radius*Math.cos(theta)*Math.cos(rho));
          y  = (float) (core_radius*Math.sin(theta) + meridian_radius*Math.sin(theta)*Math.cos(rho));
          z  = (float) (meridian_radius*Math.sin(rho));
          nx = (float) (Math.cos(theta)*Math.cos(rho));
          ny = (float) (Math.sin(theta)*Math.cos(rho));
          nz = (float) (Math.sin(rho));	

          gl.glColor3f ( u,  v, 0.0f);
          gl.glNormal3f(nx, ny, nz);
          gl.glVertex3f( x,  y,  z);

          x  = (float) (core_radius*Math.cos(theta) + meridian_radius*Math.cos(theta)*Math.cos(rho2));
          y  = (float) (core_radius*Math.sin(theta) + meridian_radius*Math.sin(theta)*Math.cos(rho2));
          z  = (float) (meridian_radius*Math.sin(rho2));
          nx = (float) (Math.cos(theta)*Math.cos(rho2));
          ny = (float) (Math.sin(theta)*Math.cos(rho2));
          nz = (float) (Math.sin(rho2));	

          gl.glColor3f ( u,  v, 0.0f);
          gl.glNormal3f(nx, ny, nz);
          gl.glVertex3f( x,  y,  z);
        }
        gl.glEnd();
      }
    }

    private void drawCube(GL gl) {
      int cr = 40;
      float scaleFactor = 0.5f;

      // back
      gl.glColor3f(1.0f, 0.0f, 0.0f);
      gl.glNormal3f(0.0f, 0.0f, -1.0f);
      drawGrid(gl, cr, cr, scaleFactor, -1.0f, -1.0f, -1.0f, 2.0f, 0.0f, 0.0f, 0.0f, 2.0f, 0.0f);

      // front
      gl.glColor3f(1.0f, 0.0f, 0.0f);
      gl.glNormal3f(0.0f, 0.0f, 1.0f);
      drawGrid(gl, cr, cr, scaleFactor, -1.0f, -1.0f, 1.0f, 2.0f, 0.0f, 0.0f, 0.0f, 2.0f, 0.0f);

      // left
      gl.glColor3f(0.0f, 1.0f, 0.0f);
      gl.glNormal3f(-1.0f, 0.0f, 0.0f);
      drawGrid(gl, cr, cr, scaleFactor, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 2.0f, 0.0f, 2.0f, 0.0f);

      // right
      gl.glColor3f(0.0f, 0.0f, 1.0f);
      gl.glNormal3f(1.0f, 0.0f, 0.0f);
      drawGrid(gl, cr, cr, scaleFactor, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 2.0f, 0.0f, 2.0f, 0.0f);

      // bottom
      gl.glColor3f(1.0f, 1.0f, 0.0f);
      gl.glNormal3f(0.0f,-1.0f, 0.0f);
      drawGrid(gl, cr, cr, scaleFactor, -1.0f, -1.0f, -1.0f, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f);

      // top
      gl.glColor3f(0.0f, 1.0f, 1.0f);
      gl.glNormal3f(0.0f, 1.0f, 0.0f);
      drawGrid(gl, cr, cr, scaleFactor, -1.0f, 1.0f, -1.0f, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f);
    }

    private void drawGrid(GL gl, int rows, int cols,
			  float scaleFactor,
                          float sx, float sy, float sz,
                          float ux, float uy, float uz,
                          float vx, float vy, float vz) {
      int x, y;

      for(y=0; y<rows; y++) {
        gl.glBegin(GL.GL_QUAD_STRIP);
        for(x=0; x<=cols; x++) {
          float u = x / (float) cols;
          float v = y / (float) rows;
          float v2 = v + (1.0f / (float) rows);

          gl.glTexCoord2f(u, v);
          gl.glVertex3f(scaleFactor * (sx + (u*ux) + (v*vx)),
			scaleFactor * (sy + (u*uy) + (v*vy)),
			scaleFactor * (sz + (u*uz) + (v*vz)));

          gl.glTexCoord2f(u, v2);
          gl.glVertex3f(scaleFactor * (sx + (u*ux) + (v2*vx)),
			scaleFactor * (sy + (u*uy) + (v2*vy)),
			scaleFactor * (sz + (u*uz) + (v2*vz)));
        }
        gl.glEnd();
      }
    }

    private void drawCylinder(GL gl, GLU glu) {
      GLUquadric quad;

      quad = glu.gluNewQuadric();
      glu.gluQuadricDrawStyle  (quad, GLU.GLU_FILL);
      glu.gluQuadricOrientation(quad, GLU.GLU_OUTSIDE);
      glu.gluQuadricNormals    (quad, GLU.GLU_SMOOTH);
      glu.gluQuadricTexture    (quad, true);

      gl.glMatrixMode(GL.GL_MODELVIEW);
      gl.glPushMatrix();
      gl.glTranslatef(-1.0f, 0.0f, 0.0f);
      gl.glRotatef   (90.0f, 0.0f, 1.0f, 0.0f);

      glu.gluCylinder(quad, 0.25f, 0.25f, 2.0f, 60, 30);
      gl.glPopMatrix();

      glu.gluDeleteQuadric(quad);
    }
  }

  private static final String[] programNames = new String[] {
    "Normal",
    "Pulsate",
    "Wave",
    "Square fisheye",
    "Spherical fisheye",
    "Ripple",
    "Twist"
  };

  private static final String[] programTexts = new String[] {
    //
    // Transform with diffuse lighting
    //
    "!!VP1.0\n" +
    "#Simple transform and diffuse lighting\n" +
    "\n" +
    "DP4   o[HPOS].x, c[0], v[OPOS] ;   # object x MVP -> clip\n" +
    "DP4   o[HPOS].y, c[1], v[OPOS] ;\n" +
    "DP4   o[HPOS].z, c[2], v[OPOS] ;\n" +
    "DP4   o[HPOS].w, c[3], v[OPOS] ;\n" +
    "\n" +
    "DP3   R1.x, c[4], v[NRML] ;        # normal x MV-1T -> lighting normal\n" +
    "DP3   R1.y, c[5], v[NRML] ;\n" +
    "DP3   R1.z, c[6], v[NRML] ;\n" +
    "\n" +
    "DP3   R0, c[32], R1 ;              # L.N\n" +
    "MUL   o[COL0].xyz, R0, c[35] ;     # col = L.N * diffuse\n" +
    "MOV   o[TEX0], v[TEX0];\n" +
    "END",

    //
    // Pulsate
    //
    "!!VP1.0\n" +
    "#Displace geometry along normal based on sine function of distance from origin\n" +
    "#(in object space)\n" +
    "#c[61].x = wave frequency\n" +
    "#c[61].y = wave amplitude\n" +
    "#c[62]   = PI constants\n" +
    "#c[63]   = Taylor series constants (see below)\n" +
    "\n" +
    "MOV   R0, v[OPOS]; \n" +
    "\n" +
    "#calculate distance from (0, 0, 0)\n" +
    "DP3   R3.x, R0, R0;\n" +
    "RSQ   R3.x, R3.x;\n" +
    "RCP   R3.x, R3.x;\n" +
    "\n" +
    "MUL   R3.x, R3.x, c[61].x; # wave frequency\n" +
    "ADD   R3.x, R3.x, c[60].x; # phase animation\n" +
    "\n" +
    "#reduce to period of 2*PI\n" +
    "MUL   R2, R3.x, c[62].x;\n" +
    "EXP   R4, R2.x;            # R4.y = R2.x - floor(R2.x)\n" +
    "MUL   R3.x, R4.y, c[62].y;\n" +
    "\n" +
    "# offset to -PI - PI\n" +
    "ADD   R3.x, R3.x, -c[62].z;\n" +
    "\n" +
    "#Sine approximation using Taylor series (accurate between -PI and PI) :\n" +
    "#sin(x)  = x - (x^3)/3! + (x^5)/5! - (x^7)/7! + ...\n" +
    "#sin(x) ~= x*(1 - (x^2)*(1/3! - (x^2)(1/5! - (x^2)/7! )))\n" +
    "#        = x * (a - y*(b - y*(c - y*d)))\n" +
    "#where\n" +
    "#a = 1.0    c[63].x\n" +
    "#b = 1/3!   c[63].y\n" +
    "#c = 1/5!   c[63].z\n" +
    "#d = 1/7!   c[63].w\n" +
    "#y = x^2    R2\n" +
    "\n" +
    "#R1.x = sin(R3.x);\n" +
    "\n" +
    "MUL   R2, R3.x, R3.x;\n" +
    "MAD   R1, -R2, c[63].w, c[63].z;\n" +
    "MAD   R1, R1, -R2, c[63].y;\n" +
    "MAD   R1, R1, -R2, c[63].x;\n" +
    "MUL   R1, R1, R3.x;\n" +
    "\n" +
    "#displace vertex along normal\n" +
    "MUL   R1.x, R1.x, c[61].y;\n" +
    "MAX   R1.x, R1.x, c[64].x;     # r1.x = max(r1.x, 0.0);\n" +
    "MUL   R2.xyz, v[NRML], R1.x;\n" +
    "ADD   R0.xyz, R0, R2;\n" +
    "\n" +
    "#simple lighting\n" +
    "DP3   R1.x, c[4], v[NRML] ;    # normal x MV-1T -> lighting normal\n" +
    "DP3   R1.y, c[5], v[NRML] ;\n" +
    "DP3   R1.z, c[6], v[NRML] ;\n" +
    "\n" +
    "DP3   R2, c[32], R1 ;          # light position DOT normal\n" +
    "MUL   o[COL0].xyz, R2, c[35] ; # col = ldotn * diffuse\n" +
    "\n" +
    "MOV   o[TEX0], v[TEX0];\n" +
    "\n" +
    "DP4   o[HPOS].x, c[0], R0 ;    # object x MVP -> clip\n" +
    "DP4   o[HPOS].y, c[1], R0 ;\n" +
    "DP4   o[HPOS].z, c[2], R0 ;\n" +
    "DP4   o[HPOS].w, c[3], R0 ;\n" +
    "\n" +
    "END",

    //
    // Wave
    //
    "!!VP1.0\n" +
    "# Perturb vertices in clip space with sine wave\n" +
    "# x += sin((y*freq)+anim) * amp\n" +
    "DP4   R0.x, c[0], v[OPOS] ;\n" +
    "DP4   R0.y, c[1], v[OPOS] ;\n" +
    "DP4   R0.z, c[2], v[OPOS] ;\n" +
    "DP4   R0.w, c[3], v[OPOS] ;\n" +
    "\n" +
    "MUL   R3.x, R0.y, c[61].x;    # wave frequency\n" +
    "ADD   R3.x, R3.x, c[60].x;    # phase animation\n" +
    "\n" +
    "# reduce to period of 2*PI\n" +
    "MUL   R2, R3.x, c[62].x;\n" +
    "EXP   R4, R2.x;               # R4.y = R2.x - floor(R2.x)\n" +
    "MUL   R3.x, R4.y, c[62].y;\n" +
    "\n" +
    "# offset to -PI - PI\n" +
    "ADD   R3.x, R3.x, -c[62].z;\n" +
    "\n" +
    "# R1.x = sin(R3.x);\n" +
    "MUL   R2,   R3.x, R3.x;\n" +
    "MAD   R1, -R2, c[63].w, c[63].z;\n" +
    "MAD   R1, R1, -R2, c[63].y;\n" +
    "MAD   R1, R1, -R2, c[63].x;\n" +
    "MUL   R1, R1, R3.x;\n" +
    "\n" +
    "MAD   R0.x, R1.x, c[61].y, R0.x;\n" +
    "\n" +
    "# simple lighting\n" +
    "DP3   R1.x, c[4], v[NRML] ;    # normal x MV-1T -> lighting normal\n" +
    "DP3   R1.y, c[5], v[NRML] ;\n" +
    "DP3   R1.z, c[6], v[NRML] ;\n" +
    "DP3   R2, c[32], R1 ;          # light position DOT normal\n" +
    "MUL   o[COL0].xyz, R2, c[35] ; # col = ldotn * diffuse\n" +
    "MOV   o[TEX0], v[TEX0];\n" +
    "\n" +
    "MOV   o[HPOS], R0;\n" +
    "\n" +
    "END",

    //
    // Fisheye
    //
    "!!VP1.0\n" +
    "#Fisheye distortion based on function:\n" +
    "#f(x)=(d+1)/(d+(1/x))\n" +
    "#maps the [0,1] interval monotonically onto [0,1]\n" +
    "\n" +
    "#c[61].z = d\n" +
    "#c[61].w = d+1\n" +
    "\n" +
    "DP4   R0.x, c[0], v[OPOS] ;\n" +
    "DP4   R0.y, c[1], v[OPOS] ;\n" +
    "DP4   R0.z, c[2], v[OPOS] ;\n" +
    "DP4   R0.w, c[3], v[OPOS] ;\n" +
    "\n" +
    "# do perspective divide\n" +
    "RCP   R1, R0.w;\n" +
    "MUL   R0, R0, R1.w;\n" +
    "\n" +
    "MAX   R1, R0, -R0;            # r1 = abs(r0)\n" +
    "\n" +
    "SLT   R2, R0, c[64].x;        # r2 = (r0 < 0.0) ? 1.0 : 0.0\n" +
    "SGE   R3, R0, c[64].x;        # r3 = (r0 >= 0.0) ? 1.0 : 0.0\n" +
    "\n" +
    "# distort x\n" +
    "# h(x)=(d+1)/(d+(1/x))\n" +
    "RCP   R1.x, R1.x;             # r1 = 1 / r1\n" +
    "ADD   R1.x, R1.x, c[61].z;    # r1 += d\n" +
    "RCP   R1.x, R1.x;             # r1 = 1 / r1\n" +
    "MUL   R1.x, R1.x, c[61].w;    # r1 *= d + 1\n" +
    "\n" +
    "# distort y\n" +
    "RCP   R1.y, R1.y;             # r1 = 1 / r1\n" +
    "ADD   R1.y, R1.y, c[61].z;    # r1 += d\n" +
    "RCP   R1.y, R1.y;             # r1 = 1 / r1\n" +
    "MUL   R1.y, R1.y, c[61].w;    # r1 *= d + 1\n" +
    "\n" +
    "# handle negative cases\n" +
    "MUL   R4.xy, R1, R3;          # r4 = r1 * r3\n" +
    "MAD   R1.xy, R1, -R2, R4;     # r1 = r1 * -r2 + r4\n" +
    "\n" +
    "# simple lighting\n" +
    "DP3   R2.x, c[4], v[NRML] ;   # normal x MV-1T -> lighting normal\n" +
    "DP3   R2.y, c[5], v[NRML] ;\n" +
    "DP3   R2.z, c[6], v[NRML] ;\n" +
    "DP3   R3, c[32], R2 ;         # light position DOT normal\n" +
    "MUL   o[COL0].xyz, R3, c[35] ; # col = ldotn * diffuse\n" +
    "\n" +
    "MOV   o[TEX0], v[TEX0];\n" +
    "\n" +
    "MOV   o[HPOS], R1;\n" +
    "\n" +
    "END",

    //
    // Spherize
    //
    "!!VP1.0\n" +
    "# Spherical fish-eye distortion\n" +
    "# in clip space\n" +
    "DP4   R0.x, c[0], v[OPOS];\n" +
    "DP4   R0.y, c[1], v[OPOS];\n" +
    "DP4   R0.z, c[2], v[OPOS];\n" +
    "DP4   R0.w, c[3], v[OPOS];\n" +
    "\n" +
    "# do perspective divide\n" +
    "RCP   R1.x, R0.w;\n" +
    "MUL   R2, R0, R1.x;\n" +
    "\n" +
    "# calculate distance from centre\n" +
    "MUL   R1.x, R2.x, R2.x;\n" +
    "MAD   R1.x, R2.y, R2.y, R1.x;\n" +
    "RSQ   R1.x, R1.x; # r1.x = 1 / sqrt(x*x+y*y)\n" +
    "\n" +
    "# calculate r3 = normalized direction vector\n" +
    "MUL   R3.xy, R0, R1.x;\n" +
    "\n" +
    "RCP   R1.x, R1.x;             # r1.x = actual distance\n" +
    "MIN   R1.x, R1.x, c[64].y;    # r1.x = min(r1.x, 1.0)\n" +
    "\n" +
    "# remap based on: f(x) = sqrt(1-x^2)\n" +
    "ADD   R1.x, c[64].y, -R1.x;\n" +
    "MAD   R1.x, -R1.x, R1.x, c[64].y;\n" +
    "RSQ   R1.x, R1.x;\n" +
    "RCP   R1.x, R1.x;\n" +
    "\n" +
    "# move vertex to new distance from centre\n" +
    "MUL   R0.xy, R3, R1.x;\n" +
    "\n" +
    "# simple lighting\n" +
    "DP3   R2.x, c[4], v[NRML];    # normal x MV-1T -> lighting normal\n" +
    "DP3   R2.y, c[5], v[NRML];\n" +
    "DP3   R2.z, c[6], v[NRML];\n" +
    "DP3   R3, c[32], R2 ;         # light position DOT normal\n" +
    "MUL   o[COL0].xyz, R3, c[35] ; # col = ldotn * diffuse\n" +
    "\n" +
    "MOV   o[TEX0], v[TEX0];\n" +
    "\n" +
    "MOV   o[HPOS], R0;\n" +
    "\n" +
    "END",

    //
    // Ripple
    //
    "!!VP1.0\n" +
    "# Ripple distortion\n" +
    "DP4   R0.x, c[0], v[OPOS];\n" +
    "DP4   R0.y, c[1], v[OPOS];\n" +
    "DP4   R0.z, c[2], v[OPOS];\n" +
    "DP4   R0.w, c[3], v[OPOS];\n" +
    "\n" +
    "# do perspective divide\n" +
    "RCP   R1.x, R0.w;\n" +
    "MUL   R4, R0, R1.x;\n" +
    "\n" +
    "# calculate distance from centre\n" +
    "MUL   R1.x, R4.x, R4.x;\n" +
    "MAD   R1.x, R4.y, R4.y, R1.x;\n" +
    "RSQ   R1.x, R1.x; " +
    "\n" +
    "RCP   R1.x, R1.x; " +
    "\n" +
    "MUL   R1.x, R1.x, c[61].x;    # wave frequency\n" +
    "ADD   R1.x, R1.x, c[60].x;    # phase animation\n" +
    "\n" +
    "# reduce to period of 2*PI\n" +
    "MUL   R2, R1.x, c[62].x;      # R2 = R1 / 2.0 * PI\n" +
    "EXP   R4, R2.x;               # R4.y = R2.x - floor(R2.x)\n" +
    "MUL   R1.x, R4.y, c[62].y;\n" +
    "\n" +
    "# offset to -PI - PI\n" +
    "ADD   R1.x, R1.x, -c[62].z;\n" +
    "\n" +
    "# R3.x = sin(R1.x)\n" +
    "MUL   R2, R1.x, R1.x;\n" +
    "MAD   R3, -R2, c[63].w, c[63].z;\n" +
    "MAD   R3, R3, -R2, c[63].y;\n" +
    "MAD   R3, R3, -R2, c[63].x;\n" +
    "MUL   R3, R3, R1.x;\n" +
    "\n" +
    "MUL   R3.x, R3.x, c[61].y;\n" +
    "\n" +
    "# move vertex towards centre based on distance\n" +
    "MAD   R0.xy, R0, -R3.x, R0;\n" +
    "\n" +
    "# lighting\n" +
    "DP3   R2.x, c[4], v[NRML];     # normal x MV-1T -> lighting normal\n" +
    "DP3   R2.y, c[5], v[NRML];\n" +
    "DP3   R2.z, c[6], v[NRML];\n" +
    "DP3   R3, c[32], R2;           # light position DOT normal\n" +
    "MUL   o[COL0].xyz, R3, c[35];  # col = ldotn * diffuse\n" +
    "\n" +
    "MOV   o[TEX0], v[TEX0];\n" +
    "\n" +
    "MOV   o[HPOS], R0;\n" +
    "\n" +
    "END",

    //
    // Twist
    //
    "!!VP1.0 # Twist\n" +
    "MOV   R0, v[OPOS];\n" +
    "\n" +
    "MUL   R1.x, R0.x, c[61].x;        # frequency\n" +
    "\n" +
    "# calculate sin(angle) and cos(angle)\n" +
    "ADD   R1.y, R1.x, -c[62].w;       # R1.y = R1.x + PI/2.0\n" +
    "\n" +
    "# reduce to period of 2*PI\n" +
    "MUL   R2, R1, c[62].x;            # R2 = R1 / 2.0 * PI\n" +
    "EXP   R3.y, R2.x;                 # R2.y = R2.x - floor(R2.x)\n" +
    "MOV   R3.x, R3.y;\n" +
    "EXP   R3.y, R2.y;                 # R2.y = R2.x - floor(R2.x)\n" +
    "MAD   R2, R3, c[62].y, -c[62].z;  # R2 = (R3 * 2.0*PI) - M_PI\n" +
    "\n" +
    "# R4.x = sin(R2.x);\n" +
    "# R4.y = cos(R2.y);\n" +
    "# parallel taylor series\n" +
    "MUL   R3,   R2, R2;\n" +
    "MAD   R4, -R3, c[63].w, c[63].z;\n" +
    "MAD   R4, R4, -R3, c[63].y;\n" +
    "MAD   R4, R4, -R3, c[63].x;\n" +
    "MUL   R4, R4, R2;\n" +
    "\n" +
    "# x    y    z    w\n" +
    "# R:\n" +
    "# 1    0    0    0\n" +
    "# 0    c   -s    0\n" +
    "# 0    s    c    0\n" +
    "# 0    0    0    1\n" +
    "\n" +
    "# c = cos(a)\n" +
    "# s = sin(a)\n" +
    "\n" +
    "# calculate rotation around X\n" +
    "MOV   R1, R0;\n" +
    "\n" +
    "MUL   R1.y, R0.y, R4.y;\n" +
    "MAD   R1.y, R0.z, -R4.x, R1.y;    # ny = y*cos(a) - z*sin(a)\n" +
    "\n" +
    "MUL   R1.z, R0.y, R4.x;\n" +
    "MAD   R1.z, R0.z, R4.y, R1.z;     # nz = y*sin(a) + z*cos(a)\n" +
    "\n" +
    "DP4   o[HPOS].x, c[0], R1;        # object x MVP -> clip\n" +
    "DP4   o[HPOS].y, c[1], R1;\n" +
    "DP4   o[HPOS].z, c[2], R1;\n" +
    "DP4   o[HPOS].w, c[3], R1;\n" +
    "\n" +
    "# rotate normal\n" +
    "MOV   R2, v[NRML];\n" +
    "MUL   R2.y, v[NRML].y, R4.y;\n" +
    "MAD   R2.y, v[NRML].z, -R4.x, R2.y;   # ny = y*cos(a) - z*sin(a)\n" +
    "\n" +
    "MUL   R2.z, v[NRML].y, R4.x;\n" +
    "MAD   R2.z, v[NRML].z, R4.y, R2.z;    # nz = y*sin(a) + z*cos(a)\n" +
    "\n" +
    "# diffuse lighting\n" +
    "DP3   R1.x, c[4], R2;             # normal x MV-1T -> lighting normal\n" +
    "DP3   R1.y, c[5], R2;\n" +
    "DP3   R1.z, c[6], R2;\n" +
    "\n" +
    "DP3   R3, c[32], R1;              # light position DOT normal\n" +
    "MUL   o[COL0].xyz, R3, c[35];     # col = ldotn * diffuse\n" +
    "\n" +
    "MOV   o[TEX0], v[TEX0];\n" +
    "\n" +
    "END"
  };

  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();
  }
}