import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import Vertex3D;
import Triangle;
import Matrix4x4;
import MatrixStack;
import Light;
import Surface;
import Luxo;
import Box;
import Ball;


/*
    LuxoApplet provides only user interface elements.
    All od the interesting stuff is done in the
    LuxoCanvas Class.
*/
public class LuxoApplet extends Applet {
  Panel viewerPanel, buttonPanel;
  LuxoCanvas luxoViewer;
  Button button[];

  // These are the button labels. The index into this array
  // determines the number passed to LuxoCanvas.processButton()
  String bLabel[ ] = {
    "Reset",
    "Sky Cam",
    "Mom Cam",
    "Jr Cam",
    "Mom @ Jr",
    "Mom @ Ball",
    "Jr @ Mom",
    "Jr @ Ball",
    "Move Jr N",
    "Move Jr S",
    "Move Jr E",
    "Move Jr W",
    "zoom in",
    "zoom out",
    "Mom nod",
    "Jr nod",
    "Jr dance",
    "Mom Watch",
    "Jr hop",
    "Jr walk"
  };


  // Here is where the user interface is created
  public void init() {
    int width = size().width;
    int height = size().height;
    button = new Button[bLabel.length];

    setLayout(new BorderLayout(10, 10));
    setBackground(new Color(128, 96, 128));

    viewerPanel = new Panel();
    luxoViewer = new LuxoCanvas(this, width - 10, height - 120);
    add(viewerPanel, BorderLayout.NORTH);
    viewerPanel.add(luxoViewer, null);

    buttonPanel = new Panel();
    add(buttonPanel, BorderLayout.SOUTH);
    buttonPanel.setLayout(new GridLayout(0, 4, 0, 0));
    for (int i = 0; i < bLabel.length; i++) {
      button[i] = new Button(bLabel[i]);
      button[i].setBackground(new Color(192, 160, 192));
      buttonPanel.add(button[i]);
    }

    luxoViewer.init();
  }

  // Here is where button presses are handled
  public boolean action(Event e, Object o) {
    if (e.target instanceof Button) {
      for (int i = 0; i < bLabel.length; i++) {
        if (bLabel[i].equals(o)) {
          luxoViewer.processButton(i);
          break;
        }
      }
      return true;
    } else {
      return false;
    }
  }

  // makes a nice 5 pixel border around
  // the LuxoCanvas and buttonPanel
  public Insets insets() {
    return new Insets(5,5,5,5);
  }
}



class LuxoCanvas extends Canvas {
    final int SKY_CAM = 0; 
    final int MOM_CAM = 1; 
    final int JR_CAM  = 2; 

    final int CHUNKSIZE = 500;          // initial array size for vertices and triangles
    final int SKYCOLOR = 0xffd0e0ff;
	
	int mode    = 0;	
	int it      = 0;
	
    float mom_angles[];
    float jr_angles[];
    float camera_angles[];
	
	
	
    Raster raster;
    Image screen;
    MatrixStack view;
    MatrixStack model;
    Vertex3D worldList[], screenList[];   // temporary storage for transformed vertices
    Luxo mom, jr;
    Vertex3D momEye, jrEye, momLookat, jrLookat, momPivot, jrPivot, ballEye;
    Box floor;
    Ball ball;
    Light lightList[];
    int lights;

    Applet parent;

    // these variables are used to set up the viewing,
    // projection, and screen-space mapping matrices
    // several of these could have been Vertex3D Objects
    // but they really don't need some of the extra stuff
    // that a Vertex3D has, like normals and colors,
    // so I didn't decided to declare them as floats
    float eyex,    eyey,    eyez;
    float jrx,     jry,     jrz;
    float momx,    momy,    momz;
    float ballx,    bally,  ballz;
    float lookatx, lookaty, lookatz;
    float upx,     upy,      upz;
	float distance;

    float fov;
    int width, height;

    // the constructor set the size of the rendering, creates
    // a raster, and fills it with the background color.
    public LuxoCanvas(Applet p, int w, int h) {
      super();
      parent = p;
      setSize(w, h);
      raster = new Raster(w, h);
      raster.fill(SKYCOLOR);
      screen = raster.toImage( );
      width = w;
      height = h;
    }

    // the init method resets the whole LuxoCanvas
    public void init( ) {
		mom_angles	  = new float[4];
		jr_angles     = new float[4];
		camera_angles = new float[3];
		mom_angles[0] =    0.0f;
		mom_angles[1] = -120.0f;
		mom_angles[2] =  120.0f;
		mom_angles[3] =  -60.0f;
		jr_angles[0]  =  180.0f;
		jr_angles[1]  = -135.0f;
		jr_angles[2]  =   90.0f;
		jr_angles[3]  =  -60.0f;
	
		camera_angles[0] = 90-21.801f;//60.0f;
		camera_angles[1] = 0.0f;
		camera_angles[2] = 0.0f;

		distance = 100f;
		
        // The model matrixStack will be used to map a model's local
        // coordinates into world coordinates, there are a lot of different
        // "local" coordinate systems used, so this matrixStack gets a lot
        // of use
        model    = new MatrixStack();

		model.push();
		model.rotate( 1.0f, 0.0f, 0.0f, toRadians( camera_angles[0] ) );
		model.rotate( 0.0f, 1.0f, 0.0f, toRadians( camera_angles[1] ) );
		model.rotate( 0.0f, 0.0f, 1.0f, toRadians( camera_angles[2] ) );
		model.translate( 0.0f, 0.0f, distance );
		Vertex3D eye_position = model.transform( new Vertex3D( 0.0f, 0.0f, 0.0f ) );
		model.pop();
		
        eyex = eye_position.x;
		eyey = eye_position.y;
		eyez = eye_position.z;
		
		System.out.println( "Initial eye position " + eye_position );
		
        lookatx = 0;     lookaty = 0;       lookatz = 0;
        upx  = 0;        upy  = 0;          upz  = 1;
		jrx  = 6.0f;	 jry  = 0.0f;		jrz  = 0.0f;
		momx = -10.0f;	 momy = 0.0f;		momz = 0.0f;
		ballx=  3.0f;    bally= 6.0f;       ballz= 0.0f;
        fov = 35;                           // in degrees
		
        // First, we create and initialize temporary storage for transformed
        // vertices. Here is a brief explanation of how these arrays get used.
        // Modeling coordinates of objects are transformed onto the worldList
        // where they are lit. These world coordinates then get transformed to
        // the screenList where they are rasterized. You can actually do this
        // trnsformation in one step, however, the method used here is more
        // flexible if you want to do some neat things later on (like shadow
        // maps).
        worldList = new Vertex3D[CHUNKSIZE];
        screenList = new Vertex3D[CHUNKSIZE];
        for (int i = 0; i < CHUNKSIZE; i++) {
            worldList[i] = new Vertex3D();
            screenList[i] = new Vertex3D();
        }

        // set default values for the viewing parameters

        // Set up two light sources, one ambient, one directional.
        // We'll get to this stuff later in class. Don't mess with
        // them at this point.
        lightList = new Light[10];
        lights = 0;
        lightList[lights++] = new Light(Light.AMBIENT, 0, 0, 0, 1.0f, 1.0f, 1.0f);
        lightList[lights++] = new Light(Light.DIRECTIONAL, 0.5f, 1.0f, -0.5f, 1.0f, 1.0f, 1.0f);

        // We next set up to matrix transform stacks.
        //
        // The view matrixStack will be used to map from world to screen
        // coordinates. It looks at the width and height of the Raster
        // to compute the mapping from canonical to screen coordinates
        view = new MatrixStack(raster);
        float t = (float) Math.sin(Math.PI*(fov/2)/180);
        float s = (t*height)/width;

        // Here we set the projection (from eye to canonical space)
        view.frustum(-t, t, -s, s, -1, -500);  // Eye canonical space

        // Make another copy of the composed mapping from eye to screen space
        view.push();  // camera

        // Set up a viewing matrix (hint: a camera)
        view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);

        // Next we create our four models.
        // The lamps each have four components, each with their own
        // vertex lists, triangle lists, and local coordinate systems.
        // The other two models, the floor and the ball have only one
        // component.
        mom   = new Luxo(new Surface(0.4f, 0.4f, 1.0f, 0.5f, 0.5f, 0.0f, 100.0f));
        jr    = new Luxo(new Surface(1.0f, 0.2f, 0.2f, 0.5f, 0.5f, 0.0f, 100.0f));
        floor = new Box(-24.0f, 24.0f, -24.0f, 24.0f, -1.0f, 0.0f,
                        new Surface(0.8f, 0.7f, 0.5f, 0.8f, 0.2f, 0.0f, 100.0f));
        ball  = new Ball(new Surface(1.0f, 0.0f, 1.0f, 0.5f, 0.5f, 0.0f, 100.0f));
 
        // Reproportion Mom to make Jr model.
        // Here we use the model matrixStack to modify the vertex list
        // of Jr. There are other ways of doing this. For instance,
        // we could have declared just one lamp model and reproportion
        // and recolor it everytime we render. I've decided to keep things
        // conceptually, simple here. Each object has it's own model.
        // This code simply rescales the body and neck of the Jr model
        // so that it will have more childlike proportions.
        model = new MatrixStack();
        model.scale(0.5f, 1.0f, 1.0f);
        Vertex3D vList[] = jr.getVertices(Luxo.BODY);
        model.transform(vList, vList, vList.length);
        vList = jr.getVertices(Luxo.NECK);
        model.transform(vList, vList, vList.length);
        model.loadIdentity();

        // Now that all of the models are set up, call the
        // method that renders the whole scene.
        DrawScene();
        repaint();
    }

    public void paint(Graphics g) {
        g.drawImage(screen, 0, 0, this);
    }

    public void update(Graphics g) {
        paint(g);
    }

    float old_x, old_y;

    public boolean mouseDown(Event e, int x, int y) {
        // You will need to make extensive changes to this code
        // to implement the various camera navigation modes
		/*
        if (e.metaDown()) {
            parent.showStatus("Resetting model matrix");
            long time = System.currentTimeMillis();
            for (int i = 0; i <= 100; i++) {
              model.loadIdentity();
              // rotate the world around the up axis
              model.rotate(upx, upy, upz, (float)(i*Math.PI/50));
              DrawScene();
              update(this.getGraphics());
            }
            time = System.currentTimeMillis() - time;
            parent.showStatus("Time = "+(time/10)+" ms per frame");
        }
		*/
        old_x = x;
        old_y = y;
	    return true;
    }

    // The renderObject() method does all of the work of rendering objects.
    // You will probably not need to modify this method, though you may if
    // you wish.
    private void renderObject(MatrixStack m, Vertex3D vList[], Triangle tList[]) {
        int verts = vList.length;
        int tris = tList.length;
        // transform the model's coordinates to the world-coodinate system
        // using MatrixStack m. Set the triangle's vertex list to these
        // coordinates, then light the triangles.
        m.transform(vList, worldList, verts);
        Triangle.setVertexList(worldList);
        for (int i = 0; i < tris; i++) {
          tList[i].Illuminate(lightList, lights);
        }

        // Next we transform our world space coordinates to screen space.
        // The we tell the triangles to use these vertices to draw themselves.
        view.transform(worldList, screenList, verts);
        Triangle.setVertexList(screenList);
        for (int i = 0; i < tris; i++) {
          tList[i].Draw(raster);
        }
    }

    private float toRadians(float degrees) {
      return (float) (degrees/180.0f * Math.PI);
    }

    // I wrote this little helper method to aid in drawing the
    // Mom luxo lamp. It limit's the model's articulations to
    // the allowed degrees of freedom. If you don't find it
    // helpful, you do not have to use it.
    private Vertex3D DrawMom(float a1, float a2, float a3, float a4) {
        model.rotate(0.0f, 0.0f, 1.0f, toRadians(a1));
        renderObject(model, mom.getVertices(Luxo.BASE), mom.getTriangles(Luxo.BASE));
        model.translate(0.0f, 0.0f, 2.5f);
        model.rotate(0.0f, 1.0f, 0.0f, toRadians(a2));
        renderObject(model, mom.getVertices(Luxo.BODY), mom.getTriangles(Luxo.BODY));
        model.translate(12.0f, 0.0f, 0.0f);
        model.rotate(0.0f, 1.0f, 0.0f, toRadians(a3));
        renderObject(model, mom.getVertices(Luxo.NECK), mom.getTriangles(Luxo.NECK));
        model.translate(12.0f, 0.0f, 0.0f);
        model.rotate(0.0f, 1.0f, 0.0f, toRadians(a4));
        renderObject(model, mom.getVertices(Luxo.HEAD), mom.getTriangles(Luxo.HEAD));
        // Opps... I did it again, I played with your heart
        model.translate( 1.0f, 0.0f, -1.0f );
		Vertex3D result =  model.transform( new Vertex3D(0f, 0f, 0f ) );
        return result;
    }

    // This helper method to aids in drawing the Jr. luxo lamp.
    // It limit's the model's articulations to the allowed
    // degrees of freedom. Just like above.
    private Vertex3D DrawJr(float a1, float a2, float a3, float a4) {
        model.scale(0.75f, 0.75f, 0.75f);
        model.rotate(0.0f, 0.0f, 1.0f, toRadians(a1));
        renderObject(model, jr.getVertices(Luxo.BASE), jr.getTriangles(Luxo.BASE));
        model.translate(0.0f, 0.0f, 2.5f);
        model.rotate(0.0f, 1.0f, 0.0f, toRadians(a2));
        renderObject(model,  jr.getVertices(Luxo.BODY), jr.getTriangles(Luxo.BODY));
        model.translate(6.0f, 0.0f, 0.0f);
        model.rotate(0.0f, 1.0f, 0.0f, toRadians(a3));
        renderObject(model, jr.getVertices(Luxo.NECK), jr.getTriangles(Luxo.NECK));
        model.translate(6.0f, 0.0f, 0.0f);
        model.rotate(0.0f, 1.0f, 0.0f, toRadians(a4));
        renderObject(model, jr.getVertices(Luxo.HEAD), jr.getTriangles(Luxo.HEAD));
        // Opps... I did it again, I played with your heart
        model.translate( 1.0f, 0.0f, -1.0f );
		Vertex3D result =  model.transform( new Vertex3D(0f, 0f, 0f ) );
        return result;
    }

    // Okay, you'll need to completely rewrite this method.
    void DrawScene() {
        // set the background color and reset the depth buffer
        raster.fill(SKYCOLOR);
        raster.resetz();

        // Draw the table, its frame is the same as the world frame.
        // In other words, it is already in world coordinates.
        renderObject(model, floor.getVertices(1), floor.getTriangles(1));

        // Save the world. Then add a ball.
        // The ball, has it's own coordinate frame, that, by default,
        // sits on the plane z = 0.
        model.push();
        model.translate( ballx, bally, ballz );
        renderObject(model, ball.getVertices(1), ball.getTriangles(1));
        model.pop();

        // Save the world. Then add Mom, whos frame also sits at z = 0.
        model.push();
        model.translate(momx,momy,momz);
        DrawMom( mom_angles[0], mom_angles[1], mom_angles[2], mom_angles[3] );
        model.pop();

        // Save the world. Then add Jr, who frame sits, you guessed it, at 0.
        model.push();
        model.translate(jrx, jry, jrz);
        DrawJr( jr_angles[0], jr_angles[1], jr_angles[2], jr_angles[3] );
        model.pop();

        // Make the raster into and Image.
        screen = raster.toImage( );
    }

    // Here's where you handle all of the button presses
    // from the button Panel
    public void processButton(int i) {
      switch (i) {
        case 0:                 // Reset
		  init();
		  mode = SKY_CAM;
          break;
        case 1:                 // Sky cam 
		  mode = SKY_CAM;
		  System.out.println( "eye    = " + eyex +    ", " + eyey +    ", " + eyez    );
		  System.out.println( "lookat = " + lookatx + ", " + lookaty + ", " + lookatz );
		  System.out.println( "up     = " + upx +     ", " + upy +     ", " + upz     );
		  view.pop();
          view.push();
          view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
          DrawScene();
          paint( getGraphics() );
          break;
        case 2:                 // Mon cam 
		  mode = MOM_CAM;

		  computeMomEyeAndLookat();
		  view.pop();
          view.push();
          view.lookAt(momEye.x, momEye.y, momEye.z, momLookat.x, momLookat.y, momLookat.z, upx, upy, upz);
          DrawScene();
          paint( getGraphics() );
          break;
        case 3:                 // Jr cam 
		  mode = JR_CAM;

		  computeJrEyeAndLookat();
		  view.pop();
          view.push();
          view.lookAt(jrEye.x, jrEye.y, jrEye.z, jrLookat.x, jrLookat.y, jrLookat.z, upx, upy, upz);
          DrawScene();
          paint( getGraphics() );
          break;
		  
		  
		  
        case 4:                 // Mon@jr
		  computeJrEyeAndLookat();
		  
		  float base_diff = 180*(float)(Math.atan2( jrEye.y-momy, jrEye.x-momx )/Math.PI)-mom_angles[0];
		  
		  System.out.println( "Making a " + base_diff + " degree turn" );
	  	  mom_angles[0] += base_diff;
		  
		  computeMomEyeAndLookat();
		  
		  
		  float a = getAngle( new Vertex3D( jrEye.x     - momPivot.x, jrEye.y     - momPivot.y, jrEye.z     - momPivot.z ),
		  					  new Vertex3D( momLookat.x - momEye.x,   momLookat.y - momEye.y,   momLookat.z - momEye.z   ) );
		  
		  float b = toDegrees( (float) Math.asin( 1.0 / dist( jrEye, momPivot ) ) );
		  
		  System.out.println( "a " + a );
		  System.out.println( "b " + b );
		  
		  mom_angles[3] -= ( a - b );
		  
		  computeMomEyeAndLookat();
		  
		  if ( mode == MOM_CAM ) {
		     view.pop();
             view.push();
             view.lookAt(momEye.x, momEye.y, momEye.z, momLookat.x, momLookat.y, momLookat.z, upx, upy, upz);
		  }
		  
          DrawScene();
          repaint();
          break;
        case 5:                 // Mon@Ball
		  computeMomEyeAndLookat();
		  
		  base_diff = 180*(float)(Math.atan2( ballEye.y-momy, ballEye.x-momx )/Math.PI)-mom_angles[0];
		  
		  System.out.println( "Making a " + base_diff + " degree turn" );
	  	  mom_angles[0] += base_diff;
		  
		  computeMomEyeAndLookat();
		  
		  
		  a = getAngle( new Vertex3D( ballEye.x	  - momPivot.x, ballEye.y 	- momPivot.y, ballEye.z	  - momPivot.z ),
		    			new Vertex3D( momLookat.x - momEye.x,	momLookat.y - momEye.y,   momLookat.z - momEye.z   ) );
		  
		  b = toDegrees( (float) Math.asin( 1.0 / dist( ballEye, momPivot ) ) );
		  
		  System.out.println( "a " + a );
		  System.out.println( "b " + b );
		  
		  mom_angles[3] -= ( a - b );
		  
		  computeMomEyeAndLookat();
		  
		  if ( mode == MOM_CAM ) {
		     view.pop();
             view.push();
             view.lookAt(momEye.x, momEye.y, momEye.z, momLookat.x, momLookat.y, momLookat.z, upx, upy, upz);
		  }
		  
		  if ( Math.abs( a-b ) > .1 ) {
		  	if ( it < 10 ) { it++; processButton(5); }
		  }
		  it = 0;
          DrawScene();
          repaint();
          break;
		  
		  
		  
		  
		  
        case 6:                 
		  computeMomEyeAndLookat();
		  
		  base_diff = 180*(float)(Math.atan2( momEye.y-jry, momEye.x-jrx )/Math.PI)-jr_angles[0];
		  
		  System.out.println( "Making a " + base_diff + " degree turn" );
	  	  jr_angles[0] += base_diff;
		  
		  computeJrEyeAndLookat();
		  
		  
		  a = getAngle( new Vertex3D( momEye.x	  - jrPivot.x,  momEye.y 	- jrPivot.y,  momEye.z	  - jrPivot.z ),
		    			new Vertex3D( jrLookat.x - jrEye.x,	jrLookat.y - jrEye.y,   jrLookat.z - jrEye.z   ) );
		  
		  b = toDegrees( (float) Math.asin( 1.0 / dist( momEye, jrPivot ) ) );
		  
		  System.out.println( "a " + a );
		  System.out.println( "b " + b );
		  
		  jr_angles[3] -= ( a - b );
		  
		  computeJrEyeAndLookat();
		  
		  if ( mode == JR_CAM ) {
		     view.pop();
             view.push();
             view.lookAt(jrEye.x, jrEye.y, jrEye.z, jrLookat.x, jrLookat.y, jrLookat.z, upx, upy, upz);
		  }
		  
          DrawScene();
          repaint();
          break;
        case 7:                 
		  computeJrEyeAndLookat();
		  
		  base_diff = 180*(float)(Math.atan2( ballEye.y-jry, ballEye.x-jrx )/Math.PI)-jr_angles[0];
		  
		  System.out.println( "Making a " + base_diff + " degree turn" );
	  	  jr_angles[0] += base_diff;
		  
		  computeJrEyeAndLookat();
		  
		  
		  a = getAngle( new Vertex3D( ballEye.x	  - jrPivot.x, ballEye.y 	- jrPivot.y, ballEye.z	  - jrPivot.z ),
		    			new Vertex3D( jrLookat.x - jrEye.x,	jrLookat.y - jrEye.y,   jrLookat.z - jrEye.z   ) );
		  
		  b = toDegrees( (float) Math.asin( 1.0 / dist( ballEye, jrPivot ) ) );
		  
		  System.out.println( "a " + a );
		  System.out.println( "b " + b );
		  
		  jr_angles[3] -= ( a - b );
		  
		  computeJrEyeAndLookat();
		  
		  if ( mode == JR_CAM ) {
		     view.pop();
             view.push();
             view.lookAt(jrEye.x, jrEye.y, jrEye.z, jrLookat.x, jrLookat.y, jrLookat.z, upx, upy, upz);
		  }
		  
		  if ( Math.abs( a-b ) > .1 ) {
		  	if ( it < 10 ) { it++; processButton(7); }
		  }
		  it = 0;
			
          DrawScene();
          repaint();
          break;
		  
		  
		  
        case 8:
		  jry++;
          DrawScene();
          repaint();
          break;
        case 9:
		  jry--;
          DrawScene();
          repaint();
          break;
        case 10:
		  jrx++;
          DrawScene();
          repaint();
          break;
        case 11:
		  jrx--;
          DrawScene();
          repaint();
          break;
 		case 12:
		  zoomIn();
 		  break;
 		case 13:
		  zoomOut();
 		  break;
 		case 14:
		  momNod();
 		  break;
 		case 15:
		  jrNod();
		  break;
 		case 16:
		  jrDance();
 		  break;
 		case 17:
		  momWatch();
 		  break;
 		case 18:
		  jrHop();
 		  break;
 		case 19:
		  jrCircle();
 		  break;
        default:
          parent.showStatus("Button "+i+" was pressed");
          break;
      }
    }

    public boolean mouseDrag(Event e, int x, int y) {
        // You will need to make extensive changes to this code
        // to implement the various camera navigation modes
 		float dx = (x - old_x);
 		float dy = (y - old_y);
 		float theta_x = (float) (((x - old_x) * Math.PI) / width);
 		float theta_y = (float) (((y - old_y) * Math.PI) / height);
		
        if (e.metaDown()) {
			if ( mode == SKY_CAM ) {
				model.push();
				model.translate( eyex, eyey, eyez );
				model.rotate( 1.0f, 0.0f, 0.0f, toRadians( camera_angles[0] ) );
				model.rotate( 0.0f, 1.0f, 0.0f, toRadians( camera_angles[1] ) );
				model.rotate( 0.0f, 0.0f, 1.0f, toRadians( camera_angles[2] ) );
				model.translate( theta_x, theta_y, 0.0f );
				Vertex3D new_eye = model.transform( new Vertex3D( 0.0f, 0.0f, 0.0f ) );
				model.pop();
				
				
				lookatx += new_eye.x - eyex;
				lookaty += new_eye.y - eyey;
				lookatz += new_eye.z - eyez;
			
			    eyex = new_eye.x;
				eyey = new_eye.y;
				eyez = new_eye.z;

				view.pop();
				view.push();
	
		       	view.lookAt( eyex,    eyey,    eyez, 
							 lookatx, lookaty, lookatz, 
							 upx,     upy,     upz);
			}

			if ( mode == MOM_CAM ) {
				mom_angles[2] += dy;
				computeMomEyeAndLookat();
		  		view.pop();
          		view.push();
          		view.lookAt(momEye.x, momEye.y, momEye.z, momLookat.x, momLookat.y, momLookat.z, upx, upy, upz);
			}
			
			if ( mode == JR_CAM ) {
				jr_angles[2] += dy;
				computeJrEyeAndLookat();
		  		view.pop();
          		view.push();
          		view.lookAt(jrEye.x, jrEye.y, jrEye.z, jrLookat.x, jrLookat.y, jrLookat.z, upx, upy, upz);
			}
        } else {
			if ( mode == SKY_CAM ) {
				Vertex3D l = new Vertex3D( eyex-lookatx, eyey-lookaty, eyez-lookatz );
				float distance  = (float) ( Math.sqrt( l.x*l.x + l.y*l.y + l.z*l.z ) );

				System.out.println( "\nold lookat = " + lookatx + ", " + lookaty + ", " + lookatz );

				camera_angles[0] += theta_y*5;
				camera_angles[1] += theta_x*5;
				
				model.push();
				model.translate( eyex, eyey, eyez );
				model.rotate( 1.0f, 0.0f, 0.0f, toRadians( camera_angles[0] ) );
				model.rotate( 0.0f, 1.0f, 0.0f, toRadians( camera_angles[1] ) );
				model.rotate( 0.0f, 0.0f, 1.0f, toRadians( camera_angles[2] ) );
				model.translate( 0.0f, 0.0f, -distance );
				Vertex3D lookat_point = model.transform( new Vertex3D( 0.0f, 0.0f, 0.0f ) );
				model.pop();
		
		        lookatx = lookat_point.x;
				lookaty = lookat_point.y;
				lookatz = lookat_point.z;

				System.out.println( "new lookat = " + lookatx + ", " + lookaty + ", " + lookatz );

				view.pop();
				view.push();

	       	 	view.lookAt( eyex,    eyey,    eyez, 
							 lookatx, lookaty, lookatz, 
							 upx,     upy,     upz);
			}

			if ( mode == MOM_CAM ) {
				mom_angles[0] += dx;
				mom_angles[3] += dy;
				computeMomEyeAndLookat();
		  		view.pop();
          		view.push();
          		view.lookAt(momEye.x, momEye.y, momEye.z, momLookat.x, momLookat.y, momLookat.z, upx, upy, upz);
			}
			
			if ( mode == JR_CAM ) {
				jr_angles[0] += dx;
				jr_angles[3] += dy;
				computeJrEyeAndLookat();
		  		view.pop();
          		view.push();
          		view.lookAt(jrEye.x, jrEye.y, jrEye.z, jrLookat.x, jrLookat.y, jrLookat.z, upx, upy, upz);
			}
        }
		
 		old_x = x;
		old_y = y;
        DrawScene();
        repaint();
        return true;
    }
	
	
    public void zoomIn() {
        // Show the zooming capability.

			model.push();
			model.translate( eyex, eyey, eyez );
			model.rotate( 1.0f, 0.0f, 0.0f, toRadians( camera_angles[0] ) );
			model.rotate( 0.0f, 1.0f, 0.0f, toRadians( camera_angles[1] ) );
			model.rotate( 0.0f, 0.0f, 1.0f, toRadians( camera_angles[2] ) );
			model.translate( 0.0f, 0.0f, -10.0f );
			Vertex3D new_eye = model.transform( new Vertex3D( 0.0f, 0.0f, 0.0f ) );
			model.pop();
			
			
			lookatx += new_eye.x - eyex;
			lookaty += new_eye.y - eyey;
			lookatz += new_eye.z - eyez;
			
			eyex = new_eye.x;
			eyey = new_eye.y;
			eyez = new_eye.z;

			view.pop();
			view.push();
	
		    view.lookAt( eyex,    eyey,    eyez, 
						 lookatx, lookaty, lookatz, 
						 upx,     upy,     upz);
	        DrawScene();
    	    repaint();
    }
	
	
    public void zoomOut() {
        // Show the zooming capability.

			model.push();
			model.translate( eyex, eyey, eyez );
			model.rotate( 1.0f, 0.0f, 0.0f, toRadians( camera_angles[0] ) );
			model.rotate( 0.0f, 1.0f, 0.0f, toRadians( camera_angles[1] ) );
			model.rotate( 0.0f, 0.0f, 1.0f, toRadians( camera_angles[2] ) );
			model.translate( 0.0f, 0.0f, 10.0f );
			Vertex3D new_eye = model.transform( new Vertex3D( 0.0f, 0.0f, 0.0f ) );
			model.pop();
			
			
			lookatx += new_eye.x - eyex;
			lookaty += new_eye.y - eyey;
			lookatz += new_eye.z - eyez;
			
			eyex = new_eye.x;
			eyey = new_eye.y;
			eyez = new_eye.z;

			view.pop();
			view.push();
	
		    view.lookAt( eyex,    eyey,    eyez, 
						 lookatx, lookaty, lookatz, 
						 upx,     upy,     upz);
	        DrawScene();
    	    repaint();
    }
	
    public void computeMomEyeAndLookat() {
		MatrixStack m = new MatrixStack();
		m.loadIdentity();
        m.push();
        m.translate(momx, momy, momz);
        m.rotate(0.0f, 0.0f, 1.0f, toRadians(mom_angles[0]) );
        m.translate(0.0f, 0.0f, 2.5f);
        m.rotate(0.0f, 1.0f, 0.0f, toRadians(mom_angles[1]));
        m.translate(12.0f, 0.0f, 0.0f);
        m.rotate(0.0f, 1.0f, 0.0f, toRadians(mom_angles[2]));
        m.translate(12.0f, 0.0f, 0.0f);
		momPivot  = m.transform( new Vertex3D(0f, 0f, 0f ) ); 
        m.rotate(0.0f, 1.0f, 0.0f, toRadians(mom_angles[3]));
        m.translate( 1.0f, 0.0f, -1.0f );
		momEye    =  m.transform( new Vertex3D(0f, 0f, 0f ) );
        m.translate( 0.0f, 0.0f, -1.0f );
		momLookat = m.transform( new Vertex3D(0f, 0f, 0f) );
        m.pop();
		ballEye = new Vertex3D( ballx, bally, ballz + 4.0f );
    }
	
    public void computeJrEyeAndLookat() {
		MatrixStack m = new MatrixStack();
		m.loadIdentity();
        m.push();
        m.translate(jrx, jry, jrz);
        m.scale(0.75f, 0.75f, 0.75f);
        m.rotate(0.0f, 0.0f, 1.0f, toRadians(jr_angles[0]));
        m.translate(0.0f, 0.0f, 2.5f);
        m.rotate(0.0f, 1.0f, 0.0f, toRadians(jr_angles[1]));
        m.translate(6.0f, 0.0f, 0.0f);
        m.rotate(0.0f, 1.0f, 0.0f, toRadians(jr_angles[2]));
        m.translate(6.0f, 0.0f, 0.0f);
		jrPivot  = m.transform( new Vertex3D(0f, 0f, 0f ) ); 
        m.rotate(0.0f, 1.0f, 0.0f, toRadians(jr_angles[3]));
        m.translate( 1.0f, 0.0f, -1.0f );
		jrEye    = m.transform( new Vertex3D(0f, 0f, 0f ) );
        m.translate( 0.0f, 0.0f, -1.0f );
		jrLookat = m.transform( new Vertex3D(0f, 0f, 0f) );
        m.pop();
		ballEye = new Vertex3D( ballx, bally, ballz + 4.0f );
	}
	
	public float getAngle( Vertex3D a, Vertex3D b ) {
		float a_mag = (float)( Math.sqrt( a.x*a.x + a.y*a.y + a.z*a.z) );
		float b_mag = (float)( Math.sqrt( b.x*b.x + b.y*b.y + b.z*b.z) );
		
		float dot   = a.x*b.x + a.y*b.y + a.z*b.z;
	
		return toDegrees( (float)Math.acos( dot/(a_mag*b_mag) ) );
	}
	
	public float dist( Vertex3D a, Vertex3D b ) {
		float dx = a.x - b.x;
		float dy = a.y - b.y;
		float dz = a.z - b.z;
		
		return (float)( Math.sqrt( dx*dx + dy*dy +dz*dz ) );
	}
	
    private float toDegrees( float radians ) {
      return (float) (radians*180.0f / Math.PI);
    }

    public void momNod() {
		Thread t = new Thread();
		t.run();
		float oa = mom_angles[3];
		double k = 2;
		for ( double a=Math.PI/k; a<=Math.PI*2; a+=Math.PI/k ) {
			mom_angles[3] = (float)( oa + Math.sin(a)*15);
			if ( mode == SKY_CAM )
				processButton( 1 );
			if ( mode == MOM_CAM )
				processButton( 2 );
			if ( mode == JR_CAM )
				processButton( 3 );
			try{ t.sleep( 50l ); } catch ( Exception e ) {}
		}
    }

    public void jrNod() {
		Thread t = new Thread();
		t.run();
		float oa = jr_angles[3];
		double k = 2;
		for ( double a=Math.PI/k; a<=Math.PI*4; a+=Math.PI/k ) {
			jr_angles[3] = (float)( oa + Math.sin(a)*15);
			if ( mode == SKY_CAM )
				processButton( 1 );
			if ( mode == MOM_CAM )
				processButton( 2 );
			if ( mode == JR_CAM )
				processButton( 3 );
			try{ t.sleep( 50l ); } catch ( Exception e ) {}
		}
    }
	
	
    public void jrDance() {
		float oa[] = new float[4];
		
		oa[0] = jr_angles[0];
		oa[1] = jr_angles[1];
		oa[2] = jr_angles[2];
		oa[3] = jr_angles[3];
		double k = 8;
		
		Thread t = new Thread();
		t.run();
		for ( double a=Math.PI/k; a<=Math.PI*10; a+=Math.PI/k ) {
			jr_angles[0] = (float)( oa[0] + Math.sin(a)*70 );
			jr_angles[1] = (float)( oa[1] - Math.sin(a)*30 );
			jr_angles[2] = (float)( oa[2] + Math.sin(a)*20 );
			jr_angles[3] = (float)( oa[3] + Math.sin(a*2)*30 );
			if ( mode == SKY_CAM )
				processButton( 1 );
			if ( mode == MOM_CAM )
				processButton( 2 );
			if ( mode == JR_CAM )
				processButton( 3 );
			try{ t.sleep( 50l ); } catch ( Exception e ) {}
		}
		
		jr_angles[0] = oa[0];
		jr_angles[1] = oa[1];
		jr_angles[2] = oa[2];
		jr_angles[3] = oa[3];
		if ( mode == SKY_CAM )
			processButton( 1 );
		if ( mode == MOM_CAM )
			processButton( 2 );
		if ( mode == JR_CAM )
			processButton( 3 );
    }
	
	
    public void momWatch() {
		float oa[] = new float[4];
		
		oa[0] = jr_angles[0];
		oa[1] = jr_angles[1];
		oa[2] = jr_angles[2];
		oa[3] = jr_angles[3];
		double k = 8;
		
		Thread t = new Thread();
		t.run();
		for ( double a=Math.PI/k; a<=Math.PI*10; a+=Math.PI/k ) {
			jr_angles[0] = (float)( oa[0] + Math.sin(a)*70 );
			jr_angles[1] = (float)( oa[1] - Math.sin(a)*30 );
			jr_angles[2] = (float)( oa[2] + Math.sin(a)*20 );
			jr_angles[3] = (float)( oa[3] + Math.sin(a*2)*30 );
			processButton(4);
			if ( mode == SKY_CAM )
				processButton( 1 );
			if ( mode == MOM_CAM )
				processButton( 2 );
			if ( mode == JR_CAM )
				processButton( 3 );
			try{ t.sleep( 50l ); } catch ( Exception e ) {}
		}
		
		jr_angles[0] = oa[0];
		jr_angles[1] = oa[1];
		jr_angles[2] = oa[2];
		jr_angles[3] = oa[3];
		if ( mode == SKY_CAM )
			processButton( 1 );
		if ( mode == MOM_CAM )
			processButton( 2 );
		if ( mode == JR_CAM )
			processButton( 3 );
    }
	
	
    public void jrHop() {
		float oa[] = new float[4];
		float jrxo = jrx;
		float jryo = jry;
		float jrzo = jrz;
		oa[0] = jr_angles[0];
		oa[1] = jr_angles[1];
		oa[2] = jr_angles[2];
		oa[3] = jr_angles[3];
		double k = 8;
		
		Thread t = new Thread();
		t.run();
		for ( double a=Math.PI/k; a<=Math.PI*2; a+=Math.PI/k ) {
			jr_angles[1] = (float)( oa[1] - Math.abs( Math.sin(a)*30 ) );
			jr_angles[2] = (float)( oa[2] + Math.abs( Math.sin(a)*30 ) );
			jr_angles[3] = (float)( oa[3] - Math.sin(a*2)*20 );
			jrz = jrzo + (float)( Math.abs( Math.sin(a)*4 ) );
			processButton(4);
			if ( mode == SKY_CAM )
				processButton( 1 );
			if ( mode == MOM_CAM )
				processButton( 2 );
			if ( mode == JR_CAM )
				processButton( 3 );
			try{ t.sleep( 50l ); } catch ( Exception e ) {}
		}
		
		jr_angles[0] = oa[0];
		jr_angles[1] = oa[1];
		jr_angles[2] = oa[2];
		jr_angles[3] = oa[3];
		jrx = jrxo;
		jry = jryo;
		jrz = jrzo;
		if ( mode == SKY_CAM )
			processButton( 1 );
		if ( mode == MOM_CAM )
			processButton( 2 );
		if ( mode == JR_CAM )
			processButton( 3 );
    }
	
	
    public void jrCircle() {
		float oa[] = new float[4];
		float jrxo = jrx;
		float jryo = jry;
		float jrzo = jrz;
		oa[0] = jr_angles[0];
		oa[1] = jr_angles[1];
		oa[2] = jr_angles[2];
		oa[3] = jr_angles[3];
		double k = 8;
		
		Thread t = new Thread();
		t.run();
		for ( double a=0; a<Math.PI*2*4; a+=Math.PI/k ) {
			jr_angles[0] = (float)( oa[0] - a/4/k*360 );
			jr_angles[1] = (float)( oa[1] - Math.abs( Math.sin(a)*40 ) );
			jr_angles[2] = (float)( oa[2] + Math.abs( Math.sin(a)*30 ) );
			jr_angles[3] = (float)( oa[3] - Math.sin(a*2)*30 );
			jrx = jrxo + (float)( -(1-Math.cos(a/4))*4 );
			jry = jryo - (float)( -(1-Math.sin(a/4))*6 );
			jrz = jrzo + (float)( Math.abs( Math.sin(a)*3 ) );
			processButton(4);
			if ( mode == SKY_CAM )
				processButton( 1 );
			if ( mode == MOM_CAM )
				processButton( 2 );
			if ( mode == JR_CAM )
				processButton( 3 );
			try{ t.sleep( 50l ); } catch ( Exception e ) {}
		}
		
		jr_angles[0] = oa[0];
		jr_angles[1] = oa[1];
		jr_angles[2] = oa[2];
		jr_angles[3] = oa[3];
		jrx = jrxo;
		jry = jryo;
		jrz = jrzo;
		if ( mode == SKY_CAM )
			processButton( 1 );
		if ( mode == MOM_CAM )
			processButton( 2 );
		if ( mode == JR_CAM )
			processButton( 3 );
    }
}
/*

				
				Matrix4x4 m = new Matrix4x4();
				m.lookAt( eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
				Vertex3D l = new Vertex3D( eyex-lookatx, eyey-lookaty, eyez-lookatz );
				float mag = (float) ( Math.sqrt( l.x*l.x + l.y*l.y + l.z*l.z ) );
				System.out.println( m.transform( new Vertex3D( l.x/mag, l.y/mag, l.z/mag ) ) );
				System.out.println( m );

			Vertex3D look = new Vertex3D( eyex-lookatx, eyey-lookaty, eyez-lookatz );
			Vertex3D up   = new Vertex3D( upx, upy, upz );
*/
