/*
GLGE WebGL Graphics Engine
Copyright (C)2009  Paul Brunt

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
 * @fileOverview
 * @name glge.js
 * @author paul.brunt@armourhome.co.uk
 */



// This code is for Firefox/Minefield compatibility.  WebGL
  // originally used a class called CanvasFloatArray, but the
  // specication has now changed, and it's called WebGLFloatArray.
  // Firefox/Minefield still (as of 13 Nov 2009) uses the old
  // name, but WebKit/Safari uses the new one.  I've moved the
  // lessons over to the new name, but this code will stay here
  // for the time being to make sure that Firefox users (like
  // me!) can run the demos.
  //
  try
  {
    WebGLFloatArray;
  }
  catch (e)
  {
    try
    {
      WebGLFloatArray = CanvasFloatArray;
      WebGLUnsignedShortArray = CanvasUnsignedShortArray;
    }
    catch (e)
    {
      alert("Could not find any WebGL array types.");
    }
  }
  // End of compatibility code

  
/**
* @namespace Holds the functionality of the library
*/
var GLGE={};

(function(GLGE){
/**
* @class A bezier class to add points to the Animation Curve 
* @param {number} x1 x-coord of the first control point
* @param {number} y1 y-coord of the first control point
* @param {number} x2 x-coord of the second control point
* @param {number} y2 y-coord of the second control point
* @param {number} x3 x-coord of the third control point
* @param {number} y3 y-coord of the third control point
*/
GLGE.BezTriple=function(x1,y1,x2,y2,x3,y3){
	this.x1=x1;
	this.y1=y1;
	this.x2=x2;
	this.y2=y2;
	this.x3=x3;
	this.y3=y3;
};

/**
* @class A curve which interpolates between control points
*/
GLGE.AnimationCurve=function(){
    this.keyFrames=[];
};
GLGE.AnimationCurve.prototype.keyFrames=null;
/**
* Adds a point to the curve
* @param {BezPoint} bezPoint The bezier point to add
* @returns {Number} Index of the newly added point
*/
GLGE.AnimationCurve.prototype.addPoint=function(bezPoint){
	this.keyFrames.push(bezPoint);
	return this.keyFrames.length-1;
};
/**
* Get the value of the curve at any point
* @param {Number} frame The frame(x-coord) to return the value for
* @returns {Number} The value of the curve at the given point
*/
GLGE.AnimationCurve.prototype.coord=function(x,y){
	return {x:x,y:y}
}
GLGE.AnimationCurve.prototype.getValue=function(frame){
	var startKey=0;
	var endKey=0;
	//find the key frame bounds
	for(var i=0; i<this.keyFrames.length;i++){
		if(this.keyFrames[i].x2<frame && (this.keyFrames[i].x2>this.keyFrames[startKey].x2 || this.keyFrames[startKey].x2>frame)) startKey=i;
		if(this.keyFrames[i].x2>frame && (this.keyFrames[i].x2<this.keyFrames[endKey].x2 || this.keyFrames[endKey].x2<frame)) endKey=i;
	}
	var C1=this.coord(this.keyFrames[startKey].x2,this.keyFrames[startKey].y2);
	var C2=this.coord(this.keyFrames[startKey].x3,this.keyFrames[startKey].y3);
	var C3=this.coord(this.keyFrames[endKey].x1,this.keyFrames[endKey].y1);
	var C4=this.coord(this.keyFrames[endKey].x2,this.keyFrames[endKey].y2);
	return this.atX(frame,C1,C2,C3,C4).y;
};
/**
* Function used to calculate bezier curve
* @private
*/
GLGE.AnimationCurve.prototype.B1=function(t) { return t*t*t };
/**
* Function used to calculate bezier curve
* @private
*/
GLGE.AnimationCurve.prototype.B2=function(t) { return 3*t*t*(1-t) };
/**
* Function used to calculate bezier curve
* @private
*/
GLGE.AnimationCurve.prototype.B3=function(t) { return 3*t*(1-t)*(1-t) };
/**
* Function used to calculate bezier curve
* @private
*/
GLGE.AnimationCurve.prototype.B4=function(t) { return (1-t)*(1-t)*(1-t) };
/**
* Gets the value of a bezier curve at a given point
* @private
*/
GLGE.AnimationCurve.prototype.getBezier=function(t,C1,C2,C3,C4) {
	var pos = {};
	pos.x = C1.x*this.B1(t) + C2.x*this.B2(t) + C3.x*this.B3(t) + C4.x*this.B4(t);
	pos.y = C1.y*this.B1(t) + C2.y*this.B2(t) + C3.y*this.B3(t) + C4.y*this.B4(t);
	return pos;
};
/**
* Solves cubic equation to get the parametic value of the curve at a specified point
* @private
*/
GLGE.AnimationCurve.prototype.Quad3Solve=function(a,b,c,d){
	b /= a;c /= a;d /= a;
	var q, r, d1, s, t, t1, r13;
	q = (3.0*c - (b*b))/9.0;
	r = -(27.0*d) + b*(9.0*c - 2.0*(b*b));
	r /= 54.0;
	t1 = (b/3.0);
	discrim = q*q*q + r*r;
	result=[];
			
	if (discrim > 0) { 
	// one real, two complex
	 s = r + Math.sqrt(discrim);
	 s = ((s < 0) ? -Math.pow(-s, (1.0/3.0)) : Math.pow(s, (1.0/3.0)));
	 t = r - Math.sqrt(discrim);
	 t = ((t < 0) ? -Math.pow(-t, (1.0/3.0)) : Math.pow(t, (1.0/3.0)));
	 result[0] = -t1 + s + t;
	 t1 = t1 + (s + t)/2.0;
	 result[1] = result[2] = -t1;
	 t1 = Math.sqrt(3.0)*(-t + s)/2;
	} 
	else if (discrim == 0){ 
	// All roots real
	 r13 = ((r < 0) ? -Math.pow(-r,(1.0/3.0)) : Math.pow(r,(1.0/3.0)));
	 result[1] = -t1 + 2.0*r13;
	 result[1] = result[2]  = -(r13 + t1);
	} 
	else
	{
		q = -q;
		d1 = q*q*q;
		d1 = Math.acos(r/Math.sqrt(1));
		r13 = 2.0*Math.sqrt(q);


		result[0] = -t1 + r13*Math.cos(d1/3.0);
		result[1] = -t1 + r13*Math.cos((d1 + 2.0*Math.PI)/3.0);
		result[2] = -t1 + r13*Math.cos((d1 + 4.0*Math.PI)/3.0);
	}
	//determine which is the correct result
	if(result[0]>=0 && result[0]<=1) return result[0];
	if(result[1]>=0 && result[1]<=1) return result[1];
	if(result[2]>=0 && result[2]<=1) return result[2];
	return false;
};
/**
* Get the value of the a single bezier curve 
* @param {Number} x xcoord of point to get
* @param {Number} C1 First bezier control point
* @param {Number} C2 Second bezier control point
* @param {Number} C3 Third bezier control point
* @param {Number} C4 Forth bezier control point
* @returns {Number} The value of the curve at the given x
*/
GLGE.AnimationCurve.prototype.atX=function(x,C1,C2,C3,C4){
	a=C1.x-C2.x*3+C3.x*3-C4.x;
	b=C2.x*3-C3.x*6+C4.x*3;
	c=C3.x*3-C4.x*3;
	d=C4.x-x;
	return this.getBezier(this.Quad3Solve(a,b,c,d),C1,C2,C3,C4);
};

/**
* @class The AnimationVectors class allows you to specify the 2D Animation curves that define specific channels of animation within the engine. 
*/
GLGE.AnimationVector=function(){
    this.curves=[];
}
GLGE.AnimationVector.prototype.curves=[];
/**
* Adds an Animation Curve to a channel 
* @param {String} channel The name of the curve to be added
* @param {GLGE.AnimationCurve} curve The animation curve to add
*/
GLGE.AnimationVector.prototype.addCurve=function(name,curve){
	this.curves[name]=curve;
}
/**
* Removes an Animation Curve form a channel
* @param {String} channel The name of the curve to be removed
*/
GLGE.AnimationVector.prototype.removeCurve=function(name){
	delete(this.curves[name]);
}

GLGE.Bone=function(name,x,y,z){
	this.name=name;
	this.x=x;
	this.y=y;
	this.z=z;
}

GLGE.error=function(message){
	alert(message)
}



/*
Object.prototype.extends=function(superclass) {
  function Temp(){}
  Temp.prototype = superclass.prototype;
  this.prototype = new Temp();
  this.prototype.constructor = this;
  this.superclass = superclass;
  this.superproto = superclass.prototype;
} 

motion2DObject=function(){
}
motion2DObject.extends(meshObject);
motion2DObject.prototype.velocity={x:0,y:0};
motion2DObject.prototype.gravity={x:0,y:0};
motion2DObject.prototype.GLRender=function(gl){
	this.superclass.call(this, gl);
}
*/



/**
* @class Class specifing bones and there locations
*/
GLGE.Skeleton=function(){
    this.bones=[];
};
GLGE.Skeleton.prototype.bones=[];

GLGE.Skeleton.prototype.addBone=function(bone,parent){
	if(parent) bone.parent=parent;
	this.bones.push(bone);
}
GLGE.Skeleton.prototype.getBoneTransforms=function(bone,action,frame){
	var TRANS1=Matrix.transMat(bone.x*-1,bone.y*-1,bone.z*-1);
	var TRANS2=Matrix.transMat(bone.x,bone.y,bone.z);

	var result=Matrix.I(4);
	if(action && action.cache[frame][bone.name]) result=TRANS2.x(action.cache[frame][bone.name]).x(TRANS1);
	return result;
}
GLGE.Skeleton.prototype.boneFromName=function(name){
    for(var i=0; i<this.bones.length;i++){
        if(this.bones[i].name==name){
            return i;
            break;
        }
    }
    return false;
}
//returns an array containing the transforms of each of the bones
GLGE.Skeleton.prototype.getTransforms=function(action,frame){
	if(!action) alert(frame);
	var transforms={}
	for(var i=0; i<this.bones.length;i++){
            bone=this.bones[i];
            base=this.getBoneTransforms(bone,action,frame);
            while(bone.parent){
		if(!bone.parentIdx) bone.parentIdx=this.boneFromName(bone.parent);
                bone=this.bones[bone.parentIdx];
                base=this.getBoneTransforms(bone,action,frame).x(base);
            }
            transforms[this.bones[i].name]=({name:this.bones[i].name,matrix:base});
        }
	return transforms;
}




/**
* @class Class to perform an action on a skeleton
*/
GLGE.SkeletalAction=function(frames){
	this.frames=frames;
	this.AnimationVectors=[];
}
GLGE.SkeletalAction.prototype.frames=0;
GLGE.SkeletalAction.prototype.frameRate=60;
GLGE.SkeletalAction.prototype.loop=true;
GLGE.SkeletalAction.prototype.cache=null;
GLGE.SkeletalAction.prototype.AnimationVectors=null;
GLGE.SkeletalAction.prototype.addAnimationVector=function(boneName,AnimationVector){
	this.AnimationVectors[boneName]=AnimationVector;
}
GLGE.SkeletalAction.prototype.removeCurve=function(boneName){
	delete(this.AnimationVectors[boneName]);
}
//get the value of the curve for a given bone at a given frame
GLGE.SkeletalAction.prototype.boneValue=function(boneName,channel,frame){
	var result;
	var result=0;
	if(channel=="ScaleX" || channel=="ScaleY" || channel=="ScaleZ") result=1;
	if(this.AnimationVectors[boneName] && this.AnimationVectors[boneName].curves[channel]){
		result=this.AnimationVectors[boneName].curves[channel].getValue(frame);
	}
	return result;
}
//get the base transform for the given bone
GLGE.SkeletalAction.prototype.getBoneTransform=function(boneName,frame){
	var LOCX=this.boneValue(boneName,"LocX",frame);
	var LOCY=this.boneValue(boneName,"LocZ",frame);//flip coords from blender
	var LOCZ=this.boneValue(boneName,"LocY",frame);//flip coords from blender
	var SCALEX=this.boneValue(boneName,"ScaleX",frame);//flip coords from blender
	var SCALEY=this.boneValue(boneName,"ScaleZ",frame);//flip coords from blender
	var SCALEZ=this.boneValue(boneName,"ScaleY",frame);
	var QUATX=this.boneValue(boneName,"QuatX",frame);
	var QUATZ=this.boneValue(boneName,"QuatY",frame);//flip coords from blender
	var QUATY=this.boneValue(boneName,"QuatZ",frame);//flip coords from blender
	var QUATW=this.boneValue(boneName,"QuatW",frame);
	

	var QUAT=Matrix.quat2rot(QUATX,QUATY,QUATZ,QUATW);
	var LOC=Matrix.transMat(LOCX,LOCY,LOCZ);
	var SCALE=Matrix.scaleMat(SCALEX,SCALEY,SCALEZ);
	return LOC.x(QUAT).x(SCALE);
}
GLGE.SkeletalAction.prototype.cacheTransforms=function(){
	this.cache=[];
	var transforms;
	for(var frame=1; frame<=this.frames;frame++){
		transforms={};
		for(bone in this.AnimationVectors){
			if(this.AnimationVectors[bone] instanceof GLGE.AnimationVector){
				transforms[bone]=this.getBoneTransform(bone,frame+0.5);
			}
		}
		this.cache[frame]=transforms;
	}
}


/**
* @class An object that can be rendered in a scene
*/
GLGE.Object=function(){
	this.position={x:0,y:0,z:0};
	this.rotation={x:0,y:0,z:0};
}
GLGE.Object.prototype.action=null;
GLGE.Object.prototype.mesh=null;
GLGE.Object.prototype.skeleton=null;
GLGE.Object.prototype.scene=null;
GLGE.Object.prototype.position=null;
GLGE.Object.prototype.rotation=null;
GLGE.Object.prototype.transformMatrix=Matrix.I(4);
GLGE.Object.prototype.material=null;
GLGE.Object.prototype.gl=null;
GLGE.Object.prototype.actionStart=null;
GLGE.Object.prototype.blendState=null;
GLGE.Object.prototype.actionCache=null;

GLGE.Object.prototype.blendAction=function(action,duration){
    this.blendState=this.getBoneTransforms();
    this.setAction(action);
    this.blendDuration=duration;
    this.actionCache=[];
}
GLGE.Object.prototype.setAction=function(action){
    this.action=action;
    this.actionCache=[];
    if(!this.action.cache) this.action.cacheTransforms();
    this.actionStart=parseInt(new Date().getTime());
}
GLGE.Object.prototype.getAction=function(){
	return this.action
}
//working on moving the bone transforms to the object and off the skeleton!
GLGE.Object.prototype.getBoneTransforms=function(){
	var now=parseInt(new Date().getTime());
	var frame;
	if(this.action.frames>1){
		frame=((now-this.actionStart)/1000*this.action.frameRate)%(this.action.frames-1)+1; 
	}else{
		frame=1;
	}
	frame=Math.round(frame);
	if(this.actionCache[frame]){
		var transforms=this.actionCache[frame];
	}
	else
	{
		var transforms=this.skeleton.getTransforms(this.action,frame);
		if(this.blendState){
			var blendframe;
			var blendpoint=(now-this.actionStart)/this.blendDuration;
			if(blendpoint>=1){
				this.blendState=null;
				this.actionCache=[];
			}
			else
			{
			    //combine the two sets of transforms
			    var newtransforms={};
			    for(bone in transforms){
				if(transforms[bone].matrix){
					newtransforms[bone]={matrix:this.blendState[bone].matrix.x(1-blendpoint).add(transforms[bone].matrix.x(blendpoint))};
				}
			    }
			    transforms=newtransforms;            
			}
		}
		else
		{
			this.actionCache[frame]=transforms;
		}
	}
	return transforms;
}

GLGE.Object.prototype.setSkeleton=function(skeleton){
	this.skeleton=skeleton;
}
GLGE.Object.prototype.getSkeleton=function(){
	return this.skeleton;
}

GLGE.Object.prototype.setMaterial=function(material){
    this.material=material;
}
GLGE.Object.prototype.getMaterial=function(){
    return this.material;
}
GLGE.Object.prototype.setScene=function(scene){
    this.scene=scene;
}
GLGE.Object.prototype.getScene=function(){
    return this.scene;
}
GLGE.Object.prototype.setMesh=function(mesh){
	if(this.mesh) this.mesh.removeObject(this);
	this.mesh=mesh;
	mesh.addObject(this);
}
GLGE.Object.prototype.getMesh=function(){
	return this.mesh;
}
GLGE.Object.prototype.GLInit=function(gl){
	this.gl=gl;
	this.GLGenerateShader(gl);
}
GLGE.Object.prototype.updateProgram=function(){
	if(this.gl) this.GLGenerateShader(this.gl);
}
GLGE.Object.prototype.GLGenerateShader=function(gl){
	if(this.GLShaderProgram) gl.deleteProgram(this.GLShaderProgram);
	//create the programs strings
	//Vertex Shader
	var vertexStr="";
    for(var i=0;i<this.mesh.buffers.length;i++){
        if(this.mesh.buffers[i].size>1)
	        vertexStr=vertexStr+"attribute vec"+this.mesh.buffers[i].size+" "+this.mesh.buffers[i].name+";\n";
        else
	        vertexStr=vertexStr+"attribute float "+this.mesh.buffers[i].name+";\n";
    }
	
	vertexStr=vertexStr+"uniform mat4 MVMatrix;\n";
	vertexStr=vertexStr+"uniform mat4 PMatrix;\n";  
    //normals needed for lighting
	vertexStr=vertexStr+"uniform mat4 uNMatrix;\n"; 

    for(var i=0; i<this.scene.lights.length;i++){
        if(this.scene.lights[i].type==GLGE.L_POINT){
            vertexStr=vertexStr+"uniform vec3 lightpos"+i+";\n";
        }
    }
  
    for(var i=0; i<this.mesh.boneWeights.length; i++){
	vertexStr=vertexStr+"uniform mat4 "+this.mesh.boneWeights[i].boneName+"Matrix;\n";  
	vertexStr=vertexStr+"uniform mat4 "+this.mesh.boneWeights[i].boneName+"nMatrix;\n";  
    }
	
    vertexStr=vertexStr+"varying vec3 eyevec;\n"; 
    for(var i=0; i<this.scene.lights.length;i++){
        if(this.scene.lights[i].type==GLGE.L_POINT){
            vertexStr=vertexStr+"varying vec3 lightvec"+i+";\n"; 
            vertexStr=vertexStr+"varying float lightdist"+i+";\n"; 
        }
    }
    
	vertexStr=vertexStr+"varying vec3 n;\n";  
    vertexStr=vertexStr+"varying vec4 UVCoord;\n";
	
	vertexStr=vertexStr+"void main(void)\n";
	vertexStr=vertexStr+"{\n";
	vertexStr=vertexStr+"UVCoord=UV;\n";
	vertexStr=vertexStr+"vec4 pos = vec4(0.0, 0.0, 0.0, 1.0);\n";
	vertexStr=vertexStr+"vec4 norm = vec4(0.0, 0.0, 0.0, 1.0);\n";
    var cnt=0;
    //calculate the total bone weight
    var totalWeight=0;
    vertexStr=vertexStr+"float totalWeight=0.0";
    for(var i=0; i<this.mesh.boneWeights.length; i=i+4){
        if(!this.mesh.boneWeights[i+1]){
            vertexStr=vertexStr+"+bones"+cnt;
        }else{
            vertexStr=vertexStr+"+bones"+cnt+"["+(i%4)+"]";
        }
        if(this.mesh.boneWeights[i+1]) vertexStr=vertexStr+"+bones"+cnt+"["+(i%4+1)+"]";
        if(this.mesh.boneWeights[i+2]) vertexStr=vertexStr+"+bones"+cnt+"["+(i%4+2)+"]";
        if(this.mesh.boneWeights[i+3]) vertexStr=vertexStr+"+bones"+cnt+"["+(i%4+3)+"]";
        cnt++;
    }
    vertexStr=vertexStr+";\n";
    vertexStr=vertexStr+"if(totalWeight>0.0){\n";
    cnt=0;
    for(var i=0; i<this.mesh.boneWeights.length; i=i+4){
        if(!this.mesh.boneWeights[i+1]){
            vertexStr=vertexStr+"pos += ("+this.mesh.boneWeights[i].boneName+"Matrix * vec4(position, 1.0))*(bones"+cnt+"/totalWeight);\n";
            vertexStr=vertexStr+"norm += ("+this.mesh.boneWeights[i].boneName+"nMatrix * vec4(normal, 1.0))*(bones"+cnt+"/totalWeight);\n";
            }else{
            vertexStr=vertexStr+"pos += ("+this.mesh.boneWeights[i].boneName+"Matrix * vec4(position, 1.0))*(bones"+cnt+"["+(i%4)+"]/totalWeight);\n";
            vertexStr=vertexStr+"norm += ("+this.mesh.boneWeights[i].boneName+"nMatrix * vec4(normal, 1.0))*(bones"+cnt+"["+(i%4)+"]/totalWeight);\n";
        }
        if(this.mesh.boneWeights[i+1]) vertexStr=vertexStr+"pos += ("+this.mesh.boneWeights[i+1].boneName+"Matrix * vec4(position, 1.0))*(bones"+cnt+"["+(i%4+1)+"]/totalWeight);\n";
        if(this.mesh.boneWeights[i+2]) vertexStr=vertexStr+"pos += ("+this.mesh.boneWeights[i+2].boneName+"Matrix * vec4(position, 1.0))*(bones"+cnt+"["+(i%4+2)+"]/totalWeight);\n";
        if(this.mesh.boneWeights[i+3]) vertexStr=vertexStr+"pos += ("+this.mesh.boneWeights[i+3].boneName+"Matrix * vec4(position, 1.0))*(bones"+cnt+"["+(i%4+3)+"]/totalWeight);\n";
        if(this.mesh.boneWeights[i+1]) vertexStr=vertexStr+"norm += ("+this.mesh.boneWeights[i+1].boneName+"nMatrix * vec4(normal, 1.0))*(bones"+cnt+"["+(i%4+1)+"]/totalWeight);\n";
        if(this.mesh.boneWeights[i+2]) vertexStr=vertexStr+"norm += ("+this.mesh.boneWeights[i+2].boneName+"nMatrix * vec4(normal, 1.0))*(bones"+cnt+"["+(i%4+2)+"]/totalWeight);\n";
        if(this.mesh.boneWeights[i+3]) vertexStr=vertexStr+"norm += ("+this.mesh.boneWeights[i+3].boneName+"nMatrix * vec4(normal, 1.0))*(bones"+cnt+"["+(i%4+3)+"]/totalWeight);\n";
        cnt++;
    }
    vertexStr=vertexStr+"pos = MVMatrix * vec4(pos.xyz, 1.0);\n";
    vertexStr=vertexStr+"norm = uNMatrix * vec4(norm.xyz, 1.0);\n";
    vertexStr=vertexStr+"}else{\n";
    vertexStr=vertexStr+"pos = MVMatrix * vec4(position, 1.0);\n";
	vertexStr=vertexStr+"norm = uNMatrix * vec4(normal, 1.0);\n";
    vertexStr=vertexStr+"}\n";
   
    
	vertexStr=vertexStr+"gl_Position = PMatrix * pos;\n";
    vertexStr=vertexStr+"eyevec = -pos.xyz;\n";
    for(var i=0; i<this.scene.lights.length;i++){
        if(this.scene.lights[i].type==GLGE.L_POINT){
            vertexStr=vertexStr+"lightvec"+i+" = (lightpos"+i+"-pos.xyz);\n";
            vertexStr=vertexStr+"lightdist"+i+" = length(lightpos"+i+".xyz-pos.xyz);\n";
        }
    }
	vertexStr=vertexStr+"n = norm.rgb;\n";
	vertexStr=vertexStr+"}\n";
	
	//Fragment Shader
    if(!this.material){
	var fragStr="";
        fragStr=fragStr+"void main(void)\n";
        fragStr=fragStr+"{\n";
        fragStr=fragStr+"gl_FragColor = vec4(1.0,1.0,1.0,1);\n";
        fragStr=fragStr+"}\n";
    }
    else
    {
        fragStr=this.material.getFragmentShader(this.scene.lights);
    }

	if(!this.GLFragmentShader) this.GLFragmentShader=gl.createShader(gl.FRAGMENT_SHADER);
	if(!this.GLVertexShader) this.GLVertexShader=gl.createShader(gl.VERTEX_SHADER);

	//set and compile the fragment shader
	//need to set str
	gl.shaderSource(this.GLFragmentShader, fragStr);
	gl.compileShader(this.GLFragmentShader);
	if (!gl.getShaderi(this.GLFragmentShader, gl.COMPILE_STATUS))
	{
		GLGE.error(gl.getShaderInfoLog(this.GLFragmentShader));
		return null;
	}
	//set and compile the vertex shader
	//need to set str
	gl.shaderSource(this.GLVertexShader, vertexStr);
	gl.compileShader(this.GLVertexShader);
	if (!gl.getShaderi(this.GLVertexShader, gl.COMPILE_STATUS))
	{
		GLGE.error(gl.getShaderInfoLog(this.GLVertexShader));
		return null;
	}
	
	if(!this.GLShaderProgram) this.GLShaderProgram = gl.createProgram();
	gl.attachShader(this.GLShaderProgram, this.GLVertexShader);
	gl.attachShader(this.GLShaderProgram, this.GLFragmentShader);
	gl.linkProgram(this.GLShaderProgram);	
}
GLGE.Object.prototype.GLUniforms=function(gl){
	//generate and set the modelView matrix
	mvMatrix=this.scene.camera.getViewMatrix();
	mvMatrix=mvMatrix.x(Matrix.transMat(this.position.x,this.position.y,this.position.z));
	mvMatrix=mvMatrix.x(Matrix.rotMat(this.rotation.x,this.rotation.y,this.rotation.z));
    
	var mvUniform = gl.getUniformLocation(this.GLShaderProgram, "MVMatrix");
	gl.uniformMatrix4fv(mvUniform, false, new WebGLFloatArray(mvMatrix.flatten()));
	
	var pUniform = gl.getUniformLocation(this.GLShaderProgram, "PMatrix");
	gl.uniformMatrix4fv(pUniform, false, new WebGLFloatArray(this.scene.camera.pMatrix.flatten()));
    
	//normalising matrix
	var normalMatrix = mvMatrix.inverse();
	normalMatrix = normalMatrix.transpose();
	var nUniform = gl.getUniformLocation(this.GLShaderProgram, "uNMatrix");
	gl.uniformMatrix4fv(nUniform, false, new WebGLFloatArray(normalMatrix.flatten()));
    
	//light
	var pos;
	for(var i=0; i<this.scene.lights.length;i++){
		pos=this.scene.lights[i].position;
		//pos=this.position;
		pos=this.scene.camera.getViewMatrix().x($V([pos.x,pos.y,pos.z,1.0])).flatten();
		gl.uniform3f(gl.getUniformLocation(this.GLShaderProgram, "lightpos"+i), pos[0],pos[1],pos[2]);
	}
       
	//set bone transforms
	var boneUniform;
	var transforms={};
	if(this.action && this.skeleton){
		transforms=this.getBoneTransforms();
	}
	for(var i=0; i<this.mesh.boneWeights.length; i++){
		if(!transforms[this.mesh.boneWeights[i].boneName]) transforms[this.mesh.boneWeights[i].boneName]=Matrix.I(4);
		
		boneUniform = gl.getUniformLocation(this.GLShaderProgram, this.mesh.boneWeights[i].boneName+"Matrix");
		gl.uniformMatrix4fv(boneUniform, false, new WebGLFloatArray(transforms[this.mesh.boneWeights[i].boneName].matrix.flatten()));
        
		boneUniform = gl.getUniformLocation(this.GLShaderProgram, this.mesh.boneWeights[i].boneName+"nMatrix");
		gl.uniformMatrix4fv(boneUniform, false, new WebGLFloatArray(transforms[this.mesh.boneWeights[i].boneName].matrix.inverse().transpose().flatten()));
	}
    
	if(this.material) this.material.textureUniforms(gl,this.GLShaderProgram,this.scene.lights);
}
GLGE.Object.prototype.GLRender=function(gl){
	var attribslot;
	gl.useProgram(this.GLShaderProgram);
	
	this.mesh.GLAttributes(gl,this.GLShaderProgram);
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.mesh.GLfaces);

	this.GLUniforms(gl);
	//gl.drawArrays(gl.LINE_LOOP, 0, this.GLbuffers["position"].numItems);
	gl.drawElements(gl.TRIANGLES, this.mesh.GLfaces.numItems, gl.UNSIGNED_SHORT, 0);
}



/**
* @class Creates a new mesh to associate with a mesh
* @see GLGE.Object
*/
GLGE.Mesh=function(){
	this.GLbuffers=[];
	this.buffers=[];
	this.UV=[];
	this.boneWeights=[];
	this.setBuffers=[];
	this.faces={};
	this.objects=[];
}
GLGE.Mesh.prototype.gl=null;
GLGE.Mesh.prototype.GLbuffers=null;
GLGE.Mesh.prototype.buffers=null;
GLGE.Mesh.prototype.setBuffers=null;
GLGE.Mesh.prototype.GLfaces=null;
GLGE.Mesh.prototype.faces=null;
GLGE.Mesh.prototype.boneWeights=null;
GLGE.Mesh.prototype.UV=null;
GLGE.Mesh.prototype.objects=null;
/**
* Set the UV coord for the first UV layer
* @param {Number[]} jsArray the UV coords in a 1 dimentional array
*/
GLGE.Mesh.prototype.setUV=function(jsArray){
	var idx=0;
	for(var i=0; i<jsArray.length;i=i+2){
		this.UV[idx]=jsArray[i];
		this.UV[idx+1]=jsArray[i+1];
		if(!this.UV[idx+2]) this.UV[idx+2]=0;
		if(!this.UV[idx+3]) this.UV[idx+3]=0;
		idx=idx+4;
	}
	this.setBuffer("UV",this.UV,4);
}
/**
* Set the UV coord for the second UV layer
* @param {Number[]} jsArray the UV coords in a 1 dimentional array
*/
GLGE.Mesh.prototype.setUV2=function(jsArray){
	var idx=0;
	for(var i=0; i<jsArray.length;i=i+2){
		if(!this.UV[idx]) this.UV[idx]=0;
		if(!this.UV[idx+1]) this.UV[idx+1]=0;
		this.UV[idx+2]=jsArray[i];
		this.UV[idx+3]=jsArray[i+1];
		idx=idx+4;
	}
	this.setBuffer("UV",this.UV,4);
}
/**
* Sets the bone weights for a perticular bone
* @param {String} boneName The name of the bone
* @param {Number[]} jsArray The 1 dimentional array of weights
*/
GLGE.Mesh.prototype.addBoneWeights=function(boneName,jsArray){
	var bone={};
	bone.boneName=boneName;
	bone.weights=jsArray;
	this.boneWeights.push(bone);
    
	var weights;
	var cnt=0;
	for(var i=0; i<this.boneWeights.length; i=i+4){
		weights=[];
		size=1;
		for(var n=0; n<this.boneWeights[i].weights.length;n++){
			weights.push(this.boneWeights[i].weights[n]);
			if(this.boneWeights[i+1]){
				size=2;
				weights.push(this.boneWeights[i+1].weights[n]);
			}
			if(this.boneWeights[i+2]){
				size=3;
				weights.push(this.boneWeights[i+2].weights[n]);
			}
			if(this.boneWeights[i+3]){
				size=4;
				weights.push(this.boneWeights[i+3].weights[n]);
			}
		}
		this.setBuffer("bones"+cnt,weights,size);
		cnt++;
	}
}
/**
* Sets the positions of the verticies
* @param {Number[]} jsArray The 1 dimentional array of positions
*/
GLGE.Mesh.prototype.setPositions=function(jsArray){
	this.setBuffer("position",jsArray,3);
}
/**
* Sets the normals of the verticies
* @param {Number[]} jsArray The 1 dimentional array of normals
*/
GLGE.Mesh.prototype.setNormals=function(jsArray){
	this.setBuffer("normal",jsArray,3);
}
/**
* Sets a buffer for the
* @param {String} boneName The name of the bone
* @param {Number[]} jsArray The 1 dimentional array of weights
* @private
*/
GLGE.Mesh.prototype.setBuffer=function(bufferName,jsArray,size){
	var buffer;
	for(var i=0;i<this.buffers.length;i++){
		if(this.buffers[i].name==bufferName) buffer=i;
	}
	if(!buffer){
		this.buffers.push({name:bufferName,data:jsArray,size:size,GL:false});
		this.updatePrograms();
	}
        else 
	{
		this.buffers[buffer]={name:bufferName,data:jsArray,size:size,GL:false};
	}
}
/**
* Sets the faces for this mesh
* @param {Number[]} jsArray The 1 dimentional array of normals
*/
GLGE.Mesh.prototype.setFaces=function(jsArray){
	this.faces={data:jsArray,GL:false};

}
/**
* Sets the faces for this mesh
* @param {Number[]} jsArray The 1 dimentional array of normals
* @private
*/
GLGE.Mesh.prototype.GLSetFaceBuffer=function(gl){
	if(!this.GLfaces) this.GLfaces = gl.createBuffer();
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.GLfaces);
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(this.faces.data), gl.STATIC_DRAW);
	this.GLfaces.itemSize = 1;
	this.GLfaces.numItems = this.faces.data.length;
}
/**
* Sets up a GL Buffer
* @param {WebGLContext} gl The context being drawn on
* @param {String} bufferName The name of the buffer to create
* @param {Number[]}  jsArray The data to add to the buffer
* @param {Number}  size Size of a single element within the array
* @private
*/
GLGE.Mesh.prototype.GLSetBuffer=function(gl,bufferName,jsArray,size){
	if(!this.GLbuffers[bufferName]) this.GLbuffers[bufferName] = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, this.GLbuffers[bufferName]);
	gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(jsArray), gl.STATIC_DRAW);
	this.GLbuffers[bufferName].itemSize = size;
	this.GLbuffers[bufferName].numItems = jsArray.length/size;
}
/**
* Sets the Attributes for this mesh
* @param {WebGLContext} gl The context being drawn on
* @private
*/
GLGE.Mesh.prototype.GLAttributes=function(gl,shaderProgram){
	//disable all the attribute initially arrays - do I really need this?
	for(var i=0; i<8; i++) gl.disableVertexAttribArray(i);
	//check if the faces have been updated
	if(!this.faces.GL){
		this.GLSetFaceBuffer(gl);
		this.faces.GL=true;
	}
	//loop though the buffers
	for(i=0; i<this.buffers.length;i++){
		if(!this.buffers[i].GL){
			this.GLSetBuffer(gl,this.buffers[i].name,this.buffers[i].data,this.buffers[i].size);
			this.buffers[i].GL=true;
		}
		attribslot=gl.getAttribLocation(shaderProgram, this.buffers[i].name);
		gl.bindBuffer(gl.ARRAY_BUFFER, this.GLbuffers[this.buffers[i].name]);
		gl.enableVertexAttribArray(attribslot);
		gl.vertexAttribPointer(attribslot, this.GLbuffers[this.buffers[i].name].itemSize, gl.FLOAT, false, 0, 0);
	}
}
/**
* updates the programs for the objects when buffers are added/removed
* @private
*/
GLGE.Mesh.prototype.updatePrograms=function(){
	for(var i=0;i<this.objects.length;i++){
		this.objects[i].updateProgram();
	}
}
/**
* Adds a object to this mesh to be notified when a buffer is added/removed
* @param {GLGE.Object} object the object this mesh has been added to
* @private
*/
GLGE.Mesh.prototype.addObject=function(object){
	this.objects.push(object);
}
/**
* Removes the association with an object
* @param {GLGE.Object} object the object this mesh has been added to
* @private
*/
GLGE.Mesh.prototype.removeObject=function(object){
	//TODO: add remove code
}


/**
* @class Creates a new light source to be added to a scene
* @property {Boolean} diffuse Dose this light source effect diffuse shading
* @property {Boolean} specular Dose this light source effect specular shading
*/
GLGE.Light=function(type){
	this.position={x:0,y:0,z:0};
	this.rotation={x:0,y:0,z:0};
	this.rotationMatrix=Matrix.I(4);
	this.color={r:1,g:1,b:1};
	this.type=type;
}
/**
* @constant 
* @description Enumeration for an point light source
*/
GLGE.L_POINT=1;
/**
* @constant 
* @description Enumeration for an directional light source
*/
GLGE.L_DIR=2;
/**
* @constant 
* @description Enumeration for an spot light source
*/
GLGE.L_SPOT=3;

GLGE.Light.prototype.position=null;
GLGE.Light.prototype.rotation=null; 
GLGE.Light.prototype.rotationMatrix=null; 
GLGE.Light.prototype.constantAttenuation=1;
GLGE.Light.prototype.linearAttenuation=0.002;
GLGE.Light.prototype.quadraticAttenuation=0.0008;
GLGE.Light.prototype.color=null; 
GLGE.Light.prototype.diffuse=true; 
GLGE.Light.prototype.specular=true; 
GLGE.Light.type=GLGE.L_POINT;
/**
* Sets the light sources Attenuation
* @returns {Object} The components of the light sources attenuation
*/
GLGE.Light.prototype.getAttenuation=function(constant,linear,quadratic){
	var attenuation={};
	attenuation.constant=this.constantAttenuation;
	attenuation.linear=this.linearAttenuation;
	attenuation.quadratic=this.quadraticAttenuation;
	return attenuation;
}
/**
* Sets the light sources Attenuation
* @param {Number} constant The constant part of the attenuation
* @param {Number} linear The linear part of the attenuation
* @param {Number} quadratic The quadratic part of the attenuation
*/
GLGE.Light.prototype.setAttenuation=function(constant,linear,quadratic){
	this.constantAttenuation=constant;
	this.linearAttenuation=linear;
	this.quadraticAttenuation=quadratic;
}
/**
* Sets the position of the light in world coords
* @param {Number} x The new x position
* @param {Number} y The new y position
* @param {Number} z The new z position
*/
GLGE.Light.prototype.setPosition=function(x,y,z){
	this.position={x:x,y:y,z:z}
}
/**
* Gets the current position of the light
* @return {[x,y,z]} The current position
*/
GLGE.Light.prototype.getPosition=function(){
	return this.position;
}
/**
* Sets the color of the light source
* @param {Number} r The new red level 0-1
* @param {Number} g The new green level 0-1
* @param {Number} b The new blue level 0-1
*/
GLGE.Light.prototype.setColor=function(r,g,b){
	this.color={r:r,g:g,b:b};
}
/**
* Gets the current color of the light source
* @return {[r,g,b]} The current position
*/
GLGE.Light.prototype.getColor=function(){
	return this.color;
}
/**
* Sets the rotation of the light
* @param {Number} x The new x rotation
* @param {Number} y The new y rotation
* @param {Number} z The new z rotation
*/
GLGE.Light.prototype.setRotation=function(x,y,z){
	this.rotation={x:x,y:y,z:z};
	this.rotationMatrix=Matrix.rotMat(x,y,z);
}
/**
* Gets the current position of the light
* @return {[x,y,z]} The current position
*/
GLGE.Light.prototype.getRotation=function(){
	return this.rotation;
}
/**
* Gets the type of the light
* @return {Number} The type of the light source eg GLGE.L_POINT
*/
GLGE.Light.prototype.getType=function(){
	return this.type;
}


/**
* @class Creates a new camera object
*/
GLGE.Camera=function(){
	this.position={x:0,y:0,z:0};
	this.rotation={x:0,y:0,z:0};
        this.pMatrix=makePerspective(45, 1.0, 0.1, 100.0);
};
GLGE.Camera.prototype.position=null;
GLGE.Camera.prototype.rotation=null;
GLGE.Camera.prototype.pMatrix=null;
/**
* Sets the rotation of the camera in rads
* @param {Number} x The new x rotation
* @param {Number} y The new y rotation
* @param {Number} z The new z rotation
*/
GLGE.Camera.prototype.setRotation=function(x,y,z){
	this.rotation={x:x,y:y,z:z}
}
/**
* Gets the current position of the camera
* @return {[x,y,z]} The current position
*/
GLGE.Camera.prototype.getRotation=function(){
	return this.rotation;
}
/**
* Sets the position of the camera in world coords
* @param {Number} x The new x position
* @param {Number} y The new y position
* @param {Number} z The new z position
*/
GLGE.Camera.prototype.setPosition=function(x,y,z){
	this.position={x:x,y:y,z:z}
}
/**
* Gets the current position of the camera
* @return {[x,y,z]} The current position
*/
GLGE.Camera.prototype.getPosition=function(){
	return this.position;
}
/**
* Method gets the current projection matrix of this camera
* @return {Matrix} Returns the camera projection matrix
*/
GLGE.Camera.prototype.getProjectionMatrix=function(){
	return this.pMatrix;
};
/**
* Method generates the projection matrix based on the 
* camera paramaters
* @param {Matrix} projection The new projection matrix
*/
GLGE.Camera.prototype.setProjectionMatrix=function(projection){
	this.pMatrix=projection;
};
/**
* Method generates the cameras view matrix
* @return Returns the view matrix based on this camera
* @type Matrix
*/
GLGE.Camera.prototype.getViewMatrix=function(){
	var vMatrix=Matrix.rotMat(this.rotation.x,this.rotation.y,this.rotation.z);
	vMatrix=vMatrix.x(Matrix.transMat(this.position.x,this.position.z,-this.position.y));
	return vMatrix.inverse();
};

/**
* @class Scene class containing the camera, lights and objects
*/
GLGE.Scene=function(){
	this.objects=[];
	this.lights=[];
	this.camera=new GLGE.Camera();
}
GLGE.Scene.prototype.camera=null;
GLGE.Scene.prototype.objects=null;
GLGE.Scene.prototype.lights=null;
GLGE.Scene.prototype.renderer=null;
/**
* Gets an array of all the object in the scene
* @returns {GLGE.Object[]} An array of objects
*/
GLGE.Scene.prototype.getObjects=function(){	
	return this.objects;
}
/**
* Gets an array of all the light sources in the scene
* @returns {GLGE.Lights[]} An array of lights
*/
GLGE.Scene.prototype.getLights=function(){	
	return this.lights;
}
/**
* Sets the active camera for this scene
* @property {GLGE.Camera} object The object to be added
*/
GLGE.Scene.prototype.setCamera=function(camera){	
	this.camera=camera;
}
/**
* Gets the scenes active camera
* @returns {GLGE.Camera} The current camera
*/
GLGE.Scene.prototype.getCamera=function(){	
	return this.camera;
}
/**
* Adds an object to the scene
* @property {GLGE.Object} object The object to be added
* @returns {Number} The index of the added object
*/
GLGE.Scene.prototype.addObject=function(object){	
	object.scene=this;
	this.objects.push(object);
	if(this.renderer) object.GLInit(this.renderer.gl);
	return this.objects.length-1;
}
/**
* Adds a light source to the scene
* @property {GLGE.Light} light The light to be added
* @returns {Number} The index of the added light
*/
GLGE.Scene.prototype.addLight=function(light){	
	light.scene=this;
	this.lights.push(light);
}
/**
* used to initialize all the WebGL buffers etc need for this scene
* @private
*/
GLGE.Scene.prototype.init=function(gl){
    for(var i=0;i<this.objects.length;i++){
        this.objects[i].GLInit(this.renderer.gl);
    }
}
/**
* used to clean up all the WebGL buffers etc need for this scene
* @private
*/
GLGE.Scene.prototype.destory=function(gl){
}
/**
* renders the scene
* @private
*/
GLGE.Scene.prototype.render=function(gl){
	for(var i=0; i<this.objects.length;i++){
		this.objects[i].GLRender(this.renderer.gl);
	}
}

/**
* Sets the scene to render
* @param {GLGE.Scene} scene The scene to be rendered
*/
GLGE.Renderer=function(canvas){
	try{
	      this.gl = canvas.getContext("webkit-3d");
	    }catch(e){}
	if (!this.gl){
	      try
	      {
		this.gl = canvas.getContext("moz-webgl");
	      }catch(e){}
	}
	if (!this.gl)
	{
	alert("What, What Whaaat? No WebGL!");
	return false;
	}
	//set up defaults
	this.gl.clearColor(0.2, 0.2, 0.2, 1.0);
	this.gl.clearDepth(1.0);
	this.gl.enable(this.gl.DEPTH_TEST);
	//this.gl.enable(this.gl.SAMPLE_ALPHA_TO_COVERAGE);
    
	this.gl.depthFunc(this.gl.LEQUAL);
};
GLGE.Renderer.prototype.gl=null;
GLGE.Renderer.prototype.scene=null;
/**
* Gets the scene which is set to be rendered
* @returns the current render scene
*/
GLGE.Renderer.prototype.getScene=function(gcene){
	return this.scene;
};
/**
* Sets the scene to render
* @param {GLGE.Scene} scene The scene to be rendered
*/
GLGE.Renderer.prototype.setScene=function(scene){
	scene.renderer=this;
	this.scene=scene;
	scene.init();
};
/**
* Renders the current scene to the canvas
*/
GLGE.Renderer.prototype.render=function(){
	this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
	this.scene.render();
};
/**
* @class The material layer describes how to apply this layer to the material
* @param {Number} texture the texture index to apply to the layer
* @param {Number} mapping how to map this layer on to the material see M_XXXXX constants
* @param {Number} uv the UV layer to map to, UV1 or UV2
* @param {Object} scale how much scaling the texture eg {x: 10, y:10, z:1}
* @param {Object} offset how much to offset the texture eg {x: 10, y:10, z:1}
* @see GLGE.Material
*/
GLGE.MaterialLayer=function(texture,mapping,uv,scale,offset){
	this.texture=texture;
	this.mapping=mapping;
	this.uv=uv;
	this.scale=scale;
	this.offset=offset;
};
GLGE.MaterialLayer.prototype.texture=null;
GLGE.MaterialLayer.prototype.mapping=null;
GLGE.MaterialLayer.prototype.uv=null;
GLGE.MaterialLayer.prototype.scale=null;
GLGE.MaterialLayer.prototype.offset=null;

/**
* @class The Material class creates materials to be applied to objects in the graphics engine
* @see GLGE.Object
*/
GLGE.Material=function(){
	this.layers=[];
	this.textures=[];
	this.lights=[];
	this.color={r:1,g:1,b:1,a:1};
	this.specColor={r:1,g:1,b:1};
	this.reflect=0.8;
	this.shine=50;
	this.specular=1;
	this.emit=0;
};
/**
* @constant 
* @description Flag for material colour
*/
GLGE.M_COLOR=1; 
/**
* @constant 
* @description Flag for material normal
*/
GLGE.M_NOR=2;
/**
* @constant 
* @description Flag for material alpha
*/
GLGE.M_ALPHA=4; 
/**
* @constant 
* @description Flag for material specular color
*/
GLGE.M_SPECCOLOR=8; 
/**
* @constant 
* @description Flag for material specular cvalue
*/
GLGE.M_SPECULAR=16;
/**
* @constant 
* @description Flag for material shineiness
*/
GLGE.M_SHINE=32; 
/**
* @constant 
* @description Flag for material reflectivity
*/
GLGE.M_REFLECT=64;
/**
* @constant 
* @description Flag for material emision
*/
GLGE.M_EMIT=128;
/**
* @constant 
* @description Flag for masking with textures red value
*/
GLGE.M_MSKR=256;
/**
* @constant 
* @description Flag for masking with textures green value
*/
GLGE.M_MSKG=512;
/**
* @constant 
* @description Flag for masking with textures blue value
*/
GLGE.M_MSKB=1024;
/**
* @constant 
* @description Flag for masking with textures alpha value
*/
GLGE.M_MSKA=2048;

/**
* @constant 
* @description Enumeration for first UV layer
*/
GLGE.UV1=0;
/**
* @constant 
* @description Enumeration for second UV layer
*/
GLGE.UV2=1;
	
GLGE.Material.prototype.layers=null;
GLGE.Material.prototype.textures=null;
GLGE.Material.prototype.color=null;
GLGE.Material.prototype.specColor=null;
GLGE.Material.prototype.specular=null;
GLGE.Material.prototype.emit=null;
GLGE.Material.prototype.shine=null;
GLGE.Material.prototype.reflect=null;
GLGE.Material.prototype.lights=null;

/**
* Sets the base colour of the material
* @param {Number} r The new red level 0-1
* @param {Number} g The new green level 0-1
* @param {Number} b The new blue level 0-1
*/
GLGE.Material.prototype.setColor=function(r,g,b){
	this.color={r:r,g:g,b:b};
};
/**
* Gets the current base color of the material
* @return {[r,g,b]} The current base color
*/
GLGE.Material.prototype.getColor=function(){
	return this.color;
};
/**
* Sets the base specular colour of the material
* @param {Number} r The new red level 0-1
* @param {Number} g The new green level 0-1
* @param {Number} b The new blue level 0-1
*/
GLGE.Material.prototype.setSpecularColor=function(r,g,b){
	this.specColor={r:r,g:g,b:b};
};
/**
* Gets the current base specular color of the material
* @return {[r,g,b]} The current base specular color
*/
GLGE.Material.prototype.getSpecularColor=function(){
	return this.specColor;
};
/**
* Sets the shininess of the material
* @param {Number} value how much shine
*/
GLGE.Material.prototype.setShininess=function(value){
	this.shine=value;
};
/**
* Gets the shininess of the material
* @return {Number} The current shininess of the material
*/
GLGE.Material.prototype.getShininess=function(){
	return this.shine;
};
/**
* Sets how much the material should emit
* @param {Number} value how much to emit (0-1)
*/
GLGE.Material.prototype.setEmit=function(value){
	this.emit=value;
};
/**
* Gets the amount this material emits
* @return {Number} The emit value for the material
*/
GLGE.Material.prototype.getEmit=function(){
	return this.emit;
};
/**
* Sets reflectivity of the material
* @param {Number} value how much to reflect (0-1)
*/
GLGE.Material.prototype.setReflectivity=function(value){
	this.reflect=value;
};
/**
* Gets the materials reflectivity
* @return {Number} The reflectivity of the material
*/
GLGE.Material.prototype.getReflectivity=function(){
	return this.reflect;
};

/**
* Add a new layer to the material
* @param {MaterialLayer} layer The material layer to add to the material
* @returns {Number} index of the added layer
*/
GLGE.Material.prototype.addLayer=function(layer){
	this.layers.push(layer);
	return this.layers.length-1;
};
/**
* Gets all the materials layers
* @returns {GLGE.MaterialLayer[]} all of the layers contained within this material
*/
GLGE.Material.prototype.getLayers=function(){
	return this.layers;
};
/**
* Generate the fragment shader program for this material
* @private
*/
GLGE.Material.prototype.getFragmentShader=function(lights,ambiantColor){
	var shader="";
    
	for(var i=0; i<lights.length;i++){
		if(lights[i].type==GLGE.L_POINT){
			shader=shader+"varying vec3 lightvec"+i+";\n"; 
			shader=shader+"varying float lightdist"+i+";\n";  
		}
	}
	shader=shader+"varying vec3 n;\n";  
	shader=shader+"varying vec4 UVCoord;\n";
	shader=shader+"varying vec3 eyevec;\n"; 

	//texture uniforms
	for(var i=0; i<this.textures.length;i++){
		shader=shader+"uniform sampler2D TEXTURE"+i+";\n";
	}
	for(var i=0; i<lights.length;i++){
		if(lights[i].type==GLGE.L_AMBIENT){
			shader=shader+"uniform vec3 lightcolor"+i+";\n";  
		}
		if(lights[i].type==GLGE.L_POINT){
			shader=shader+"uniform vec3 lightcolor"+i+";\n";  
			shader=shader+"uniform vec3 lightAttenuation"+i+";\n";  
		}
	}
	shader=shader+"uniform vec4 baseColor;\n";
	shader=shader+"uniform vec3 specColor;\n";
	shader=shader+"uniform float shine;\n";
	shader=shader+"uniform float specular;\n";
	shader=shader+"uniform float reflect;\n";
	shader=shader+"uniform float emit;\n";
    
	shader=shader+"void main(void)\n";
	shader=shader+"{\n";
	shader=shader+"float att;\n"; 
	shader=shader+"float mask=1.0;\n"; //initial value of the mask
	shader=shader+"float spec=specular;\n"; 
	shader=shader+"vec3 specC=specColor;\n"; 
	shader=shader+"float ref=reflect;\n";
	shader=shader+"float sh=shine;\n"; 
	shader=shader+"float em=emit;\n"; 
	shader=shader+"vec4 normalmap=vec4(0.5,0.5,0.5,0.0);\n"
	shader=shader+"vec4 color = baseColor;"; //set the initial color
	for(i=0; i<this.layers.length;i++){
		if((this.layers[i].mapping & GLGE.M_COLOR) == GLGE.M_COLOR){
			shader=shader+"color = color*(1.0-mask) + texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+"))*mask;\n";
		}        
		if((this.layers[i].mapping & GLGE.M_SPECCOLOR) == GLGE.M_SPECCOLOR){
			shader=shader+"specC = specC*(1.0-mask) + texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+")).rgb*mask;\n";
		}
		if((this.layers[i].mapping & GLGE.M_MSKR) == GLGE.M_MSKR){
			shader=shader+"mask = texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+")).r;\n";
		}
		if((this.layers[i].mapping & GLGE.M_MSKG) == GLGE.M_MSKG){
			shader=shader+"mask = texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+")).g;\n";
		}
		if((this.layers[i].mapping & GLGE.M_MSKG) == GLGE.M_MSKB){
			shader=shader+"mask = texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+")).b;\n";
		}
		if((this.layers[i].mapping & GLGE.M_MSKG) == GLGE.M_MSKA){
			shader=shader+"mask = texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+")).a;\n";
		}
		if((this.layers[i].mapping & GLGE.M_SPECULAR) == GLGE.M_SPECULAR){
			shader=shader+"spec = spec*(1.0-mask) + texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+")).r*mask*10.0;\n";
		}
		if((this.layers[i].mapping & GLGE.M_REFLECT) == GLGE.M_REFLECT){
			shader=shader+"ref = ref*(1.0-mask) + texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+")).g*mask;\n";
		}
		if((this.layers[i].mapping & GLGE.M_SHINE) == GLGE.M_SHINE){
			shader=shader+"sh = sh*(1.0-mask) + texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+")).b*mask*512.0;\n";
		}
		if((this.layers[i].mapping & GLGE.M_EMIT) == GLGE.M_EMIT){
			shader=shader+"em = em*(1.0-mask) + texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+")).r*mask;\n";
		}
		if((this.layers[i].mapping & GLGE.M_NOR) == GLGE.M_NOR){
			shader=shader+"normalmap = normalmap*(1.0-mask) + texture2D(TEXTURE"+this.layers[i].texture+", vec2(UVCoord["+(this.layers[i].uv*2)+"]*"+this.layers[i].scale.x.toFixed(4)+",(1.0-UVCoord["+(this.layers[i].uv*2+1)+"])*"+this.layers[i].scale.y.toFixed(4)+"))*mask;\n";
		}
	}
	shader=shader+"normalmap=(normalmap-vec4(0.5,0.5,0.5,0.5))*2.0;\n";
	shader=shader+"vec3 normal = normalize(n+normalmap.rgb);\n";
	shader=shader+"vec3 lightvalue=vec3(0.0,0.0,0.0);\n"; 
	shader=shader+"vec3 specvalue=vec3(0.0,0.0,0.0);\n"; 
	shader=shader+"float dotN;";
	for(var i=0; i<lights.length;i++){
		if(lights[i].type==GLGE.L_POINT){
			shader=shader+"dotN=max(dot(normal,normalize(lightvec"+i+")),0.0);\n";       
			shader=shader+"att = 1 / (lightAttenuation"+i+"[0] + lightAttenuation"+i+"[1] * lightdist"+i+" + lightAttenuation"+i+"[2] * lightdist"+i+" * lightdist"+i+");\n";
			if(lights[i].diffuse){
				shader=shader+"lightvalue += att * dotN * lightcolor"+i+";\n";
			}
			if(lights[i].specular){
				shader=shader+"if(dotN>0.0){\n";
				shader=shader+"specvalue += att * specC * lightcolor"+i+" * spec  * pow(max(dot(normal,normalize(eyevec)),0.0), sh);\n";
				shader=shader+"}\n";
			}
		}
	}
	shader=shader+"lightvalue *= ref;\n"
	shader=shader+"gl_FragColor = vec4(specvalue,0.0)+vec4(color.r*em+(color.r*lightvalue.r*(1.0-em)),color.g*em+(color.g*lightvalue.g*(1.0-em)),color.b*em+(color.b*lightvalue.b*(1.0-em)),1.0);\n";
	shader=shader+"}\n";
	return shader;
};
/**
* Set the uniforms needed to render this material
* @private
*/
GLGE.Material.prototype.textureUniforms=function(gl,shaderProgram,lights){
	gl.uniform4f(gl.getUniformLocation(shaderProgram, "baseColor"), this.color.r,this.color.g,this.color.b,this.color.a);
	gl.uniform3f(gl.getUniformLocation(shaderProgram, "specColor"), this.specColor.r,this.specColor.g,this.specColor.b);
	gl.uniform1f(gl.getUniformLocation(shaderProgram, "specular"), this.specular);
	gl.uniform1f(gl.getUniformLocation(shaderProgram, "shine"), this.shine);
	gl.uniform1f(gl.getUniformLocation(shaderProgram, "reflect"), this.reflect);
	gl.uniform1f(gl.getUniformLocation(shaderProgram, "emit"), this.emit);
	for(var i=0; i<lights.length;i++){
		if(lights[i].type==GLGE.L_POINT){
		    gl.uniform3f(gl.getUniformLocation(shaderProgram, "lightcolor"+i), lights[i].color.r,lights[i].color.g,lights[i].color.b);
		    gl.uniform3f(gl.getUniformLocation(shaderProgram, "lightAttenuation"+i), lights[i].constantAttenuation,lights[i].linearAttenuation,lights[i].quadraticAttenuation);
		}
	}
    
	for(var i=0; i<this.textures.length;i++){
        eval("gl.activeTexture(gl.TEXTURE"+i+")"); //evil code but I can't see another way right now
		//create the texture if it's not already created
		if(!this.textures[i].glTexture) this.textures[i].glTexture=gl.createTexture();
		//if the image is loaded then set in the texture data
		if(this.textures[i].state==1){
			gl.bindTexture(gl.TEXTURE_2D, this.textures[i].glTexture);
			gl.texImage2D(gl.TEXTURE_2D, 0, this.textures[i].image);
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
			gl.bindTexture(gl.TEXTURE_2D, null);
			this.textures[i].state=2;
		}
		gl.bindTexture(gl.TEXTURE_2D, this.textures[i].glTexture);
		gl.uniform1i(gl.getUniformLocation(shaderProgram, "TEXTURE"+i), i);
	}
};
/**
* Adds a new texture to this material
* @param {String} image URL of the image to be used by the texture
* @return {Number} index of the new texture
*/
GLGE.Material.prototype.addTexture=function(image){
	var newTexture={};
	newTexture.image=new Image();
	newTexture.image.texture=newTexture;
	newTexture.image.onload = function(){
		this.texture.state=1;
	}	
	newTexture.image.src=image;	
	newTexture.state=0;
	newTexture.glTexture=null;
	
	this.textures.push(newTexture);
	
	return (this.textures.length-1);
};
})(GLGE);


