function Scene3D(canvas)
{
	this.canvas=canvas;
	this.doClear=true;
	this.container=new Container3D();
	this.vanishingX=canvas.width*0.5;
	this.vanishingY=canvas.height*0.5;
	this.renderContext=canvas.getContext("2d");
	this.scenePoints=[];
	this.scenePoints2d=[];
	this.scales=[]; 
	this.items=[];
}
Scene3D.prototype.addItem=function(item)
{
	if(item.render!=null)
	{
		this.items[this.items.length]=item;
	}
	
	item.addToScene(this);
}
Scene3D.prototype.render=function(camera)
{
	//TRANSFORM 3D POINTS FROM CAMERA
	var numPoints=this.scenePoints.length/3;
	var curTrans=this.container.matrix.clone();
	
	
	var cameraTrans = new Matrix3D();
	
	cameraTrans.translate(-camera.x, -camera.y, -camera.z);
	cameraTrans.concat(camera.rotation.getInverse().getMatrix());
	
	
	curTrans.concat(cameraTrans);
	
	//WHAT THE ABOVE 4 lines were before
	//curTrans.concat(camera.trans);
	//console.log(this.scenePoints);
	var transformedPoints=curTrans.transformArray(this.scenePoints);
	//console.log(transformedPoints);
	//PROJECT POINTS TO BE 2D
	for(var i=0;i<numPoints;i++)
	{
		var i3=i*3;
		var i2=i*2;
		
		var x=transformedPoints[i3];
		var y=transformedPoints[i3+1];
		var z=transformedPoints[i3+2];
		
		var drawScale=camera.scale * camera.focalLength/(camera.focalLength+z);
		
		this.scales[i]=drawScale;
		
		this.scenePoints2d[i2]=camera.
		flipHack*x*drawScale+this.vanishingX;
		this.scenePoints2d[i2+1]=y*drawScale+this.vanishingY;
	}
	
	if(this.doClear)
	{
		this.renderContext.fillStyle="rgb(0, 0, 0)";
		this.renderContext.fillRect(0, 0, canvas.width, canvas.height);
	}
	
	this.renderContext.save();
	//NOW RENDER ITEMS
	for(var i=0;i<this.items.length;i++)
	{
		this.items[i].render();	
	}
	this.renderContext.restore();
}
/************************************************************************************************************************/
/************************************************************************************************************************/
/************************************************************************************************************************/
function Quaternion(w, x, y, z)
{
	this.w=w==null?1:w;
	this.x=x==null?0:x;
	this.y=y==null?0:y;
	this.z=z==null?0:z;
}

Quaternion.getFromEuler=function(rotationX, rotationY, rotationZ)
{
	var quat = new Quaternion();
	quat.fromEuler(rotationX, rotationY, rotationZ);
	return quat;
}

Quaternion.getFromAxisAngle=function(angle, x, y, z)
{
	var quat = new Quaternion();
	quat.fromAxisAngle(angle, x, y, z);
	return quat;
}

Quaternion.getDistance=function(qa, qb)
{
	dx = qb.x - qa.x;
	dy = qb.y - qa.y;
	dz = qb.z - qa.z;
	dw = qb.w - qa.w;
	return Math.sqrt(dx*dx + dy*dy + dz*dz + dw*dw);
}


Quaternion.prototype.invert=function()
{
	var s=this.w*this.w+this.x*this.x+this.y*this.y+this.z*this.z;
	this.w = this.w / s;
	this.x = -this.x / s;
	this.y = -this.y / s;
	this.z = -this.z / s;
}

Quaternion.prototype.getInverse=function()
{
	var tempQuat = new Quaternion(this.w, this.x, this.y, this.z);
	tempQuat.invert();
	return tempQuat;
}

Quaternion.prototype.getMatrix=function()
{
	var w=this.w;
	var x=this.x;
	var y=this.y;
	var z=this.z;
	
	var rVal=new Matrix3D(
		w*w+x*x-y*y-z*z, 	2*x*y-2*w*z, 		2*x*z+2*w*y, 		0,
		2*x*y+2*w*z, 		w*w-x*x+y*y-z*z, 	2*y*z-2*w*x, 		0,
		2*x*z-2*w*y, 		2*y*z+2*w*x, 		w*w-x*x-y*y+z*z, 	0,
		0, 					0, 					0, 					1
	);
	
	return rVal;
}

Quaternion.prototype.multiply=function(value)
{
	var w1=this.w;
	var w2=value.w;
	var x1=this.x;
	var x2=value.x;
	var y1=this.y;
	var y2=value.y;
	var z1=this.z;
	var z2=value.z;

	this.w=w1*w2 - x1*x2 - y1*y2 - z1*z2;
	this.x=w1*x2 + x1*w2 + y1*z2 - z1*y2;
	this.y=w1*y2 - x1*z2 + y1*w2 + z1*x2;
	this.z=w1*z2 + x1*y2 - y1*x2 + z1*w2;
}

Quaternion.prototype.fromEuler=function(rotationX, rotationY, rotationZ)
{
	/*
	Qx = [ cos(a/2), (sin(a/2), 0, 0)]
	Qy = [ cos(b/2), (0, sin(b/2), 0)]
	Qz = [ cos(c/2), (0, 0, sin(c/2))]
	*/
	
	var qX=new Quaternion(Math.cos(rotationX/2), Math.sin(rotationX/2), 0, 0);
	var qY=new Quaternion(Math.cos(rotationY/2), 0, Math.sin(rotationY/2), 0);
	var qZ=new Quaternion(Math.cos(rotationZ/2), 0, 0, Math.sin(rotationZ/2));

	this.multiply(qX);
	this.multiply(qY);
	this.multiply(qZ);
	
	return this;
}

Quaternion.prototype.fromAxisAngle = function(angle, x, y, z)
{
	var s = Math.sin(angle/2);
	var c = Math.cos(angle/2);
	var d = Math.sqrt(x*x + y*y + z*z);
	
	this.x = s*x/d;
	this.y = s*y/d;
	this.z = s*z/d;
	this.w = c;
}


var EPSILON=0.000001;
Quaternion.slerp=function(qa, qb, alpha)
{
	var angle = qa.w * qb.w + qa.x * qb.x + qa.y * qb.y + qa.z * qb.z;
 
	if (angle < 0.0)
	{
		 qa.x *= -1.0;
		 qa.y *= -1.0;
		 qa.z *= -1.0;
		 qa.w *= -1.0;
		 angle *= -1.0;
	}
	 
	 var scale;
	 var invscale;
	 
	if ((angle + 1.0) > EPSILON) // Take the shortest path
	 {
		if ((1.0 - angle) >= EPSILON)  // spherical interpolation
	     {
			 var theta = Math.acos(angle);
			 var invsintheta = 1.0 / Math.sin(theta);
			 scale = Math.sin(theta * (1.0-alpha)) * invsintheta;
			 invscale = Math.sin(theta * alpha) * invsintheta;
	     }
	     else // linear interploation
         {
			scale = 1.0 - alpha;
			invscale = alpha;
         }
	 }
	 else // long way to go...
     {
		 qb.y = -qa.y;
		 qb.x = qa.x;
		 qb.w = -qa.w;
		 qb.z = qa.z;

         scale = Math.sin(Math.PI * (0.5 - alpha));
         invscale = Math.sin(Math.PI * alpha);
     }
	 
	return new Quaternion(scale * qa.w + invscale * qb.w,
						  scale * qa.x + invscale * qb.x, 
						  scale * qa.y + invscale * qb.y,
						  scale * qa.z + invscale * qb.z
						   );
}

Quaternion.prototype.clone=function()
{
	return new Quaternion(this.w, this.x, this.y, this.z);
}

Quaternion.prototype.toString=function()
{
	return "["+this.w+", "+this.x+", "+this.y+", "+this.z+"]";	
}

/************************************************************************************************************************/
/************************************************************************************************************************/
/************************************************************************************************************************/
function Container3D()
{
	this.x=0;
	this.y=0;
	this.z=0;
	this.rotation=new Quaternion();
	this.scaleX=0;
	this.scaleY=0;
	this.scaleZ=0;
	
	this.__defineGetter__("matrix", 
	function()
	{ 
		return this.rotation.getMatrix();
	});
	
	var _matrix=new Matrix3D();
}
/************************************************************************************************************************/
/************************************************************************************************************************/
/************************************************************************************************************************/
function Camera3D()
{
	this.focalLength=1000;
	this.scale=1;
	this.flipHack=1;
	this.rotation=new Quaternion();
	
	this.pointTo=null;
	
	this.__defineGetter__("trans", function()
	{
		//*
		var moveMat=new Matrix3D();
		moveMat.createBox(1, 1, 1, 0, 0, 0, -this.x, -this.y, -this.z);
		
		_trans.identity();
		_trans.concat(moveMat);
		_trans.concat(this.rotation.getMatrix());
		
		return _trans;
		
		//*/
		
		/*
		if(this.pointTo==null)
			{
				_trans.createBox(1, 1, 1, 
								 -this.rotationY,
								 -this.rotationZ,
								 -this.x,
								 -this.y,
								 -this.z);
			}
			else
			{
				_trans=this.calculatePointTo();
			}
	 
			return _trans;
		//*/ 
	});
	 
	this.x=0;
	this.y=0;
	this.z=0;
	
	var _trans=new Matrix3D();
}

Camera3D.prototype.calculatePointTo=function()
{
	var upVector=new Vector3D(Math.sin(this.rotationZ), -Math.cos(this.rotationZ), 0);
	var forwardVector=new Vector3D(this.pointTo.x-this.x,
								   this.pointTo.y-this.y,
								   this.pointTo.z-this.z);
								   
	forwardVector.normalize();
	
	var rightVector=upVector.cross(forwardVector);
	rightVector.normalize();
	
	upVector=rightVector.cross(forwardVector);
	upVector.normalize();
	
	/*
	 *  | right.x    right.y      right.z    0 |
	 *  | up.x       up.y         up.z       0 |
	 *  | forward.x  forward.y    forward.z  0 |  ; (rotation4x4)
	 *  | 0          0            0          1 |
	 */
	 
	var rotMatrix=new Matrix3D(
								rightVector.x,   rightVector.y,   rightVector.z,   0,
								upVector.x, 	 upVector.y,      upVector.z,      0,
								forwardVector.x, forwardVector.y, forwardVector.z, 0,
								0,               0,               0,               1
							  );
							  
	var moveMatrix=new Matrix3D(
									1, 0, 0, 0,
									0, 1, 0, 0,
									0, 0, 1, 0,
									-this.x, -this.y, -this.z, 1
								);
								
	var combinedMatrix=new Matrix3D();
	combinedMatrix.concat(rotMatrix);
	combinedMatrix.concat(moveMatrix);
	
	return combinedMatrix;
}

/************************************************************************************************************************/
/************************************************************************************************************************/
/************************************************************************************************************************/
function Vector3D(x, y, z)
{	
	this.x=x==null?0:x;
	this.y=y==null?0:y;
	this.z=z==null?0:z;
	
	this.xIdx=-1;
	this.yIdx=-1;
	this.zIdx=-1;
	this.xIdx2d=-1;
	this.yIdx2d=-1;
	
	this.scene=null;
	this.scenePoints=null;
	this.scenePoints2d=null;
	this.scales=null;
}
Vector3D.prototype=
{
	get x()
	{
		return this._x;	
	},
	
	set x(value)
	{
		this._x=value;
		
		if(this.scenePoints!=null)
		{
			this.scenePoints[this.xIdx]=value;	
		}
	},
	
	get y()
	{
		return this._y;	
	},
	
	set y(value)
	{
		this._y=value;
		
		if(this.scenePoints!=null)
		{
			this.scenePoints[this.yIdx]=value;	
		}
	},
	
	get z()
	{
		return this._z;	
	},
	
	set z(value)
	{
		this._z=value;
		
		if(this.scenePoints!=null)
		{
			this.scenePoints[this.zIdx]=value;	
		}
	},
	
	get x2D()
	{
		return this.scenePoints2d[this.xIdx2d];
	},
	
	get y2D()
	{
		return this.scenePoints2d[this.yIdx2d];
	},
	
	get scale()
	{
		return this.scales[this.scaleIdx];
	},
	
	get magnitude()
	{
		return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z);
	}
};
Vector3D.prototype.addToScene=function(scene)
{
	if(this.scene==null)
	{
		this.canvas=scene.canvas;
		this.scenePoints=scene.scenePoints;
		this.scenePoints2d=scene.scenePoints2d;
		this.scales=scene.scales;
		
		this.xIdx=scene.scenePoints.length;
		this.yIdx=this.xIdx+1;
		this.zIdx=this.xIdx+2;
		
		this.xIdx2d=scene.scenePoints2d.length;
		this.yIdx2d=this.xIdx2d+1;
		
		this.scaleIdx=scene.scales.length;
		
		scene.scenePoints2d[this.xIdx2d]=0;
		scene.scenePoints2d[this.yIdx2d]=0;
		scene.scales[this.scaleIdx]=1;
		
		//hax
		var hax=true;
		if(hax)
		{
			this._x=this.x;
			
			//this._x=value;
			//if( this.xIdx < 100) console.log([this.x, this._x]);
			if(this.scenePoints!=null)
			{
				this.scenePoints[this.xIdx]=this._x;	
			}
		}
		else
		{
			this.x=this.x;
		}
		this.y=this.y;
		this.z=this.z;
	}
}
Vector3D.prototype.isInView=function()
{
	var width=this.canvas.width;
	var height=this.canvas.height;
	
	return this.x2D>0 && this.x2D<width && this.y2D>0 && this.y2D<height;
}

Vector3D.prototype.normalize=function()
{
	var mag=this.magnitude;
	
	this.x=this.x/mag;
	this.y=this.y/mag;
	this.z=this.z/mag;
}

Vector3D.prototype.cross=function(other)
{
	/*
	y1*z2-z1*y2
	z1*x2-x1*z2
	x1*y2-y1*x2
	*/
	
	var nVect=new Vector3D();
	nVect.x=this.y*other.z-this.z*other.y;
	nVect.y=this.z*other.x-this.x*other.z;
	nVect.z=this.x*other.y-this.y*other.x;
	
	return nVect;
}

Vector3D.prototype.toString=function()
{
	return "X:"+this.x+" Y:"+this.y+" Z:"+this.z;
}

/************************************************************************************************************************/
/************************************************************************************************************************/
/************************************************************************************************************************/
function Line3D()
{
	this.colour="#FFFFFF";
	this.lineWidth=1;
	this.scene=null;
	this.startPoint=new Vector3D();
	this.endPoint=new Vector3D();
	
	this.__defineGetter__("sX", function(){ return this.startPoint.x });
	this.__defineGetter__("sY", function(){ return this.startPoint.y });
	this.__defineGetter__("sZ", function(){ return this.startPoint.z });
	this.__defineGetter__("eX", function(){ return this.endPoint.x });
	this.__defineGetter__("eY", function(){ return this.endPoint.y });
	this.__defineGetter__("eZ", function(){ return this.endPoint.z });
	this.__defineSetter__("sX", function(value){ this.startPoint.x=value; });
	this.__defineSetter__("sY", function(value){ this.startPoint.y=value; });
	this.__defineSetter__("sZ", function(value){ this.startPoint.z=value; });
	this.__defineSetter__("eX", function(value){ this.endPoint.x=value; });
	this.__defineSetter__("eY", function(value){ this.endPoint.y=value; });
	this.__defineSetter__("eZ", function(value){ this.endPoint.z=value; });
}
Line3D.prototype.render=function()
{
	if(this.startPoint.scale>0 && this.endPoint.scale>0 && (this.startPoint.isInView() || this.endPoint.isInView()))
	{
		this.scene.renderContext.strokeStyle=this.colour;
		this.scene.renderContext.lineWidth=this.lineWidth;
		this.scene.renderContext.beginPath();
		this.scene.renderContext.moveTo(this.startPoint.x2D, this.startPoint.y2D);
		this.scene.renderContext.lineTo(this.endPoint.x2D, this.endPoint.y2D);
		this.scene.renderContext.closePath();
		this.scene.renderContext.stroke();
	}
}
Line3D.prototype.addToScene=function(scene)
{
	this.scene=scene;
	this.startPoint.addToScene(scene);
	this.endPoint.addToScene(scene);
}
/************************************************************************************************************************/
/************************************************************************************************************************/
/************************************************************************************************************************/
function Circle3D()
{
	this.radius=10;
	this.colour="#FFFFFF";
	
	this.__defineGetter__("position", function(){ return _position; });
	this.__defineGetter__("x", function(){ return _position.x; });
	this.__defineGetter__("y", function(){ return _position.y; });
	this.__defineGetter__("z", function(){ return _position.z; });
	this.__defineSetter__("x", function(value){ _position.x=value; });
	this.__defineSetter__("y", function(value){ _position.y=value; });
	this.__defineSetter__("z", function(value){ _position.z=value; });
	
	var _position=new Vector3D();
}
Circle3D.prototype.addToScene=function(scene)
{
	this.scene=scene;
	this.position.addToScene(scene);
}
Circle3D.prototype.render=function()
{
	if(this.position.scale>0 && this.position.isInView())
	{
		this.scene.renderContext.fillStyle=this.colour;
		this.scene.renderContext.beginPath();
		this.scene.renderContext.arc(this.position.x2D, this.position.y2D, this.radius*this.position.scale, 0, Math.PI*2, true);
		this.scene.renderContext.fill();
		this.scene.renderContext.closePath();
	}
}
Circle3D.prototype.toString=function()
{
	return "["+this.x+" "+this.y+" "+this.z+"]"; 
}
/************************************************************************************************************************/
/************************************************************************************************************************/
/************************************************************************************************************************/
function CurvePath3D()
{
	this.colour="#F9F9F9";
	this.lineWidth=1;
	this.scene=null;
	this.__defineGetter__("startPoint", function(){ return _startPoint; });
	this.__defineSetter__("startPoint", function(value){ _startPoint=value; });
	this.controlPoints1=[];
	this.controlPoints2=[];
	this.endPoints=[];
	
	var _startPoint=null;
}
CurvePath3D.prototype.addCurve=function(endPosition, controlPoint1, controlPoint2)
{
	this.controlPoints1[this.controlPoints1.length]=controlPoint1;
	this.controlPoints2[this.controlPoints2.length]=controlPoint2;
	this.endPoints[this.endPoints.length]=endPosition;
}
CurvePath3D.prototype.render=function()
{
	this.scene.renderContext.strokeStyle=this.colour;
	this.scene.renderContext.lineWidth=this.lineWidth;
	this.scene.renderContext.beginPath();
	this.scene.renderContext.moveTo(this.startPoint.x2D, this.startPoint.y2D);
	
	for(var i=0;i<this.controlPoints1.length;i++)
	{
		if(this.controlPoints1[i]!=undefined && this.controlPoints2[i]!=undefined)
		{
			this.scene.renderContext.bezierCurveTo(this.controlPoints1[i].x2D, this.controlPoints1[i].y2D, 
												   this.controlPoints2[i].x2D, this.controlPoints2[i].y2D,
												   this.endPoints[i].x2D, this.endPoints[i].y2D);
		}
		else if(this.controlPoints1[i]!=undefined)
		{
			this.scene.renderContext.quadraticCurveTo(this.controlPoints1[i].x2D, this.controlPoints1[i].y2D, 
													  this.endPoints[i].x2D, this.endPoints[i].y2D);
		}
		else
		{
			this.scene.renderContext.lineTo(this.endPoints[i].x2D, this.endPoints[i].y2D);
		}
	}
	
	this.scene.renderContext.closePath();
	this.scene.renderContext.stroke();
}
CurvePath3D.prototype.addToScene=function(scene)
{
	this.scene=scene;
	
	this.startPoint.addToScene(scene);
	
	for(var i=0;i<this.controlPoints1.length;i++)
	{
		if(this.controlPoints1[i]!=undefined)
		{
			this.controlPoints1[i].addToScene(scene);
		}
	}
	
	for(var i=0;i<this.controlPoints2.length;i++)
	{
		if(this.controlPoints2[i]!=undefined)
		{
			this.controlPoints2[i].addToScene(scene);
		}
	}
	
	for(var i=0;i<this.endPoints.length;i++)
	{
		this.endPoints[i].addToScene(scene);
	}
}
