package jocode;


import javax.media.opengl.glu.*;

import jomodel.*;


/**
 * Camera class by Philipp Crocoll at CodeColony (codecolony.de).
 * Ported from C++ to Java by mark napier (potatoland.org).
 *
 * Can move camera position forward/back, pan left/right, shift up/down, and
 * rotate around X, Y, Z axes.
 *
 * Uses gluLookat() to orient scene to camera position.  See Camera.render().
 *
 * To use:
 *    renderScene() {
 *       gl.glMatrixMode(GL.GL_MODELVIEW);
 *       gl.glLoadIdentity();
 *       camera.render();
 *       // render scene ...
 *    }
 *
 * Requires JOVector to perform vector operations.
 *
 * jul13,2006: added move(x,y,z).  added ctor(pos,dir,up).
 */
public class JOCamera {
	
    static final float PI_DIV_180 = 0.0174532925f;
	
    public static GLU glu;
    
	public JOVector viewDir;
	public JOVector rightVector;
	public JOVector upVector;
	public JOVector position;
	public float rotatedX, rotatedY, rotatedZ;
	
	public JOCamera() {
	    glu = new GLU();
	    
		setCamera(0f, 0f, 0f,    // position at origin
				  0f, 0f, -1f,   // looking down Z axis
				  0f, 1f, 0f );  // camera up axis is straight up Y axis
	}
	
	public JOCamera(float posX, float posY, float posZ,
			        float dirX, float dirY, float dirZ,
			        float upX,  float upY,  float upZ) {
		setCamera(posX, posY, posZ, 
		          dirX, dirY, dirZ, 
		          upX,  upY,  upZ );
	}
	
	/**
	 * Set the camera position, view direction and up vector.  NOTE: direction
	 * is direction the camera is facing NOT a target position (as in gluLookAt()).  
	 * 
	 * @param posX   Position of camera
	 * @param posY
	 * @param posZ
	 * @param dirX   Direction camera is facing
	 * @param dirY
	 * @param dirZ
	 * @param upX    Up vector
	 * @param upY
	 * @param upZ
	 */
	public void setCamera(float posX, float posY, float posZ,
			              float dirX, float dirY, float dirZ,
			              float upX,  float upY,  float upZ) {
		if (upX == 0 && upY == 0 && upZ == 0) {
			System.out.println("JOCamera.setCamera(): Up vector needs to be defined");
			upX=0; upY=1; upZ=0;
		}
		if (dirX == 0 && dirY == 0 && dirZ == 0) {
			System.out.println("JOCamera.setCamera(): ViewDirection vector needs to be defined");
			dirX=0; dirY=0; dirZ=-1;
		}
		position 	= new JOVector(posX, posY, posZ);
		viewDir 	= new JOVector(dirX, dirY, dirZ);
		upVector 	= new JOVector(upX, upY, upZ);
		rightVector	= JOVector.crossProduct(viewDir, upVector);
		rotatedX = rotatedY = rotatedZ = 0.0f;  // TO DO: should set these to correct values
	}
	
	/**
	 * Set the camera to look at a target.  Positions the camera on the same X and Y
	 * as the target, at the Z value specified by the distance param, looking down 
	 * the Z axis.
	 *  
	 * @param targetX     camera will face this XYZ
	 * @param targetY
	 * @param targetZ
	 * @param distance    distance from target
	 */
	public void setCamera(float targetX, float targetY, float targetZ,  float distance) {
		position 	= new JOVector(targetX, targetY, targetZ+distance);
		viewDir 	= new JOVector(0, 0, -1);
		upVector 	= new JOVector(0, 1, 0);
		rightVector	= JOVector.crossProduct(viewDir, upVector);
		rotatedX = rotatedY = rotatedZ = 0.0f;
	}
	
	/**
	 * Move camera position in the given direction
	 */
	public void viewDir(JOVector direction) {
		viewDir = direction;
		rightVector	= JOVector.crossProduct(viewDir, upVector);
	}
	
	/**
	 * Move camera position in the given direction
	 */
	public void move(JOVector direction) {
		position = JOVector.add(position, direction);
	}
	
	/**
	 * Move camera position in the given direction
	 */
	public void move(float x, float y, float z) {
		JOVector Direction = new JOVector(x,y,z);
		position = JOVector.add(position, Direction);
	}
	
	/**
	 * Move camera to the given xyz
	 */
	public void moveTo(float x, float y, float z) {
		position = new JOVector(x, y, z);
	}
	
	public void rotateX(float angle) {
		rotatedX += angle;
		
		//Rotate viewdir around the right vector:
		viewDir = JOVector.normalize(
		        JOVector.add(
		                JOVector.multiply(viewDir, (float) Math.cos(angle * PI_DIV_180)),
		                JOVector.multiply(upVector, (float) Math.sin(angle * PI_DIV_180))
		        )
		);
		
		//now compute the new UpVector (by cross product)
		upVector = JOVector.multiply(JOVector.crossProduct(viewDir, rightVector), -1);
	}
	
	public void rotateY(float angle) {
		rotatedY += angle;
		
		//Rotate viewdir around the up vector:
		viewDir = JOVector.normalize(
				JOVector.sub(
						JOVector.multiply(viewDir, (float) Math.cos(angle * PI_DIV_180)),
						JOVector.multiply(rightVector, (float) Math.sin(angle * PI_DIV_180))
				));
		
		//now compute the new RightVector (by cross product)
		rightVector = JOVector.crossProduct(viewDir, upVector);
	}
	
	public void rotateZ(float angle) {
		rotatedZ += angle;
		
		//Rotate viewdir around the right vector:
		rightVector = JOVector.normalize(
				JOVector.add(
						JOVector.multiply(rightVector, (float) Math.cos(angle * PI_DIV_180)),
						JOVector.multiply(upVector, (float) Math.sin(angle * PI_DIV_180))
				));
		
		//now compute the new UpVector (by cross product)
		upVector = JOVector.multiply(JOVector.crossProduct(viewDir, rightVector), -1);
	}
	
	/**
	 * Rotate the camera around the absolute vertical axis (0,1,0), NOT around the cameras Y axis.
	 * This simulates a person looking up or down and rotating in place.  You will rotate your 
	 * body around the vertical axis, while your head remains tilted at the same angle.  
	 * @param angle the angle to rotate around the vertical axis in degrees
	 */
	public void rotateV(float angle) {
		// Make a matrix to rotate the given number of degrees around Y axis
		JOMatrix M = JOMatrix.rotateMatrix(0, (float)Math.toRadians(angle), 0);
		// rotate the view vector 
		JOVector vd = M.transform(viewDir);
		// the up vector is perpendicular to the old view and the new view
		upVector = (angle > 0)? JOVector.crossProduct(viewDir,vd) : JOVector.crossProduct(vd,viewDir);
		// the right vector is perpendicular to the new view and Up vectors
		rightVector = JOVector.crossProduct(vd,upVector);
		// set the view direction
		viewDir = vd;
		rotatedY += angle;
	}
	
	public void moveForward(float distance) {
		position = JOVector.add(position, JOVector.multiply(viewDir, -distance));
	}
	
	public void moveUpward(float distance) {
		position = JOVector.add(position, JOVector.multiply(upVector, distance));
	}
	
	public void strafeRight(float distance) {
		position = JOVector.add(position, JOVector.multiply(rightVector, distance));
	}
	
	/**
	 * Call GLU.gluLookAt() to set view position, direction and orientation.  Be 
	 * sure that the modelview matrix is current before calling render() 
	 * (glMatrixMode(GL_MODEL_VIEW)).
	 */
	public void render() {
		//The point at which the camera looks:
		JOVector viewPoint = JOVector.add(position, viewDir);
		
		//as we know the up vector, we can easily use gluLookAt:
		glu.gluLookAt(position.x, position.y, position.z,
				      viewPoint.x, viewPoint.y, viewPoint.z,
				      upVector.x, upVector.y, upVector.z);
		
//		System.out.println(position.x + "," + position.y + "," + position.z + "  " + 
//				viewDir.x + "," + viewDir.y + "," + viewDir.z + "  " + 
//				upVector.x + "," + upVector.y + "," + upVector.z + "  " 
//				);
	}
	
	/** 
	 * Return the current camera view direction
	 */
	public JOVector getViewDir() {
		return viewDir;
	}	
}
