/*
 * VisRenderer.java
 *
 * Copyright 2005-2006                                
 * Performance Research Laboratory, University of Oregon
 */
package edu.uoregon.tau.vis;

import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;
import javax.swing.JCheckBox;

import com.jogamp.common.nio.Buffers;

/**
 * This object manages the JOGL interface.
 *    
 * <P>CVS $Id: VisRenderer.java,v 1.11 2009/08/20 23:11:24 amorris Exp $</P>
 * @author	Alan Morris
 * @version	$Revision: 1.11 $
 */
public class VisRenderer implements GLEventListener, MouseListener, MouseMotionListener, MouseWheelListener {

	static class Vec {
		float x;
		float y;
		float z;
		float w;

		/**
		 * Constructor with float parameters
		 * 
		 * @param x
		 *            value in the x direction
		 * @param y
		 *            value in the y direction
		 * @param z
		 *            value in the z direction
		 */
		public Vec(float x, float y, float z) {
			this.x = x;
			this.y = y;
			this.z = z;
			this.w = 1;
		}

		/**
		 * Constructor with double parameters
		 * 
		 * @param x
		 *            value in the x direction
		 * @param y
		 *            value in the y direction
		 * @param z
		 *            value in the z direction
		 */
		public Vec(double x, double y, double z) {
			this.x = (float) x;
			this.y = (float) y;
			this.z = (float) z;
			this.w = 1;
		}

		/**
		 * Copy constructor
		 * 
		 * @param rhs
		 *            Vec to copy
		 */
		public Vec(Vec rhs) {
			this.x = rhs.x;
			this.y = rhs.y;
			this.z = rhs.z;
			this.w = rhs.w;
		}

		/**
		 * returns this Vec's x value
		 * 
		 * @return this Vec's x value
		 */
		public float x() {
			return x;
		}

		/**
		 * returns this Vec's y value
		 * 
		 * @return this Vec's y value
		 */
		public float y() {
			return y;
		}

		/**
		 * returns this Vec's z value
		 * 
		 * @return this Vec's z value
		 */
		public float z() {
			return z;
		}

		/**
		 * returns this Vec's w value
		 * 
		 * @return this Vec's w value
		 */
		public float w() {
			return w;
		}

		/**
		 * Sets this Vec's x value
		 * 
		 * @param x
		 */
		public void setx(double x) {
			this.x = (float) x;
		}

		/**
		 * Sets this Vec's y value
		 * 
		 * @param y
		 */
		public void sety(double y) {
			this.y = (float) y;
		}

		/**
		 * Sets this Vec's z value
		 * 
		 * @param z
		 */
		public void setz(double z) {
			this.z = (float) z;
		}

		/**
		 * Sets this Vec's w value
		 * 
		 * @param w
		 */
		public void setw(double w) {
			this.w = (float) w;
		}

		/**
		 * Sets this Vec's x value
		 * 
		 * @param x
		 */
		public void setx(float x) {
			this.x = x;
		}

		/**
		 * Sets this Vec's y value
		 * 
		 * @param y
		 */
		public void sety(float y) {
			this.y = y;
		}

		/**
		 * Sets this Vec's z value
		 * 
		 * @param z
		 */
		public void setz(float z) {
			this.z = z;
		}

		/**
		 * Sets this Vec's w value
		 * 
		 * @param w
		 */
		public void setw(float w) {
			this.w = w;
		}

		public void scale(float x, float y, float z) {
			this.x *= x;
			this.y *= y;
			this.z *= z;
		}

		public void scale(float value) {
			this.x *= value;
			this.y *= value;
			this.z *= value;
		}

		/**
		 * Adds two Vecs together. Vec adding is commutative
		 * 
		 * @param a
		 *            the first Vec
		 * @param b
		 *            the second Vec
		 * @return the sum of the two Vecs
		 */
		public static Vec add(Vec a, Vec b) {
			return new Vec(a.x() + b.x(), a.y() + b.y(), a.z() + b.z());
		}

		/**
		 * Returns the length of the Vec
		 * 
		 * @return the length of the Vec
		 */
		public double length() {
			double l2 = (x * x) + (y * y) + (z * z);
			double l = Math.sqrt(l2);
			return l;
		}

		/**
		 * Normalizes this Vec
		 */
		public void normalize() {
			double l2 = (x * x) + (y * y) + (z * z);
			double l = Math.sqrt(l2);
			x = (float) (x / l);
			y = (float) (y / l);
			z = (float) (z / l);
		}

		/**
		 * Adds this Vec and another together and returns the result
		 * 
		 * @param rhs
		 *            the second Vec
		 * @return the result
		 */
		public Vec add(Vec rhs) {
			return new Vec(this.x + rhs.x, this.y + rhs.y, this.z + rhs.z);
		}

		/**
		 * Subtracts a Vec from this one.
		 * 
		 * @param rhs
		 *            the second Vec
		 * @return the result
		 */
		public Vec subtract(Vec rhs) {
			return new Vec(this.x - rhs.x, this.y - rhs.y, this.z - rhs.z);
		}

		/**
		 * Computes the cross product of two Vecs
		 * 
		 * @param rhs
		 *            the second Vec
		 * @return the result
		 */
		public Vec cross(Vec rhs) {
			return new Vec((this.y * rhs.z) - (this.z * rhs.y),
					(this.z * rhs.x) - (this.x * rhs.z), (this.x * rhs.y)
							- (this.y * rhs.x));
		}

		/**
		 * Returns a string representation of the Vec (for debug purposes)
		 */
		public String toString() {
			return "(" + x + ", " + y + ", " + z + ")";
		}

	}

	static class Matrix {

		private double[][] matrix = new double[4][4];

		/**
		 * Class constructor. This matrix will be initialized to the identity
		 * matrix
		 */
		public Matrix() {
			// initialize to the identity matrix;
			setIdentity();
		}

		/**
		 * Sets this matrix to the identity matrix.
		 */
		public void setIdentity() {
			matrix[0][0] = 1;
			matrix[0][1] = 0;
			matrix[0][2] = 0;
			matrix[0][3] = 0;
			matrix[1][0] = 0;
			matrix[1][1] = 1;
			matrix[1][2] = 0;
			matrix[1][3] = 0;
			matrix[2][0] = 0;
			matrix[2][1] = 0;
			matrix[2][2] = 1;
			matrix[2][3] = 0;
			matrix[3][0] = 0;
			matrix[3][1] = 0;
			matrix[3][2] = 0;
			matrix[3][3] = 1;
		}

		/**
		 * Sets this matrix to a translation matrix
		 * 
		 * @param x
		 *            the amount in the x direction
		 * @param y
		 *            the amount in the y direction
		 * @param z
		 *            the amount in the z direction
		 */
		public void setToTranslate(double x, double y, double z) {
			setIdentity();
			matrix[0][3] = x;
			matrix[1][3] = y;
			matrix[2][3] = z;
		}

		/**
		 * Sets this matrix to an orthogonal rotation matrix
		 * 
		 * @param u
		 *            the 'u' vector
		 * @param v
		 *            the 'v' vector
		 * @param n
		 *            the 'n' vector
		 */
		public void setOrthRotate(Vec u, Vec v, Vec n) {
			matrix[0][0] = u.x();
			matrix[0][1] = u.y();
			matrix[0][2] = u.z();
			matrix[0][3] = 0;

			matrix[1][0] = v.x();
			matrix[1][1] = v.y();
			matrix[1][2] = v.z();
			matrix[1][3] = 0;

			matrix[2][0] = n.x();
			matrix[2][1] = n.y();
			matrix[2][2] = n.z();
			matrix[2][3] = 0;

			matrix[3][0] = 0;
			matrix[3][1] = 0;
			matrix[3][2] = 0;
			matrix[3][3] = 1;
		}

		/**
		 * Transposes this matrix
		 */
		public void transpose() {
			// create a new matrix to place transposed values
			Matrix m = new Matrix();
			for (int i = 0; i < 4; i++) {
				for (int j = 0; j < 4; j++) {
					m.matrix[i][j] = matrix[j][i];
				}
			}

			// now copy back
			for (int i = 0; i < 4; i++) {
				for (int j = 0; j < 4; j++) {
					matrix[i][j] = m.matrix[i][j];
				}
			}
		}

		/**
		 * Transforms a given vector with this matrix
		 * 
		 * @param v
		 *            the vector to transform
		 * @return the transformed vector
		 */
		public Vec transform(Vec v) {
			Vec out = new Vec(0.0, 0.0, 0.0);
			out.setx((v.x() * matrix[0][0]) + (v.y() * matrix[0][1])
					+ (v.z() * matrix[0][2]) + (v.w() * matrix[0][3]));
			out.sety((v.x() * matrix[1][0]) + (v.y() * matrix[1][1])
					+ (v.z() * matrix[1][2]) + (v.w() * matrix[1][3]));
			out.setz((v.x() * matrix[2][0]) + (v.y() * matrix[2][1])
					+ (v.z() * matrix[2][2]) + (v.w() * matrix[2][3]));
			out.setw((v.x() * matrix[3][0]) + (v.y() * matrix[3][1])
					+ (v.z() * matrix[3][2]) + (v.w() * matrix[3][3]));

			return out;
		}

		/**
		 * Creates a rotation matrix around the X axis
		 * 
		 * @param theta
		 *            the angle to rotate
		 * @return the transformation matrix
		 */
		public static Matrix createRotateX(double theta) {
			Matrix m = new Matrix();
			m.setRotateX(theta);
			return m;
		}

		/**
		 * Creates a rotation matrix around the Y axis
		 * 
		 * @param theta
		 *            the angle to rotate
		 * @return the transformation matrix
		 */
		public static Matrix createRotateY(double theta) {
			Matrix m = new Matrix();
			m.setRotateY(theta);
			return m;
		}

		/**
		 * Creates a rotation matrix around the Z axis
		 * 
		 * @param theta
		 *            the angle to rotate
		 * @return the transformation matrix
		 */
		public static Matrix createRotateZ(double theta) {
			Matrix m = new Matrix();
			m.setRotateZ(theta);
			return m;
		}

		/**
		 * Sets the transformation matrix to a rotation about the X
		 * 
		 * @param theta
		 *            the angle to rotate
		 */
		public void setRotateX(double theta) {
			matrix[0][0] = 1;
			matrix[0][1] = 0;
			matrix[0][2] = 0;
			matrix[0][3] = 0;
			matrix[1][0] = 0;
			matrix[1][1] = Math.cos(theta);
			matrix[1][2] = -Math.sin(theta);
			matrix[1][3] = 0;
			matrix[2][0] = 0;
			matrix[2][1] = Math.sin(theta);
			matrix[2][2] = Math.cos(theta);
			matrix[2][3] = 0;
			matrix[3][0] = 0;
			matrix[3][1] = 0;
			matrix[3][2] = 0;
			matrix[3][3] = 1;
		}

		/**
		 * Sets the transformation matrix to a rotation about the Y
		 * 
		 * @param theta
		 *            the angle to rotate
		 */
		public void setRotateY(double theta) {
			matrix[0][0] = Math.cos(theta);
			matrix[0][1] = 0;
			matrix[0][2] = Math.sin(theta);
			matrix[0][3] = 0;
			matrix[1][0] = 0;
			matrix[1][1] = 1;
			matrix[1][2] = 0;
			matrix[1][3] = 0;
			matrix[2][0] = -Math.sin(theta);
			matrix[2][1] = 0;
			matrix[2][2] = Math.cos(theta);
			matrix[2][3] = 0;
			matrix[3][0] = 0;
			matrix[3][1] = 0;
			matrix[3][2] = 0;
			matrix[3][3] = 1;
		}

		/**
		 * Sets the transformation matrix to a rotation about the Z
		 * 
		 * @param theta
		 *            the angle to rotate
		 */
		public void setRotateZ(double theta) {
			matrix[0][0] = Math.cos(theta);
			matrix[0][1] = -Math.sin(theta);
			matrix[0][2] = 0;
			matrix[0][3] = 0;
			matrix[1][0] = Math.sin(theta);
			matrix[1][1] = Math.cos(theta);
			matrix[1][2] = 0;
			matrix[1][3] = 0;
			matrix[2][0] = 0;
			matrix[2][1] = 0;
			matrix[2][2] = 1;
			matrix[2][3] = 0;
			matrix[3][0] = 0;
			matrix[3][1] = 0;
			matrix[3][2] = 0;
			matrix[3][3] = 1;
		}

		/**
		 * Multiply two matrices (this and rhs) and return the result. C = this
		 * * rhs Note: this matrix and rhs are unnaffected.
		 * 
		 * @param rhs
		 *            the right hand side matrix
		 * @return the resulting (C) matrix
		 */
		public Matrix multiply(Matrix rhs) {
			int i, j, x;
			Matrix C = new Matrix();

			for (i = 0; i < 4; i++) {
				for (j = 0; j < 4; j++) {
					C.matrix[i][j] = 0;
					for (x = 0; x < 4; x++) {
						C.matrix[i][j] += this.matrix[i][x] * rhs.matrix[x][j];
					}
				}
			}
			return C;
		}

	}

    private class VisAnimator extends Thread {

        private volatile boolean stop = false;

        public void run() {
            stop = false;
            while (!stop) {
                try {
                    if (rotateSpeed == 0) {
                        Thread.sleep(250);
                    } else {
                        VisRenderer.this.rotate(rotateSpeed, 0);
                    }
                } catch (Exception e) {
                    // Who cares if we were interrupted
                }
            }
        }

        public void end() {
            stop = true;
        }
    }

    // OpenGL objects
    private GL2 gl;
    private GLU glu;
    private GLAutoDrawable glDrawable;

    // conversion to/from degress/radians
    private final static float DTOR = 0.0174532925f;
    //private final static float RTOD = 57.2957795f;

    // sensitivity settings
    private final static float lateralSense = 1 * DTOR;
    private final static float verticalSense = 1 * DTOR;

    // camera information
    private double viewAltitude = -30 * DTOR; // The angle from the x-y plane that the eye is placed 
    private double viewAzimuth = -135 * DTOR; // The angle on the x-y plane that the eye is placed
    private double viewDistance = 50.0; // The distance from the eye to the aim
    private Vec eye; // The location of the eye
    private Vec aim = new Vec(0, 0, 0); // Where the eye is focused at
    private Vec vup; // The canonical V-up vector
    private float camera_aperture = 45.0f;
    private float camera_near = 1.0f;
    private float camera_far = 500.0f;
    private float camera_focallength = 50.0f;

	private List<ScatterPlot> shapes = new ArrayList<ScatterPlot>(); // The list
																		// of
																		// shapes
																		// to
																		// draw

    private float fps; // Frames per Second
    private int framesRendered;

    // dimensionality of the viewing frame (pixels on the screen)
    private int width, height;

    // for screenshot capability
    private boolean makeScreenShot;
    private BufferedImage screenShot;

    // auto-rotation capability
    private VisAnimator visAnimator;
    private volatile float rotateSpeed = 0.5f;

    // GL info

    private boolean stereo_available;

    // settings
    private boolean stereo;
    private JCheckBox stereoCheckBox;
    private boolean antiAliasedLines;
    private boolean fsaa;
    private boolean reverseVideo;

    private int prevMouseX, prevMouseY;
    private boolean mouseRButtonDown;

    public static final int CAMERA_PLOT = 0;
    public static final int CAMERA_STICK = 1;

    private int cameraMode = CAMERA_PLOT;

	private ScatterPlotExample visCanvasListener;
    private boolean listenerInit=false;

    public VisRenderer() {}

    /**
     * Set the handler for VisCanvas events.  When a canvas change is needed, 
     * for example when FSAA is set/unset, the parent needs to place the new canvas
     * into their JFrame.
     * @param visCanvasListener
     */
	public void setVisCanvasListener(ScatterPlotExample visCanvasListener) {
        this.visCanvasListener = visCanvasListener;
    }

    /**
     * Add a shape to the list of shapes to be drawn
     * @param shape		the shape to add
     */
	public void addShape(ScatterPlot shape) {
        shapes.add(shape);
    }

    /**
     * Remove a shape from the list of shapes to be drawn
     * @param shape		the shape to remove
     */
	public void removeShape(ScatterPlot shape) {
        shapes.remove(shape);
    }

    private void setLighting() {
        float lightPosition[] = { 0.0f, 0.0f, 1.0f, 0.0f };

        //float lightPosition[] = { 0.7f, 1.0f, 0.6f, 0.0f };
        //float lightPosition[] = { 5.7f, 5.0f, 5.6f, 1.0f };
        //float lightPosition[] = { -1.0f, 0.0f, 1.0f, 0.0f };

        //float lightPosition[] = { 0f, 10.0f, 10.0f, 1.0f };

        //float lightPosition2[] = { 0.7f, -1.0f, 0.6f, 0.0f };
        float whiteLight[] = { 0.75f, 0.75f, 0.75f, 1.0f };
        //float ambientLight[] = { 0.15f, 0.15f, 0.15f, 1.0f };
        float ambientLight[] = { 0.15f, 0.15f, 0.15f, 1.0f };

        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, lightPosition, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, whiteLight, 0);
        //gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, whiteLight, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, ambientLight, 0);

        //        float mat_shininess[] = { 50.0f };
        //        float mat_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
        //        gl.glMaterialfv(GL.GL_FRONT, GL.GL_SHININESS, mat_shininess, 0);
        //        gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, mat_specular, 0);

        //gl.glLightfv(GL.GL_LIGHT1, GL.GL_POSITION, lightPosition2);
        //gl.glLightfv(GL.GL_LIGHT1, GL.GL_DIFFUSE, whiteLight);
        //gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, whiteLight);
        //gl.glLightfv(GL.GL_LIGHT1, GL.GL_AMBIENT, ambientLight);

        gl.glLightModelfv(GL2.GL_LIGHT_MODEL_AMBIENT, ambientLight, 0);
        gl.glEnable(GL2.GL_COLOR_MATERIAL);
        gl.glShadeModel(GL2.GL_FLAT);
        gl.glEnable(GL2.GL_LIGHTING);
        gl.glEnable(GL2.GL_LIGHT0);

        //        float fogColor[] = {0.5f, 0.5f, 0.5f, 1.0f}; //set the for color to grey
        //        float density = 0.015f;
        //        gl.glEnable(GL.GL_FOG);
        //        gl.glFogi (GL.GL_FOG_MODE, GL.GL_EXP2); //set the fog mode to GL_EXP2
        //        gl.glFogfv (GL.GL_FOG_COLOR, fogColor, 0); //set the fog color to our color chosen above
        //        gl.glFogf (GL.GL_FOG_DENSITY, density); //set the density to the value above
        //        gl.glHint (GL.GL_FOG_HINT, GL.GL_DONT_CARE); // set the fog to look the nicest, may slow down on older cards

        //gl.glEnable(GL.GL_LIGHT1);
        //gl.glEnable(GL.GL_BLEND);

    }

    /**
     * This method is called by JOGL, do not use.
     *
     * @see net.java.games.jogl.GLEventListener#init(net.java.games.jogl.GLAutoDrawable)
     * 
     * @param drawable	The drawable provided by JOGL
     */
    public void init(GLAutoDrawable drawable) {
        gl = drawable.getGL().getGL2();
        glu = new GLU();
        this.glDrawable = drawable;


        byte[] bytes = new byte[1];
        gl.glGetBooleanv(GL2.GL_STEREO, bytes, 0);
        if (bytes[0] != 0) {
            stereo_available = true;
        } else {
            stereo_available = false;
        }

        if (stereoCheckBox != null) {
            stereoCheckBox.setEnabled(stereo_available);
        }

		gl.glEnable(GL.GL_CULL_FACE);
		gl.glEnable(GL.GL_DEPTH_TEST);

        setLighting();

        computeEye();

        if (System.getProperty("vis.polyline") != null) {
            gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2.GL_LINE);
        }

        if (System.getProperty("vis.polyfill") != null) {
            gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2.GL_FILL);
        }

        if (System.getProperty("vis.polypoint") != null) {
            gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2.GL_POINT);
        }

        if(!listenerInit){
        Object o = drawable.getUpstreamWidget();
        
        if(o instanceof javax.media.opengl.awt.GLCanvas){
        	javax.media.opengl.awt.GLCanvas glc = (javax.media.opengl.awt.GLCanvas)o;
        	glc.addMouseListener(this);
            glc.addMouseMotionListener(this);
            glc.addMouseWheelListener(this);
        }
//        else if(o instanceof com.jogamp.opengl.swt.GLCanvas){
//        	com.jogamp.opengl.swt.GLCanvas glc = (com.jogamp.opengl.swt.GLCanvas)o;
//        	glc.addMouseListener(this);
//            glc.addMouseMotionListener(this);
//            glc.addMouseWheelListener(this);
//        }
       /* 
        com.jogamp.newt.opengl.GLWindow	has a	com.jogamp.newt.Window
        SWT	com.jogamp.opengl.swt.GLCanvas	is a	org.eclipse.swt.widgets.Canvas
        AWT	javax.media.opengl.awt.GLCanvas	is a	java.awt.Canvas
        AWT	javax.media.opengl.awt.GLJPanel	is a	javax.swing.JPanel
        
        drawable.addMouseListener(this);
        drawable.addMouseMotionListener(this);
        drawable.addMouseWheelListener(this);*/
        listenerInit=true;
        }
    }

    // computes the eye's position based on the viewAngle, viewDirection and aim vector
    private void computeEye() {

        if (aim == null)
            return;

        //System.out.println ("viewAltitude now " + viewAltitude);
        //System.out.println ("viewAzimuth now " + viewAzimuth);

        Matrix rotateY = Matrix.createRotateY(-viewAltitude);
        Matrix rotateZ = Matrix.createRotateZ(viewAzimuth);

        eye = rotateZ.transform(rotateY.transform(new Vec(1, 0, 0)));
        eye.normalize();

        // set the canonical v-up vector
        vup = rotateZ.transform(rotateY.transform(new Vec(0, 0, 1)));

        eye.setx(eye.x() * viewDistance);
        eye.sety(eye.y() * viewDistance);
        eye.setz(eye.z() * viewDistance);

        if (cameraMode == CAMERA_STICK) {
            aim.setx(0);
            aim.sety(0);
        }

        eye = eye.add(aim);
    }

    /**
     * Cleans up resources used by this object.
     */
    public void cleanUp() {
        if (visAnimator != null) {
            visAnimator.end();
        }
    }

    /**
     * Rotate the camera
     */
    private void rotate(float x, float y) {
        final float limit = 90.0f;

        viewAltitude += verticalSense * y;
        if (viewAltitude < -DTOR * limit)
            viewAltitude = -DTOR * limit;
        if (viewAltitude > DTOR * limit)
            viewAltitude = DTOR * limit;

        viewAzimuth += lateralSense * x;
        if (viewAzimuth >= 2 * Math.PI)
            viewAzimuth -= 2 * Math.PI;
        if (viewAzimuth >= 2 * Math.PI)
            viewAzimuth -= 2 * Math.PI;

        computeEye();
        redraw();
    }

    /**
     * Translate the camera
     */
    private void translate(float x, float y) {

        double oldViewAltitude = viewAltitude;
        float oldz = aim.z();

        viewAltitude = 45.0;

        computeEye();

        Vec VPN = aim.subtract(eye);
        Vec n = new Vec(VPN);
        n.normalize();
        Vec u = vup.cross(n);
        u.normalize();
        Vec v = n.cross(u);
        v.normalize();

        Matrix translate = new Matrix();
        translate.setToTranslate(-eye.x(), -eye.y(), -eye.z());

        Matrix rotate = new Matrix();
        rotate.setOrthRotate(u, v, n);

        Matrix M = rotate.multiply(translate);

        Vec diff = eye.subtract(aim);

        double scaleFactor = Math.sqrt(diff.length()) / 50;

        translate.setToTranslate(scaleFactor * x, 0, scaleFactor * y);
        M = translate.multiply(M);

        rotate.transpose();
        M = rotate.multiply(M);

        translate.setToTranslate(eye.x(), eye.y(), eye.z());

        M = translate.multiply(M);

        aim = M.transform(aim);

        aim.setz(oldz);
        viewAltitude = oldViewAltitude;

        computeEye();

        redraw();
    }

    private void translateStick(float x, float y) {
        aim.setz(aim.z() - (y / 3));
        computeEye();
        redraw();
    }

    /**
     * Redraws the shapes
     */
    public void redraw() {
        if (glDrawable != null) {
            glDrawable.display();
        }
    }

    /**
     * Check if we are ready to draw
     */
    public boolean isReadyToDraw() {
        if (glDrawable != null) {
            return true;
        }
        return false;
    }

    /**
     * Creates a screenshot of the current display
     * @return	the screenshot
     */
    public BufferedImage createScreenShot() {
        // screenshot must be taken within display loop
        // set a boolean tag and rerun display loop
        makeScreenShot = true;
        glDrawable.display();
        return screenShot;
    }

    /**
     * This method is called by JOGL, do not use.
     * @see net.java.games.jogl.GLEventListener#reshape(net.java.games.jogl.GLAutoDrawable, int, int, int, int)
     */
    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        this.width = width;
        this.height = height;

        //camera_aperture = 45.0f;
        camera_near = 1.0f;
        camera_far = 500.0f;

        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(camera_aperture, (float) width / (float) height, camera_near, camera_far);
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    /**
     * This method is called by JOGL, do not use.
     * To force a redraw, use VisRenderer.redraw()
     *
     * @see net.java.games.jogl.GLEventListener#display(net.java.games.jogl.GLAutoDrawable)
     */
    public void display(GLAutoDrawable drawable) {

        reshape(drawable, 0, 0, this.getWidth(), this.getHeight());

        int n = 1;
        if (stereo) {
            n = 2;
        }

        for (int frame = 0; frame < n; frame++) {

            if (stereo) {
                if (frame == 0) {
                    gl.glDrawBuffer(GL2.GL_BACK_LEFT);
                } else {
                    gl.glDrawBuffer(GL2.GL_BACK_RIGHT);
                }
            } else {
                gl.glDrawBuffer(GL.GL_BACK);
            }
            //        gl = new DebugGL(drawable.getGL());
            //        gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

            if (reverseVideo) {
                //gl.glClearColor(0.75f, 0.75f, 0.75f, 1.0f);
                gl.glClearColor(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 1.0f);
            } else {
                gl.glClearColor(0, 0, 0, 0);
            }
            gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

            gl.glPushMatrix();
            gl.glTranslated(0, 0, -viewDistance);

            setLighting();

            if (aim == null) {
                aim = new Vec(0, 0, 0);
            }

            if (stereo) {

                float eyesep, wd2, ratio, radians;

                //camera_focallength = 50.0f;
                //camera_aperture = 45.0f;
                camera_near = 1.0f;
                camera_far = 500.0f;

                eyesep = camera_focallength / 20;

                ratio = (float) this.width / (float) this.height;
                radians = DTOR * camera_aperture / 2;
                wd2 = (float) (camera_near * Math.tan(radians));

                // direction vector
                Vec direction = eye.subtract(aim);

                // cross product of v-up and the view direction will give us the normal, which is the axis the eyes are on
                // we will add and subtract this vector to move to the left and right eye
                Vec crossRight = direction.cross(vup);
                crossRight.normalize();
                crossRight.scale((float) (eyesep / 2.0));

                float leftRightFrustumShift;

                if (frame == 0) {
                    leftRightFrustumShift = 0.5f;
                } else {
                    leftRightFrustumShift = -0.5f;
                }

                // create a parallel axis asymmetric frustum perspective projection
                float left, right, top, bottom;
                gl.glMatrixMode(GL2.GL_PROJECTION);
                gl.glLoadIdentity();
                float ndfl = camera_near / camera_focallength;
                left = (float) (-ratio * wd2 + leftRightFrustumShift * eyesep * ndfl);
                right = (float) (ratio * wd2 + leftRightFrustumShift * eyesep * ndfl);
                top = wd2;
                bottom = -wd2;
                gl.glFrustum(left, right, bottom, top, camera_near, camera_far);
                gl.glMatrixMode(GL2.GL_MODELVIEW);

                if (frame == 0) {
                    glu.gluLookAt(aim.x() - crossRight.x(), aim.y() - crossRight.y(), aim.z() - crossRight.z(), eye.x()
                            - crossRight.x(), eye.y() - crossRight.y(), eye.z() - crossRight.z(), vup.x(), vup.y(), vup.z());
                } else {
                    glu.gluLookAt(aim.x() + crossRight.x(), aim.y() + crossRight.y(), aim.z() + crossRight.z(), eye.x()
                            + crossRight.x(), eye.y() + crossRight.y(), eye.z() + crossRight.z(), vup.x(), vup.y(), vup.z());
                }
            } else {
                glu.gluLookAt(aim.x(), aim.y(), aim.z(), eye.x(), eye.y(), eye.z(), vup.x(), vup.y(), vup.z());
            }

            for (int i = 0; i < shapes.size(); i++) {
				ScatterPlot shape = shapes.get(i);
                if(shape!=null)
                	shape.render(this);
            }

            //        int err = gl.glGetError();
            //        if (err != GL.GL_NO_ERROR)
            //            System.out.println("err = " + glu.gluErrorString(err));

            gl.glPopMatrix();
            framesRendered++;

        }

        // if screenshot was requested since last draw
        if (makeScreenShot) {
            makeScreenShot = false;
            makeScreenShot(drawable);
        }

    }

    private void makeScreenShot(GLAutoDrawable drawable) {

        int width = drawable.getWidth();
        int height = drawable.getHeight();

        ByteBuffer pixelsRGB = Buffers.newDirectByteBuffer(width * height * 3);

        GL2 gl = drawable.getGL().getGL2();

        gl.glReadBuffer(GL.GL_BACK);
        gl.glPixelStorei(GL.GL_PACK_ALIGNMENT, 1);

        gl.glReadPixels(0, // GLint x
                0, // GLint y
                width, // GLsizei width
                height, // GLsizei height
                GL.GL_RGB, // GLenum format
                GL.GL_UNSIGNED_BYTE, // GLenum type
                pixelsRGB); // GLvoid *pixels

        int[] pixelInts = new int[width * height];

        // Convert RGB bytes to ARGB ints with no transparency. Flip image vertically by reading the
        // rows of pixels in the byte buffer in reverse - (0,0) is at bottom left in OpenGL.

        int p = width * height * 3; // Points to first byte (red) in each row.
        int q; // Index into ByteBuffer
        int i = 0; // Index into target int[]
        int w3 = width * 3; // Number of bytes in each row

        for (int row = 0; row < height; row++) {
            p -= w3;
            q = p;
            for (int col = 0; col < width; col++) {
                int iR = pixelsRGB.get(q++);
                int iG = pixelsRGB.get(q++);
                int iB = pixelsRGB.get(q++);
                pixelInts[i++] = 0xFF000000 | ((iR & 0x000000FF) << 16) | ((iG & 0x000000FF) << 8) | (iB & 0x000000FF);
            }
        }

        screenShot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        screenShot.setRGB(0, 0, width, height, pixelInts, 0, width);
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {}

    // MouseListener implementation
    public void mouseEntered(MouseEvent e) {}

    public void mouseExited(MouseEvent e) {}

    public void mouseClicked(MouseEvent e) {}

    public void mouseMoved(MouseEvent e) {}

    public void mousePressed(MouseEvent e) {
        prevMouseX = e.getX();
        prevMouseY = e.getY();
        if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0) {
            mouseRButtonDown = true;
        }
    }

    public void mouseReleased(MouseEvent e) {
        if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0) {
            mouseRButtonDown = false;
        }
    }

    public void mouseDragged(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();
        //Dimension size = e.getComponent().getSize();

        //float thetaY = 360.0f * ((float) (x - prevMouseX) / (float) size.width);
        //float thetaX = 360.0f * ((float) (prevMouseY - y) / (float) size.height);

        float dy, dx;
        dx = x - prevMouseX;
        dy = y - prevMouseY;

        if (mouseRButtonDown) {
            if (cameraMode == CAMERA_PLOT) {
                translate(-dx, -dy);
            } else {
                translateStick(-dx, -dy);
            }
        } else {
            rotate(-dx, dy);
        }

        prevMouseX = x;
        prevMouseY = y;
    }

    // Zoom in and out with the mouse wheel
    public void mouseWheelMoved(MouseWheelEvent e) {
        int scrollAmount = e.getWheelRotation();
        if (scrollAmount > 0) {
            zoomOut();
        } else {
            zoomIn();
        }
    }

    /**
     * Zooms the camera in by dividingthe distance between the <tt>aim</tt> and <tt>eye</tt> by <tt>1.1</tt>.
     */
    public void zoomIn() {
        viewDistance /= 1.1;
        computeEye();
        glDrawable.display();
    }

    /**
     * Zooms the camera out by multiplying the distance between the <tt>aim</tt> and <tt>eye</tt> by <tt>1.1</tt>.
     */
    public void zoomOut() {
        viewDistance *= 1.1;
        computeEye();
        glDrawable.display();
    }

    /**
     * Returns the fps value.
     * @return the fps value.
     */
    public float getFps() {
        return fps;
    }

    /**
     * Sets the fps value, stored by this <tt>VisRenderer</tt>, but not currently used by it.
     * @param fps The fps to set.
     */
    public void setFps(float fps) {
        this.fps = fps;
    }

    /**
     * Returns the framesRendered.
     * @return the framesRendered.
     */
    public int getFramesRendered() {
        return framesRendered;
    }

    /**
     * Sets the number of frames rendered (used to reset it).
     * @param framesRendered the number of frames rendered (used to reset it).
     */
    public void setFramesRendered(int framesRendered) {
        this.framesRendered = framesRendered;
    }

    /**
     * Returns the height.
     * @return the height.
     */
    public int getHeight() {
        return height;
    }

    /**
     * Returns the width.
     * @return the width.
     */
    public int getWidth() {
        return width;
    }

    /**
     * Returns the point (as a <tt>Vec</tt>) that the camera is located.
     * @return the point (as a <tt>Vec</tt>) that the camera is located.
     */
    public Vec getEye() {
        return eye;
    }

    /**
     * Returns the point (as a <tt>Vec</tt>) that the camera is currently aimed.
     * @return the point (as a <tt>Vec</tt>) that the camera is currently aimed.
     */
    public Vec getAim() {
        return aim;
    }

    /**
     * Set the point (as a <tt>Vec</tt>) that the camera is currently aimed at.
     * @param aim the <tt>Vec</tt> to aim at.
     */
    public void setAim(Vec aim) {
        this.aim = aim;
        computeEye();
    }



    /**
     * Returns whether or not reverse video is on (black on white).
     * @return whether or not reverse video is on (black on white).
     */
    public boolean getReverseVideo() {
        return reverseVideo;
    }

    /**
     * Makes video reversed or not (black on white or white on black).
     * @param reverseVideo <tt>true</tt> for black background; <tt>false</tt> for white.
     */
    public void setReverseVideo(boolean reverseVideo) {
        this.reverseVideo = reverseVideo;
        this.redraw();
    }

    /**
     * Returns the <tt>GLAutoDrawable</tt> for this <tt>VisRenderer</tt>.
     * @return the <tt>GLAutoDrawable</tt> for this <tt>VisRenderer</tt>.
     */
    public GLAutoDrawable getGLAutoDrawable() {
        return glDrawable;
    }

    /**
     * Returns the direction of the camera.  Used by the plots to determine how 
     * to draw the data (for proper translucency, you must draw back to front).
     * @return the direction of the camera.
     */
    public Vec getViewDirection() {
        return eye.subtract(aim);
    }

    public boolean getAntiAliasedLines() {
        return antiAliasedLines;
    }

    public void setAntiAliasedLines(boolean antiAliasedLines) {
        this.antiAliasedLines = antiAliasedLines;
        this.redraw();
    }

    public boolean getStereo() {
        return stereo;
    }

    public void setStereo(boolean stereo) {
        this.stereo = stereo;
        this.redraw();
    }

    public float getCamera_focallength() {
        return camera_focallength;
    }

    public void setCamera_focallength(float camera_focallength) {
        this.camera_focallength = Math.max(1.0f, camera_focallength);
        this.redraw();
    }

    public float getCamera_aperture() {
        return camera_aperture;
    }

    public void setCamera_aperture(float camera_aperture) {
        this.camera_aperture = camera_aperture;
        this.redraw();
    }

    public int getCameraMode() {
        return cameraMode;
    }

    public void setCameraMode(int cameraMode) {
        this.cameraMode = cameraMode;
        computeEye();
    }

    public boolean getFSAA() {
        return fsaa;
    }

    public void setFSAA(boolean fsaa) {
        if (fsaa != this.fsaa) {
            this.fsaa = fsaa;
            
            
            if (visCanvasListener != null) {
                visCanvasListener.createNewCanvas();
            }
            
            for (int i = 0; i < shapes.size(); i++) {
				ScatterPlot shape = shapes.get(i);
                shape.resetCanvas();
            }
            redraw();
        }
    }

	public void dispose(GLAutoDrawable drawable) {
		System.out.println("DESTROY!");
	}

}