package glapp;

import org.lwjgl.util.glu.*;
import glmodel.GL_Vector;
import glmodel.GL_Matrix;

/**
 * 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() {
 *       GL11.glMatrixMode(GL11.GL_MODELVIEW);
 *       GL11.glLoadIdentity();
 *       camera.Render();
 *       // render scene ...
 *    }
 *
 * Requires GL_Vector to perform vector operations.
 *
 * jul13,2006: added move(x,y,z).  added ctor(pos,dir,up).
 */
public class GLCamera {
	static final float PIdiv180 = 0.0174532925f;
	public GL_Vector ViewDir;
	public GL_Vector RightVector;
	public GL_Vector UpVector;
	public GL_Vector Position;
	public float RotatedX, RotatedY, RotatedZ;
	
	public GLCamera() {
		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 GLCamera(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("GLCamera.setCamera(): Up vector needs to be defined");
			upx=0; upy=1; upz=0;
		}
		if (dirx == 0 && diry == 0 && dirz == 0) {
			System.out.println("GLCamera.setCamera(): ViewDirection vector needs to be defined");
			dirx=0; diry=0; dirz=-1;
		}
		Position 	= new GL_Vector(posx, posy, posz);
		ViewDir 	= new GL_Vector(dirx, diry, dirz);
		UpVector 	= new GL_Vector(upx, upy, upz);
		RightVector	= GL_Vector.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 GL_Vector(targetX, targetY, targetZ+distance);
		ViewDir 	= new GL_Vector(0, 0, -1);
		UpVector 	= new GL_Vector(0, 1, 0);
		RightVector	= GL_Vector.crossProduct(ViewDir, UpVector);
		RotatedX = RotatedY = RotatedZ = 0.0f;
	}
	
	/**
	 * Move camera position in the given direction
	 */
	public void viewDir(GL_Vector direction) {
		ViewDir = direction;
		RightVector	= GL_Vector.crossProduct(ViewDir, UpVector);
	}
	
	/**
	 * Move camera position in the given direction
	 */
	public void Move(GL_Vector Direction) {
		Position = GL_Vector.add(Position, Direction);
	}
	
	/**
	 * Move camera position in the given direction
	 */
	public void Move(float x, float y, float z) {
		GL_Vector Direction = new GL_Vector(x,y,z);
		Position = GL_Vector.add(Position, Direction);
	}
	
	/**
	 * Move camera to the given xyz
	 */
	public void MoveTo(float x, float y, float z) {
		Position = new GL_Vector(x, y, z);
	}
	
	public void RotateX(float Angle) {
		RotatedX += Angle;
		
		//Rotate viewdir around the right vector:
		ViewDir = GL_Vector.normalize(
				GL_Vector.add(
						GL_Vector.multiply(ViewDir, (float) Math.cos(Angle * PIdiv180)),
						GL_Vector.multiply(UpVector, (float) Math.sin(Angle * PIdiv180))
				)
		);
		
		//now compute the new UpVector (by cross product)
		UpVector = GL_Vector.multiply(GL_Vector.crossProduct(ViewDir, RightVector), -1);
	}
	
	public void RotateY(float Angle) {
		RotatedY += Angle;
		
		//Rotate viewdir around the up vector:
		ViewDir = GL_Vector.normalize(
				GL_Vector.sub(
						GL_Vector.multiply(ViewDir, (float) Math.cos(Angle * PIdiv180)),
						GL_Vector.multiply(RightVector, (float) Math.sin(Angle * PIdiv180))
				));
		
		//now compute the new RightVector (by cross product)
		RightVector = GL_Vector.crossProduct(ViewDir, UpVector);
	}
	
	public void RotateZ(float Angle) {
		RotatedZ += Angle;
		
		//Rotate viewdir around the right vector:
		RightVector = GL_Vector.normalize(
				GL_Vector.add(
						GL_Vector.multiply(RightVector, (float) Math.cos(Angle * PIdiv180)),
						GL_Vector.multiply(UpVector, (float) Math.sin(Angle * PIdiv180))
				));
		
		//now compute the new UpVector (by cross product)
		UpVector = GL_Vector.multiply(GL_Vector.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
		GL_Matrix M = GL_Matrix.rotateMatrix(0,(float)Math.toRadians(Angle),0);
		// rotate the view vector 
		GL_Vector vd = M.transform(ViewDir);
		// the up vector is perpendicular to the old view and the new view
		UpVector = (Angle > 0)? GL_Vector.crossProduct(ViewDir,vd) : GL_Vector.crossProduct(vd,ViewDir);
		// the right vector is perpendicular to the new view and Up vectors
		RightVector = GL_Vector.crossProduct(vd,UpVector);
		// set the view direction
		ViewDir = vd;
		RotatedY += Angle;
	}
	
	public void MoveForward(float Distance) {
		Position = GL_Vector.add(Position, GL_Vector.multiply(ViewDir, -Distance));
	}
	
	public void MoveUpward(float Distance) {
		Position = GL_Vector.add(Position, GL_Vector.multiply(UpVector, Distance));
	}
	
	public void StrafeRight(float Distance) {
		Position = GL_Vector.add(Position, GL_Vector.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:
		GL_Vector ViewPoint = GL_Vector.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 GL_Vector getViewDir() {
		return ViewDir;
	}
	
}
