/*
 * Javascript/Canvas Textured 3D Renderer v0.2
 * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
 * This software is free to use for non-commercial purposes. For anything else, please contact the author.
 */

Canvas3D.Mesh = function() {

	this._oPosition = new Canvas3D.Vec3(0,0,0);
	this._oRotation = new Canvas3D.Vec3(0,0,0);

	this._aVertices = []; // vertex positions in object space
	this._aGlobalVertices = []; // vertices translated to global space
	this._aFaces = [];
	this._aNormals = [];
	this._aMaterials = [];

	this._bDirty = true;

	this._bVisible = true;

	this._iForcedZ = -1;
	this._bHideWhenRotating = false;

	this._oDefaultColor = {r:155,g:155,b:155};
	this._oDefaultMaterial = {};
	this._iSize = 1;

	this._fScale = 1;

	this._bFill = true;		// render filled triangles
	this._bWire = false;		// render wireframe
	this._bShading = true;		// shade/light filled triangles
	this._bBackfaceCull = true;	// only draw triangles facing the camera
	this._bZSort = true;		// sort triangles back-to-front
	this._bExpandClipPath = true;	// expand clip path by 1px, to minimize gaps

	this._bTexture = false;		// render textured triangles (must enable bFill as well)
	this._bTextureShading = false;	// render shading on textured triangles (must enable bShading as well)

	// sometimes the exported normals from Max are messed up, or they're imported wrong or whatever, I don't know.
	// We can recalculate them after loading.
	this._bCalcNormals = true;

	// only allow textures if canvas is available
	var oCanvas = document.createElement("canvas");
	this._bCanTexture = false;	
	this._bCanTextureUV = false;	
	if (oCanvas.getContext && oCanvas.getContext("2d")) {
		this._bCanTexture = true;
		if (oCanvas.getContext("2d").getImageData) {
			this._bCanTextureUV = true;
		}
	}


};

// parse the mesh data
// the mesh data (vertices, faces, texture coordinates, materials) are read from a JSON object structure
// and copied into local arrays
// normals are recalculated, if enabled.
Canvas3D.Mesh.prototype.setMeshData = function(oMeshData, oScene) 
{
	this._oMeshData = oMeshData;

	this._aVertices = [];
	this._aFaces = [];
	this._aNormals = [];
	this._aMaterials = [];

	var oPos = this._oPosition;

	var me = this;

	if (this._oMeshData.mat) {
		for (var m=0;m<this._oMeshData.mat.length;m++) {
			var oMat = this._oMeshData.mat[m];
			oMat.idx = m;
			if (oMat.t) {
				oMat.image = new Image();
				if (oMat.uv && this._bCanTextureUV) {
					oMat.image.mat = oMat;
					oMat.image.onload = function() {
						me._bakeTexture(this.mat);
						if (oScene) {
							oScene.setDirty(true);
						}
						this.onload = null;
					}
				}
				oMat.image.src = "textures/" + oMat.t;
			}
			this._aMaterials.push(oMat);
		}
	}

	for (var o=0;o<this._oMeshData.obj.length;o++) {

		var oObject = this._oMeshData.obj[o];
		var aVertices = oObject.vrt;
		var aTexCoords = oObject.tex;

		this._aTexCoords = aTexCoords;

		var iVertOffset = this._aVertices.length;

		var fTotalX = 0;
		var fTotalY = 0;
		var fTotalZ = 0;

		var iNumVertices = aVertices.length;

		for (var v=0;v<iNumVertices;v++) {
			var oVertex = new Canvas3D.Vec3(
					aVertices[v][0],
					aVertices[v][1],
					aVertices[v][2]
			);

			this._aVertices.push(oVertex);

			this._aGlobalVertices.push(
				new Canvas3D.Vec3(
					oVertex.x + oPos.x,
					oVertex.y + oPos.y,
					oVertex.z + oPos.z
				)
			);

			fTotalX += oVertex.x;
			fTotalY += oVertex.y;
			fTotalZ += oVertex.z;
		}

		var fAvgX = fTotalX / iNumVertices;
		var fAvgY = fTotalY / iNumVertices;
		var fAvgZ = fTotalZ / iNumVertices;

		var oLocalCenter = new Canvas3D.Vec3(fAvgX, fAvgY, fAvgZ);

		var aFaces = oObject.fac;
		for (var f=0;f<aFaces.length;f++) {
			var oFace = aFaces[f];

			var oPoint1 = this._aGlobalVertices[oFace[0] + iVertOffset];
			var oPoint2 = this._aGlobalVertices[oFace[1] + iVertOffset];
			var oPoint3 = this._aGlobalVertices[oFace[2] + iVertOffset];

			var oCenter = new Canvas3D.Vec3(
				(oPoint1.x + oPoint2.x + oPoint3.x) / 3, 
				(oPoint1.y + oPoint2.y + oPoint3.y) / 3, 
				(oPoint1.z + oPoint2.z + oPoint3.z) / 3
			);

			var oNormal = new Canvas3D.Vec3(
					oObject.nrm[f][0],
					oObject.nrm[f][1],
					oObject.nrm[f][2]
			);

			var oFace = {
					a : oFace[0] + iVertOffset,
					b : oFace[1] + iVertOffset,
					c : oFace[2] + iVertOffset,
					normal : oNormal,
					center : oCenter,
					mat : oFace[3],
					idx : f
			}

			this._aFaces.push(oFace);
		}

		if (this._bCalcNormals) {
			this._recalcNormals();
		}
	}
}

// here we prepare the texture for easy rendering. 
// For each face, a triangular region is drawn onto a canvas containing the image data for that face
// We draw a triangular section on the destination canvas containing the corresponding pixel data from the source texture,
// using the UV coords to retrieve the correct pixels
// that way, we have all texture parts rotated and scaled into a common form, so they can easily be drawn later
// at the moment, the data is read with getImageData and is rather slow, but it should be possible do to sort of the reverse process
// of the drawTextureTriangle transform to draw these texture parts.
Canvas3D.Mesh.prototype._bakeTexture = function(oMat) 
{

	var f = this._aFaces.length;

	var aTexFaces = [];
	// find all face that need this texture
	do {
		var oFace = this._aFaces[f-1];
		if (oFace.mat == oMat.idx) {
			aTexFaces.push(oFace);
			oFace.texidx = aTexFaces.length-1;
		}
	} while (--f)

	f = aTexFaces.length;
	if (f < 1) {
		return;
	}

	var fc = aTexFaces.length;

	var iTexRes = oMat.res;
	var iTexWidth = iTexRes * f + f*2;
	var iTexHeight = iTexRes + 2;

	var iSrcWidth = oMat.w;
	var iSrcHeight = oMat.h;

	// create canvas for source texture image and paint the texture on it
	var oSource = document.createElement("canvas");
	oSource.width = iSrcWidth;
	oSource.height = iSrcHeight;
	oSource.style.width = iSrcWidth+"px";
	oSource.style.height = iSrcHeight+"px";
	var oSrcCtx = oSource.getContext("2d");
	oSrcCtx.drawImage(oMat.image, 0, 0, iSrcWidth, iSrcHeight);
	var oSrcDataObj = oSrcCtx.getImageData(0, 0, iSrcWidth, iSrcHeight);
	var aSrcData = oSrcDataObj.data;

	// create canvas for finished face textures.
	var oTexCanvas = document.createElement("canvas");
	oTexCanvas.width = iTexWidth;
	oTexCanvas.height = iTexHeight;
	oTexCanvas.style.width = iTexWidth+"px";
	oTexCanvas.style.height = (iTexHeight)+"px";
	oTexCanvas.style.backgroundColor = "rgb(255,0,255)";
	oTexCanvas.resolution = iTexRes;
	var oDstCtx = oTexCanvas.getContext("2d");


	var oDstDataObj = oDstCtx.getImageData(0, 0, iTexWidth, iTexHeight);
	var aDstData = oDstDataObj.data;

	var oContext = oTexCanvas.getContext("2d");

	//uncomment to see how the texture is prepared
	//document.body.appendChild(oSource);
	//document.body.appendChild(oTexCanvas);

	var iTexOffsetX = iTexRes;

	do {
		var oFace = aTexFaces[f-1];

		var oCoords = this._aTexCoords[oFace.idx];

		var oTexPoint1 = oCoords[1];
		var oTexPoint2 = oCoords[2];
		var oTexPoint3 = oCoords[0];

		var x1 = oTexPoint1[0] * iSrcWidth;
		var y1 = (1 - oTexPoint1[1]) * iSrcHeight;
		var x3 = oTexPoint2[0] * iSrcWidth;
		var y3 = (1 - oTexPoint2[1]) * iSrcHeight;
		var x2 = oTexPoint3[0] * iSrcWidth;
		var y2 = (1 - oTexPoint3[1]) * iSrcHeight;

		var fUnitAX = (x2 - x1);
		var fUnitAY = (y2 - y1);

		var fUnitBX = (x3 - x2);
		var fUnitBY = (y3 - y2);

		var iOffsetX = 0;

		var iDstXOffset = (iTexWidth - iTexOffsetX - (fc-f)*2-2);

		// we paint the triangular texture with a 1px margin on each side and let the texture spill over into this margin.
		// this is to prevent small transparent gaps to appear between the triangles when they are rotated and scaled into place during rendering.

		var y = iTexRes+2;
		do {
			var iDstY = iTexRes+2-y;
			var fStepY = (iTexRes+1-y) / iTexRes;

			var fStepYUnitBX = fStepY*fUnitBX;
			var fStepYUnitBY = fStepY*fUnitBY;

			var iDstYOffset = iDstY*iTexWidth*4;

			var x = iTexRes+2 - iOffsetX;
			do {
				var iDstX = x + iDstXOffset - 1;
				var fStepX = (x-1 + iOffsetX) / iTexRes;
	
				var iSrcX = Math.floor(x1 + fStepX*fUnitAX + fStepYUnitBX);
				var iSrcY = Math.floor(y1 + fStepX*fUnitAY + fStepYUnitBY);

				if (iSrcX < 0) iSrcX = 0;
				if (iSrcY < 0) iSrcY = 0;

				if (iSrcX >= iSrcWidth) iSrcX = iSrcWidth-1;
				if (iSrcY >= iSrcHeight) iSrcY = iSrcHeight-1;

				var iDstPixOffset = iDstYOffset + iDstX*4;
				var iSrcPixOffset = (iSrcY*iSrcWidth + iSrcX)*4;
	
				aDstData[iDstPixOffset] = aSrcData[iSrcPixOffset];
				aDstData[iDstPixOffset+1] = aSrcData[iSrcPixOffset+1];
				aDstData[iDstPixOffset+2] = aSrcData[iSrcPixOffset+2];

				aDstData[iDstPixOffset+3] = oMat.texalpha ? aSrcData[iSrcPixOffset+3] : 255;

			} while (--x);

			iOffsetX++;
			iDstXOffset++;

		} while (--y);

		iTexOffsetX += iTexRes;

	} while (--f);


	oDstCtx.putImageData(oDstDataObj, 0, 0);
	oDstCtx.fillRect(0,0,0,0); // Opera doesn't update until we draw something?
	oMat.facecanvas = oTexCanvas;
}

var fncZSort = function(a, b) { 
	return a.transcenter.z - b.transcenter.z;
}


// math and misc shortcuts
var sin = Math.sin;
var cos = Math.cos;
var asin = Math.asin;
var acos = Math.acos;
var pow = Math.pow;
var sqrt = Math.sqrt;

var fRadDeg = 180 / Math.PI;
var fDegRad = Math.PI / 180;
var fDegRad45 = fDegRad*45;
var fDegRad90 = fDegRad*90;
var fDegRad180 = fDegRad*180;
var fSqrt2Div2 = sqrt(2) / 2;


// this functions draws a textured (and shaded) triangle on the canvas
// this is done by rotating/scaling a triangular section in place, setting up a clipping path and drawing the image.
// if UV coords are enabled, only the correct part of the triangle-strip texture is drawn
// if not, the entire texture is drawn for each face
// some of the code used for skewing the image was inspired by the AS function found here:
// http://www.senocular.com/flash/actionscript.php?file=ActionScript_1.0/Prototypes/MovieClip/skew.as
Canvas3D.Mesh.prototype._drawTextureTriangle = function(oContext, oMat, oPoint1, oPoint2, oPoint3, iOffsetX, iOffsetY, fShade, oNormal, iIdx) 
{
	if (!oMat.image) {
		return;
	}
	if (!oMat.image.complete) {
		return;
	}

	var oMatImage = oMat.image;

	if (!oMatImage.canvas) {
		// first time around, we paint the texture image to a canvas
		// drawing the triangle later on is slightly faster using another canvas object rather than an image object
		// this should be moved to someplace else

		var iTexWidth = 50;
		var iTexHeight = 50;

		var oTextureCanvas = document.createElement("canvas");

		oTextureCanvas.width = iTexWidth;
		oTextureCanvas.height = iTexHeight;

		oTextureCanvas.style.width = iTexWidth + "px";
		oTextureCanvas.style.height = iTexHeight + "px";

		var oTexCtx = oTextureCanvas.getContext("2d");
		oTexCtx.drawImage(oMatImage, 0, 0, iTexWidth, iTexHeight);

		oMatImage.canvas = oTextureCanvas;
	}

	var x1 = oPoint1.x;
	var y1 = oPoint1.y;
	var x2 = oPoint2.x;
	var y2 = oPoint2.y;
	var x3 = oPoint3.x;
	var y3 = oPoint3.y;

	// trig to calc the angle we need to rotate in order get our texturetriangle in place
	var dx = x3 - x2;
	var dy = y3 - y2;
	var a = sqrt((dx*dx + dy*dy));

	dx = x3 - x1;
	dy = y3 - y1;
	var b = sqrt((dx*dx + dy*dy));

	dx = x2 - x1;
	dy = y2 - y1;
	var c = sqrt((dx*dx + dy*dy));

	var aa = a*a, bb = b*b, cc = c*c;

	var fCosB = (aa + cc - bb) / (2*a*c);
	var fAngleB = acos(fCosB);
	if (isNaN(fAngleB)) return;

	var fCosC = (aa + bb - cc) / (2*a*b);
	var fAngleC = acos(fCosC);
	if (isNaN(fAngleC)) return;

	if ((fAngleB + fAngleC) == 0) return;

	var fSkewX = -(fDegRad90 - (fAngleB + fAngleC));

	var fTriRotation = -(asin((y2 - y1) / c));

	if (x2 > x1) { // rotate the other way around if triangle is flipped
		fTriRotation = fDegRad180 - fTriRotation;
	}

	if (fSkewX == fDegRad90) fSkewX = fDegRad*89.99;
	if (fSkewX == -fDegRad90) fSkewX = -fDegRad*89.99;

	var fCosX = cos(fSkewX);

	var fRotation = fDegRad45 + fSkewX * 0.5;

	var fDiv = 1 / (sin(fRotation) * fSqrt2Div2);

	var fScaleX = fCosX * fDiv;
	var fScaleY = (sin(fSkewX) + 1) * fDiv;


	// setup the clipping path, so only texture within the triangle is drawn.
	var iClipX1 = x1 + iOffsetX;
	var iClipY1 = y1 + iOffsetY;

	var iClipX2 = x2 + iOffsetX;
	var iClipY2 = y2 + iOffsetY;

	var iClipX3 = x3 + iOffsetX;
	var iClipY3 = y3 + iOffsetY;


	// here we try to expand the clip path by 1 pixel to get rid of (some of the) gaps between the triangles
	// we do this simply by moving the topmost point 1px up, the leftmost point 1px left, and so on.
	// later, we also render the triangle itself 1 px too big
	// drawbacks are that the contour of the object will appear a bit "pointy".
	if (this._bExpandClipPath) {
		if (iClipY1 < iClipY2 && iClipY1 < iClipY3)
			iClipY1--;
		else if (iClipY2 < iClipY1 && iClipY2 < iClipY3)
			iClipY2--;
		else if (iClipY3 < iClipY1 && iClipY3 < iClipY2)
			iClipY3--;

		if (iClipY1 > iClipY2 && iClipY1 > iClipY3)
			iClipY1++;
		else if (iClipY2 > iClipY1 && iClipY2 > iClipY3)
			iClipY2++;
		else if (iClipY3 > iClipY1 && iClipY3 > iClipY2)
			iClipY3++;

		if (iClipX1 < iClipX2 && iClipX1 < iClipX3)
			iClipX1--;
		else if (iClipX2 < iClipX1 && iClipX2 < iClipX3)
			iClipX2--;
		else if (iClipX3 < iClipX1 && iClipX3 < iClipX2)
			iClipX3--;

		if (iClipX1 > iClipX2 && iClipX1 > iClipX3)
			iClipX1++;
		else if (iClipX2 > iClipX1 && iClipX2 > iClipX3)
			iClipX2++;
		else if (iClipX3 > iClipX1 && iClipX3 > iClipX2)
			iClipX3++;
	}

	oContext.save();

	// do the clip path
	oContext.beginPath();
	oContext.moveTo(iClipX1, iClipY1);
	oContext.lineTo(iClipX2, iClipY2);
	oContext.lineTo(iClipX3, iClipY3);
	oContext.closePath();
	oContext.clip();

	// setup the skew/rotation transformation
	oContext.translate(x2 + iOffsetX, y2 + iOffsetY);
	oContext.rotate(fRotation + fTriRotation);
	oContext.scale(fScaleX, fScaleY);
	oContext.rotate(-fDegRad45);

	var fTriScaleX = c / 2; // 100 * 50;
	var fTriScaleY = b / 2; // 100 * 50;

	if (oMat.uv) {
		// we are using UV coordinates for texturing
		if (this._bCanTextureUV && oMat.facecanvas) {
			var iTexRes = oMat.facecanvas.resolution;
			// draw our texture
			// there will be a small gap between the triangles. Drawing the texture at offset (-1,-1)  gets rid of some of it.
			oContext.drawImage(
				oMat.facecanvas, 
				iIdx * iTexRes + iIdx*2+1, 1, iTexRes, iTexRes,
				-1, -1, 
				fTriScaleX + 2,
				fTriScaleY + 2
			);
		}
	} else {
		// no UV, just draw the same texture for all faces
		oContext.drawImage(
			oMatImage.canvas, 
			-1, -1, 
			fTriScaleX + 2,
			fTriScaleY + 2
		);
	}


	oContext.restore();


	// if shading is turned on, render a semi-transparent black triangle on top.
	// that means that a fully lit triangle will just be the raw texture, and the less lit a triangle is, the darker it gets.
	// we could render semi-transparent white on top to make it brighter, but it doesn't look right, so we settle for that.
	if (this._bTextureShading && fShade > 0) {
		oContext.beginPath();
		oContext.moveTo(iClipX1, iClipY1);
		oContext.lineTo(iClipX2, iClipY2);
		oContext.lineTo(iClipX3, iClipY3);
		oContext.closePath();
	
		oContext.fillStyle = "rgba(0,0,0," + (fShade*0.5) + ")";
		oContext.fill();
	}
}

// draw the mesh on the oContext canvas context, at offset [iOffsetX, iOffsetY]
Canvas3D.Mesh.prototype.draw = function(oContext, iOffsetX, iOffsetY) 
{
	if (!this._bVisible) return;

	var oScene = this._oScene;

	var oCam = oScene.getActiveCamera();
	var oAmbient = oScene.getAmbientLight()

	// if shading is enabled, calculate the direction vectors to all light sources
	if (this._bShading && this._bFill) {
		var aLights = oScene.getLights();
		var aLightDirections = [];
		for (var l=0;l<aLights.length;l++) {
			// todo: this should be position relative to mesh
			var oLightPos = aLights[l].getPosition();
			var oLightDirection = oLightPos.unit();
			aLightDirections.push(oLightDirection);
		}
	}

	var aVertices = this._aGlobalVertices;
	var aFaces = this._aFaces;

	if (aVertices.length < 3 || aFaces.length < 1) {
		// nothing to draw
		return;
	}


	// let the camera transform all vertices and project them to 2D.
	var aPoints2D = [];
	var aTransVertices = [];
	var v = aVertices.length;
	do {
		var oVertex = aVertices[v-1];

		var oVec = oCam.transformPoint(oVertex);
		aTransVertices[v-1] = oVec;

		var oPoint2D = oCam.transform2D(oVec);
		aPoints2D[v-1] = oPoint2D;
	} while (--v);

	var aSortedFaces;

	// if the faces are filled, we need to do z-sorting
	if (this._bFill && this._bZSort) {
		var f = aFaces.length;
		do {
			var oFace = aFaces[f-1];
			oFace.transcenter = oCam.transformPoint(oFace.center);
		} while (--f);

		aSortedFaces = aFaces.sort(fncZSort);

	// if not, just use the raw list
	} else {

		aSortedFaces = aFaces;
	}


	f = aSortedFaces.length;
	if (f < 1) {
		return;
	}

	// run through all faces
	do {
		var oFace = aSortedFaces[f-1];

		var oPoint1 = aPoints2D[oFace.a];
		var oPoint2 = aPoints2D[oFace.b];
		var oPoint3 = aPoints2D[oFace.c];

		var oNormal = oFace.normal;

		var bDraw = false;

		// do backface culling
		// we do backface culling in screen space by checking if the 2d triangle is flipped,
		// since I couldn't get 3d culling to work flawlessly.
		if (this._bBackfaceCull) {
			// screen space backface culling adapted from http://www.kirupa.com/developer/actionscript/backface_culling.htm
			if (((oPoint3.y-oPoint1.y)/(oPoint3.x-oPoint1.x) - (oPoint2.y-oPoint1.y)/(oPoint2.x-oPoint1.x) <= 0) ^ (oPoint1.x <= oPoint3.x == oPoint1.x > oPoint2.x)){
     				bDraw = true;
			}
		} else {
			bDraw = true;
		}

		
		// if triangle is facing camera, draw it
		if (bDraw) {

			// get the material for this face
			var oFaceMat = this._aMaterials[oFace.mat];
			if (oFaceMat) {
				oFaceColor = oFaceMat;
			} else {
				oFaceMat = this._oDefaultMaterial;
				oFaceColor = this._oDefaultColor;
			}
			var bFaceTexture = this._bTexture && oFaceMat.t;

			// save the original color
			var oFaceOrgColor = {r:oFaceColor.r, g:oFaceColor.g, b:oFaceColor.b};

			var fLight = 0;
			var fShade = 1;

			if (this._bFill) {
				// setup ambient face color
				if (!bFaceTexture) {
					var oFaceColorAmb = {
						r:(oAmbient.r / 255) * oFaceColor.r,
						g:(oAmbient.g / 255) * oFaceColor.g,
						b:(oAmbient.b / 255) * oFaceColor.b
					};
				}

				// do lighting
				if (this._bShading) {

					for (var l=0;l<aLights.length;l++) {
						var oLightPos = aLights[l].getPosition();
						var oLightDir = new Canvas3D.Vec3(
							oLightPos.x - oFace.center.x,
							oLightPos.y - oFace.center.y,
							oLightPos.z - oFace.center.z
						).unit();

						var fDot = -oLightDir.dot(oNormal);

						// is the face facing the light source
						if (fDot > 0) {
							//fDot = Math.sqrt(fDot);
							fLight = fDot * aLights[l].getIntensity();
							fShade = fShade - fLight;

							// lighten the face by the light intensity
							if (!bFaceTexture) {
								oFaceColorAmb = {
									r: oFaceColorAmb.r + oFaceColor.r * fLight,
									g: oFaceColorAmb.g + oFaceColor.g * fLight,
									b: oFaceColorAmb.b + oFaceColor.b * fLight
								};
							}
						}
					}
				}
				if (!bFaceTexture) {
					oFaceColor = oFaceColorAmb;

					oFaceColor.r = Math.floor(oFaceColor.r);
					oFaceColor.g = Math.floor(oFaceColor.g);
					oFaceColor.b = Math.floor(oFaceColor.b);

					if (oFaceColor.r < 0) oFaceColor.r = 0;
					if (oFaceColor.g < 0) oFaceColor.g = 0;
					if (oFaceColor.b < 0) oFaceColor.b = 0;

					if (oFaceColor.r > 255) oFaceColor.r = 255;
					if (oFaceColor.g > 255) oFaceColor.g = 255;
					if (oFaceColor.b > 255) oFaceColor.b = 255;
				}
			}

			oContext.beginPath();
			oContext.moveTo(oPoint1.x + iOffsetX, oPoint1.y + iOffsetY);
			oContext.lineTo(oPoint2.x + iOffsetX, oPoint2.y + iOffsetY)
			oContext.lineTo(oPoint3.x + iOffsetX, oPoint3.y + iOffsetY)
			oContext.closePath();

			if (this._bFill) {
				if (this._bCanTexture && this._bTexture && oFaceMat.image) {
					this._drawTextureTriangle(oContext, oFaceMat, oPoint1, oPoint2, oPoint3, iOffsetX, iOffsetY, fShade, oNormal, oFace.texidx);
				} else {
					oContext.fillStyle = "rgb(" + oFaceColor.r + "," + oFaceColor.g + "," + oFaceColor.b + ")";
					oContext.fill();
					if (!this._bWire) {
						oContext.lineWidth = 0.7;
						oContext.strokeStyle = "rgb(" + oFaceColor.r + "," + oFaceColor.g + "," + oFaceColor.b + ")";
						oContext.stroke();
					}
				}
			}

			if (this._bWire) {
				oFaceOrgColor.r = Math.min(Math.max(Math.round(oFaceOrgColor.r),0),255);
				oFaceOrgColor.g = Math.min(Math.max(Math.round(oFaceOrgColor.g),0),255);
				oFaceOrgColor.b = Math.min(Math.max(Math.round(oFaceOrgColor.b),0),255);

				oContext.lineWidth = 1;
				oContext.strokeStyle = "rgb(" + oFaceOrgColor.r + "," + oFaceOrgColor.g + "," + oFaceOrgColor.b + ")";
				oContext.stroke();
			}

		}
	} while (--f);
	this._bDirty = false;
}

Canvas3D.Mesh.prototype.setScene = function(oScene)
{
	if (this._oScene != oScene) {
		this._oScene = oScene;
	}
}

Canvas3D.Mesh.prototype.setLighting = function(bEnable)
{
	this._bShading = bEnable;
}

Canvas3D.Mesh.prototype.setBackfaceCull = function(bEnable)
{
	this._bBackfaceCull = bEnable;
}

Canvas3D.Mesh.prototype.setZSort = function(bEnable)
{
	this._bZSort = bEnable;
}

Canvas3D.Mesh.prototype.setFill = function(bEnable)
{
	this._bFill = bEnable;
}

Canvas3D.Mesh.prototype.setWire = function(bEnable)
{
	this._bWire = bEnable;
}

Canvas3D.Mesh.prototype.setTexture = function(bEnable)
{
	this._bTexture = bEnable;
}

Canvas3D.Mesh.prototype.setTextureShading = function(bEnable)
{
	this._bTextureShading = bEnable;
}

Canvas3D.Mesh.prototype._updateGlobalVertices = function()
{
	var oRot = this._oRotation;
	var oPos = this._oPosition;

	for (var i=0;i<this._aVertices.length;i++) {
		var oRotatedVertex = new Canvas3D.Vec3(
			this._aVertices[i].x,
			this._aVertices[i].y,
			this._aVertices[i].z
		);

		if (oRot.x)
			oRotatedVertex.rotateX(oRot.x);
		if (oRot.y)
			oRotatedVertex.rotateY(oRot.y);
		if (oRot.z)
			oRotatedVertex.rotateZ(oRot.z);

		this._aGlobalVertices[i].x = oRotatedVertex.x * this._fScale + oPos.x;
		this._aGlobalVertices[i].y = oRotatedVertex.y * this._fScale + oPos.y;
		this._aGlobalVertices[i].z = oRotatedVertex.z * this._fScale + oPos.z;
	}

	this._recalcNormals();
}

Canvas3D.Mesh.prototype._recalcNormals = function()
{
	for (var f=0;f<this._aFaces.length;f++) {
		var oFace = this._aFaces[f];

		var oPoint1 = this._aGlobalVertices[oFace.a];
		var oPoint2 = this._aGlobalVertices[oFace.b];
		var oPoint3 = this._aGlobalVertices[oFace.c];

		var oCenter = new Canvas3D.Vec3(
			(oPoint1.x + oPoint2.x + oPoint3.x) / 3, 
			(oPoint1.y + oPoint2.y + oPoint3.y) / 3, 
			(oPoint1.z + oPoint2.z + oPoint3.z) / 3
		);

		oFace.center = oCenter;

		var oNormal = new Canvas3D.Vec3(
			((oPoint1.y - oPoint2.y) * (oPoint1.z - oPoint3.z)) - ((oPoint1.z - oPoint2.z) * (oPoint1.y - oPoint3.y)),
			((oPoint1.z - oPoint2.z) * (oPoint1.x - oPoint3.x)) - ((oPoint1.x - oPoint2.x) * (oPoint1.z - oPoint3.z)),
			((oPoint1.x - oPoint2.x) * (oPoint1.y - oPoint3.y)) - ((oPoint1.y - oPoint2.y) * (oPoint1.x - oPoint3.x))
		).unit();
		oFace.normal = oNormal;
	}
}


Canvas3D.Mesh.prototype.setPosition = function(oVec)
{
	this._oPosition = oVec;
	this._updateGlobalVertices();

	this._bDirty = true;
}

Canvas3D.Mesh.prototype.setRotation = function(oVec)
{
	this._oRotation = oVec;
	this._updateGlobalVertices();

	this._bDirty = true;

}

Canvas3D.Mesh.prototype.getPosition = function(oVec)
{
	return this._oPosition;
}

Canvas3D.Mesh.prototype.setForcedZ = function(iZ)
{
	this._iForcedZ = iZ;
}

Canvas3D.Mesh.prototype.getForcedZ = function()
{
	return this._iForcedZ;
}

Canvas3D.Mesh.prototype.getHideWhenRotating = function()
{
	return this._bHideWhenRotating;
}

Canvas3D.Mesh.prototype.setHideWhenRotating = function(bEnable)
{
	this._bHideWhenRotating = bEnable;
}

Canvas3D.Mesh.prototype.getDirty = function()
{
	return this._bDirty;
}


Canvas3D.Mesh.prototype.hide = function()
{
	this._bVisible = false;
	this._bDirty = true;
}

Canvas3D.Mesh.prototype.show = function()
{
	this._bVisible = true;
	this._bDirty = true;
}


Canvas3D.Mesh.prototype.setScale = function(fScale)
{
	this._fScale = fScale;
	this._bDirty = true;
	this._updateGlobalVertices();
}

Canvas3D.Mesh.prototype.getScale = function()
{
	return this._fScale;
}

