/*
 * Canvas 3D
 * 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._aVertices = [];
	this._aFaces = [];
	this._aNormals = [];
	this._aMaterials = [];

	this._oDefaultColor = {r:155,g:155,b:155};

	this._iSize = 1;

	this._bFill = true;
	this._bWire = false;
	this._bShading = true;
	this._bBackfaceCull = true;
	this._bZSort = true;

};

// parse the mesh data
Canvas3D.Mesh.prototype.setMeshData = function(oMeshData) 
{
	this._oMeshData = oMeshData;

	this._aVertices = [];
	this._aFaces = [];
	this._aNormals = [];
	this._aMaterials = [];

	var oPos = this._oPosition;

	if (this._oMeshData.mat) {
		for (var m=0;m<this._oMeshData.mat.length;m++) {
			var oMat = this._oMeshData.mat[m];
			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 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);

			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._aVertices[oFace[0] + iVertOffset];
			var oPoint2 = this._aVertices[oFace[1] + iVertOffset];
			var oPoint3 = this._aVertices[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]
			);

			this._aFaces.push(
				{
					a : oFace[0] + iVertOffset,
					b : oFace[1] + iVertOffset,
					c : oFace[2] + iVertOffset,
					normal : oNormal,
					center : oCenter,
					mat : oFace[3]
				}
			);
		}
	}
}


var fncZSort = function(a, b) { 
	return a.transcenter.z - b.transcenter.z;
}


Canvas3D.Mesh.prototype.draw = function(oContext, iOffsetX, iOffsetY) 
{
	var oScene = this._oScene;

	var oCam = oScene.getActiveCamera();

	var oAmbient = oScene.getAmbientLight()

	if (this._bShading && this._bFill) {
		var aLights = oScene.getLights();
		var aLightDirections = [];
		for (var l=0;l<aLights.length;l++) {
			var oLightPos = aLights[l].getPosition();
			var oLightDirection = oLightPos.unit();
			aLightDirections.push(oLightDirection);
		}
	}

	var aVertices = this._aVertices;
	var aFaces = this._aFaces;

	if (aVertices.length < 3 || aFaces.length < 1) {
		// nothing to draw
		return;
	}

	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 (this._bFill && this._bZSort) {
		// if the faces are filled, we need to do z-sorting
		var f = aFaces.length;
		do {
			var oFace = aFaces[f-1];
			oFace.transcenter = oCam.transformPoint(oFace.center);
		} while (--f);

		aSortedFaces = Array.apply(null,aFaces).sort(fncZSort);

	} else {
		// if not, just use the raw list
		aSortedFaces = aFaces;
	}

	f = aSortedFaces.length;
	if (f < 1) {
		return;
	}

	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;

		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 (bDraw) {
			if (this._aMaterials[oFace.mat]) {
				oFaceColor = this._aMaterials[oFace.mat];
			} else {
				oFaceColor = this._oDefaultColor;
			}

			var oFaceOrgColor = {r:oFaceColor.r, g:oFaceColor.g, b:oFaceColor.b};

			if (this._bFill) {
				var oFaceColorAmb = {
					r:(oAmbient.r / 255) * oFaceColor.r,
					g:(oAmbient.g / 255) * oFaceColor.g,
					b:(oAmbient.b / 255) * oFaceColor.b
				};

				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);

						if (fDot > 0) {
							var fLight = fDot * aLights[l].getIntensity();
							oFaceColorAmb = {
								r: oFaceColorAmb.r + oFaceColor.r * fLight,
								g: oFaceColorAmb.g + oFaceColor.g * fLight,
								b: oFaceColorAmb.b + oFaceColor.b * fLight
							};
						}
					}
				}
				oFaceColor = oFaceColorAmb;
	
				oFaceColor.r = Math.min(Math.max(Math.round(oFaceColor.r),0),255);
				oFaceColor.g = Math.min(Math.max(Math.round(oFaceColor.g),0),255);
				oFaceColor.b = Math.min(Math.max(Math.round(oFaceColor.b),0),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) {
				oContext.fillStyle = "rgb(" + oFaceColor.r + "," + oFaceColor.g + "," + oFaceColor.b + ")";
				oContext.fill();
				if (!this._bWire) {
					oContext.lineWidth = 0.5;
					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);

}

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;
}


