/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</copyright> */
/* 
 * camera class 
 */
camera = function() {
	this.proj = mat4.identity();
	this.view = mat4.identity();
	this.world =  mat4.identity();
	this.viewProj = mat4.identity();
	this.invViewProj = mat4.identity();
	this.frustum = [];
	this.frustumPts = [];
	this.controller = null;
	
	this.setPerspective = function(fov, aratio, near, far) 
	{
		this.ortho			= null;
		this.persp			= {};
		this.persp.fov		= fov;
		this.persp.aratio	= aratio;
		this.persp.near		= near;
		this.persp.far		= far;
		this.proj			= mat4.perspective(fov, aratio, near, far);
		this.recalc();
	}
	
	this.reset = function() {
		this.world = mat4.identity();       
		this.recalc();
	}

	this.copy = function(cam) {
		mat4.inplace_copy(this.view, cam.view);
		mat4.inplace_copy(this.world, cam.world);
		mat4.inplace_copy(this.proj, cam.proj);
		mat4.inplace_copy(this.viewProj, cam.viewProj);
		mat4.inplace_copy(this.invViewProj, cam.invViewProj);
		this.frustum = cam.frustum.slice();
		this.frustumPts = cam.frustumPts.slice();
	}

	this.recalc = function() {			
		// update frustum planes
		this.frustum = [];
		var vp = this.viewProj;
		
		normalizePlane = function( p ) {
			var len = vec3.length( p ); 
			if( Math.abs( 1.0 - len ) > 0.001 ) {
				p[0] /= len;	
				p[1] /= len;	
				p[2] /= len;	
				p[3] /= len;	
			}
			return p;
		}	

	/* This is the old way 
		var t = this.persp.near * Math.tan(0.017453292519943295769236 * this.persp.fov * 0.5);
		var r = t * this.persp.aratio;
		var u = t;
		var l = -r;
		var b = -u;

		tview = mat4.transpose(this.view);
		this.frustum.push( normalizePlane( mat4.transformPoint(tview, [this.persp.near, 0.0, l, 0.0] ) ) );     // left
		this.frustum.push( normalizePlane( mat4.transformPoint(tview, [-this.persp.near, 0.0, -r, 0.0] ) ) );   // right
		this.frustum.push( normalizePlane( mat4.transformPoint(tview, [0.0, this.persp.near, b, 0.0] ) ) );     // bottom
		this.frustum.push( normalizePlane( mat4.transformPoint(tview, [0.0, -this.persp.near, -u, 0.0] ) ) );   // top
		this.frustum.push( normalizePlane( mat4.transformPoint(tview, [0.0, 0.0, -1.0, -this.persp.near] ) ) ); // near
		this.frustum.push( normalizePlane( mat4.transformPoint(tview, [0.0, 0.0, 1.0, this.persp.far] ) ) );    // far
	*/  	
		var l = normalizePlane( [vp[3] + vp[0], vp[7] + vp[4], vp[11] + vp[8], vp[15]+vp[12]] ); 
		var r = normalizePlane( [vp[3] - vp[0], vp[7] - vp[4], vp[11] - vp[8], vp[15]-vp[12]] ); 
		var t = normalizePlane( [vp[3] - vp[1], vp[7] - vp[5], vp[11] - vp[9], vp[15]-vp[13]] ); 
		var b = normalizePlane( [vp[3] + vp[1], vp[7] + vp[5], vp[11] + vp[9], vp[15]+vp[13]] ); 
		var n = normalizePlane( [vp[3] + vp[2], vp[7] + vp[6], vp[11] + vp[10], vp[15]+vp[14]] ); 
		var f = normalizePlane( [vp[3] - vp[2], vp[7] - vp[6], vp[11] - vp[10], vp[15]-vp[14]] );

		this.frustum.push(l);
		this.frustum.push(r);
		this.frustum.push(t);
		this.frustum.push(b);
		this.frustum.push(n);
		this.frustum.push(f);
		
		// update frustum points
		this.frustumPts = [];	
		var invvp = this.viewProj;	
		this.frustumPts.push( mat4.transformPoint( invvp, [-1,-1,-1] ) );
		this.frustumPts.push( mat4.transformPoint( invvp, [-1,1,-1] ) );
		this.frustumPts.push( mat4.transformPoint( invvp, [1,1,-1] ) );
		this.frustumPts.push( mat4.transformPoint( invvp, [1,-1,-1] ) );
		this.frustumPts.push( mat4.transformPoint( invvp, [-1,-1,1] ) );
		this.frustumPts.push( mat4.transformPoint( invvp, [-1,1,1] ) );
		this.frustumPts.push( mat4.transformPoint( invvp, [1,1,1] ) );
		this.frustumPts.push( mat4.transformPoint( invvp, [1,-1,1] ) );
	};

	this.setWorld = function(m) {
		this.world = m;
		this.view = mat4.inverse(m);
		this.viewProj = mat4.mul(  this.view, this.proj );
		this.invViewProj = mat4.inverse(this.viewProj);
		this.recalc();
	}

	this.setView = function(m) {
		this.view = m;
		this.world = mat4.inverse(m);
		this.viewProj = mat4.mul( this.view, this.proj );
		this.invViewProj = mat4.inverse(this.viewProj);
		this.recalc();
	}

	this.setLookAt = function(eyePos, targetPos, upVec) {
		this.setWorld( mat4.lookAt(eyePos, targetPos, upVec) );
		//this.recalc();
	};

	this.setPerspective = function(fov, aratio, near, far) {
		this.ortho			= null;
		this.persp			= {};
		this.persp.fov		= fov;
		this.persp.aratio	= aratio;
		this.persp.near		= near;
		this.persp.far		= far;
		this.proj			= mat4.perspective(fov, aratio, near, far);
		this.recalc();
	}

	this.setOrthographic = function( l, r, t, b, n, f ) {
		this.persp			= null;
		this.ortho			= {};
		this.ortho.left = l;
		this.ortho.right = r;
		this.ortho.top = t;
		this.ortho.bottom = b;
		this.ortho.near = n;
		this.ortho.far = f;
		this.proj = mat4.orthographic(l, r, t, b, n, f);	
		this.recalc();
	}

	this.onResize = function(x, y, width, height) {
		if( this.persp ) {
			this.setPerspective(this.persp.fov, width / height, this.persp.near, this.persp.far);
		}
		if( this.ortho ) {
			this.setOrthographic(x, x + width, y, y + height, this.ortho.near, this.ortho.far);
		}
	}

	this.zNear = function() {
		if( this.persp ) {
			return this.persp.near;
		}
		
		if( this.ortho ) {
			return this.ortho.near;
		}
		
		return 0.0;
	}

	this.zFar = function() {
		if( this.persp ) {
			return this.persp.far;
		}
		
		if( this.ortho ) {
			return this.ortho.far;
		}
		
		return 0.0;
	}

	// this is used by ambient occlusion... 
	this.getFTR = function() {
	  var fovyRad = (this.persp.fov * 0.5) * Math.PI / 180.0;
	  return [
		Math.tan(fovyRad) * this.persp.far, 
		Math.tan(fovyRad / this.persp.aratio) * this.persp.far,
		this.persp.far ];
	}

	this.attachCameraToNode = function( node )
	{
		this.controller = node;
	}
}


/**	Camera Manager
 * This class is used to manage the active camera. It provides functionality 
 * for getting and setting the active camera, as well as providing stack operations 
 * to switch to and from multiple cameras. 
 */
cameraManager = function() {
	this.stack = [];
		
	/* Set the active camera.
	 * This function sets the active camera to the given camera. 
	 */			
	this.setActiveCamera = function(c) {
		// pop the active camera off the stack.
		if( this.stack.length > 0 ) {
			this.stack.pop();
		}
		// push the given camera onto the stack.
		this.stack.push(c);
	}
	
	/* Get the active camera. 
	 * The active camera always resides at the top of the stack. 
	 */
	this.getActiveCamera = function() {
		if( this.stack.length > 0 ) {
			return this.stack[this.stack.length - 1];
		} else {
			return null;
		}
	}

	/* Push a camera onto the stack. 
	 * The given camera becomes the active camera.
	 */
	this.pushCamera = function(c) {
		this.stack.push(c);
	}

	/* Pop a camera off the stack. 
	 * Returns the camera that was popped.
	 * The next camera on the stack becomes active.
	 */
	this.popCamera = function() {
		return this.stack.pop();
	}
	
	this.onResize = function(x, y, w, h) {
		var i = this.stack.length - 1;
		while( i >= 0 ) {
			this.stack[i].onResize( x, y, w, h ); 
			i--;
		}
	}
}