/**
* WebGL Model Viewer - Mesh.js
* Copyright © Cyril Diagne 2010.
*
* @author kikko.fr
*/

Mesh = function() {
	
	this.name = "";
	this.color = 0;
	
	this.mvMatrix = mat4.create();
	this.normalMatrix = mat4.create();
	
	// data
	this.vertices = [];
	this.faces = [];
	
	this.facesData = [];
	this.vboData = [];
	
	this.hasUV = false;
	this.hasNormals = false;
	this.hasTangents = false;
	
	// materialList
	this.materialList;
	this.materialsLengths = null;
	
	// webgl buffers
	this.vbo = null;
	this.vboInfos = null;
	this.vboStride = 0;
	this.facesBuffer = null;
}

Mesh.prototype = {

	computeTN : function() {

		// based on 'Generating Vertex Normals' by Max Wagner && http://jerome.jouvie.free.fr/OpenGl/Lessons/Lesson8.php
		var i, l, f, vA, vB, vC, vecCB=vec3.create(), vecAB=vec3.create(), faceNormal, uvCB, uvAB, coef;
		var fLength = this.faces.length, vLength = this.vertices.length;
		
		var faceNormal = vec3.create();
		var faceTangent = vec3.create();
		i = fLength;
		while(i--) {
			f = this.faces[i];
			// retrieve vertices of this face
			vA = this.vertices[f.a];
			vB = this.vertices[f.b];
			vC = this.vertices[f.c];
			vec3.subtract(vC.p, vB.p, vecCB);
			vec3.subtract(vA.p, vB.p, vecAB);
			// normal
			vec3.cross(vecCB, vecAB, faceNormal);
			vA.n = vec3.normalize(vec3.add(vA.n, faceNormal));
			vB.n = vec3.normalize(vec3.add(vB.n, faceNormal));
			vC.n = vec3.normalize(vec3.add(vC.n, faceNormal));
			// tangent
			uvCB = [ vC.uv[0]-vB.uv[0], vC.uv[1]-vB.uv[1] ];
			uvAB = [ vA.uv[0]-vB.uv[0], vA.uv[1]-vB.uv[1] ];
			coef = 1 / (uvCB[0]*uvAB[1]-uvAB[0]*uvCB[1]);
			faceTangent[0] = coef*((vecCB[0]*uvAB[1])+(vecAB[0]*-uvCB[1]));
			faceTangent[1] = coef*((vecCB[1]*uvAB[1])+(vecAB[1]*-uvCB[1]));
			faceTangent[2] = coef*((vecCB[2]*uvAB[1])+(vecAB[2]*-uvCB[1]));
			vA.t = vec3.normalize(vec3.add(vA.t,faceTangent));
			vB.t = vec3.normalize(vec3.add(vB.t,faceTangent));
			vC.t = vec3.normalize(vec3.add(vC.t,faceTangent));
		}

		return;	
		
		//Gram-Schmidt tangent orthogonalization
		for(i=0; i<vLength; i++) {
			var v = this.vertices[i];
			v.t = v.t.subtract(v.n.multiply(v.n.dot(v.t))).toUnitVector(); 
		}
	},

	createBuffers : function() {
	
		var i, facesOfMat, newFaces=[];
		this.materialsLengths = [];
		var scope = this;
		for(i = 0; i<this.materialList.length; i++) {
			facesOfMat = this.faces.filter( function(f){return f.mat==scope.materialList[i].name;});
			this.materialsLengths.push(facesOfMat.length);
			newFaces = newFaces.concat(facesOfMat);
		}
		var f = this.faces = newFaces;
		
		this.facesData = [];
		for (i = 0; i < f.length; i++) {
			this.facesData = this.facesData.concat(f[i].vertices);
		}
		
		this.facesBuffer = this.facesBuffer || gl.createBuffer();
		
		gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.facesBuffer);
		gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(this.facesData), gl.STATIC_DRAW);
		
		this.createVBO();
	},
	

	createVBO : function() {
			
		this.vboData = [];
		
		var i, v, pos, verts=this.vertices, vd=this.vboData, vLength = verts.length;
		this.hasNormals = (verts[0].n!=null);
		this.hasTangents = (verts[0].t!=null);
		this.hasUV = (verts[0].uv!=null);		
		this.vboInfos = [];
		
		this.pushVBOData("vertexPos", 3, "p");
		if(this.hasNormals){ this.pushVBOData("normal", 3, "n"); }
		if(this.hasTangents){ this.pushVBOData("tangent", 3, "t"); }
		if(this.hasUV){ this.pushVBOData("texCoord", 2, "uv"); }
						
		this.vbo = this.vbo || gl.createBuffer();
		gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
		gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vd), gl.STATIC_DRAW);
	},
	
	pushVBOData : function(attribName, numFloat, dataName) {
				
		var i, j, v, arr, args, pos, verts=this.vertices, vd=this.vboData, vLength = verts.length, stride=this.vboStride;
		for (i = 0; i < vLength; i++) {
			v = this.vertices[i];
			pos = (i+1)*stride+i*numFloat;
			arr = v[dataName];
			args = [pos, 0];
			for(j=0;j<numFloat;j++){ args.push(arr[j]);}
			vd.splice.apply( vd, args );
		}
		this.vboInfos.push({attrib:attribName, numFloat:numFloat});
		this.vboStride += numFloat;
	},

	draw : function() {
		
		mat4.inverse(this.mvMatrix, this.normalMatrix);
		mat4.transpose(this.normalMatrix);
		
		var totalMatOffset = 0;
		var j = 0;
		for (var j=0; j<this.materialList.length; j++) {
			
			var matLength = this.materialsLengths[j]*3; // 3 indices per face
			var mat = this.materialList[j];
			mat.enable();
			
			gl.uniformMatrix4fv(mat.pMatrixUniform, false, projectionMatrix);
			gl.uniformMatrix4fv(mat.mvMatrixUniform, false, this.mvMatrix);
			gl.uniformMatrix4fv(mat.nMatrixUniform, false, this.normalMatrix);
			
			gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
			
			var info, attrib, offset = 0, length=this.vboInfos.length;
			for(var i=0; i<length; i++) {
				info = this.vboInfos[i];
				attrib = mat[info.attrib+"Attrib"];
				gl.enableVertexAttribArray(attrib);
				gl.vertexAttribPointer(attrib, info.numFloat, gl.FLOAT, false, this.vboStride*4, offset*4);
				offset+=info.numFloat;
			}
					
			gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.facesBuffer);
			gl.drawElements(gl.TRIANGLES, matLength, gl.UNSIGNED_SHORT, totalMatOffset*2);
			
			for(var i=0; i<length; i++) {
				attrib = mat[this.vboInfos[i].attrib+"Attrib"];
				gl.disableVertexAttribArray(attrib);
			}
			
			totalMatOffset += matLength;
			
			mat.disable();
		}
	}
}
