//fix code
// 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



//BezTriple Object
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;
};

//bezier keyframe animation curve
function animationCurve(){
    this.keyFrames=[];
};
animationCurve.prototype.totalFrames=50;
animationCurve.prototype.keyFrames=null;
animationCurve.prototype.coord = function (x,y) {
	if(!x) var x=0;
	if(!y) var y=0;
	return {x: x, y: y};
};

//adds a keyframe point to the curve
animationCurve.prototype.addPoint=function(bezPoint){
	this.keyFrames.push(bezPoint);
};
//get the value of the curve at any point
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;
};
//Bezier Curve Helpers
animationCurve.prototype.B1=function(t) { return t*t*t };
animationCurve.prototype.B2=function(t) { return 3*t*t*(1-t) };
animationCurve.prototype.B3=function(t) { return 3*t*(1-t)*(1-t) };
animationCurve.prototype.B4=function(t) { return (1-t)*(1-t)*(1-t) };
//Parametric Bezier Curve
animationCurve.prototype.getBezier=function(t,C1,C2,C3,C4) {
	var pos = new this.coord();
	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;
};
//only does real parts as that is all we are interested in!
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 Y value of the curve at a given X
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);
};


//contains associative array of Animation Curves
var Ipo=function(){
    this.curves=[];
}
Ipo.prototype.curves=[];
Ipo.prototype.addCurve=function(name,curve){
	this.curves[name]=curve;
}
Ipo.prototype.removeCurve=function(name){
	delete(this.curves[name]);
}

//contains associative array of Ipos for bone animation
var Action=function(frames){
	this.frames=frames;
    this.ipo=[];
}
Action.prototype.frames=0;
Action.prototype.frameRate=25;
Action.prototype.loop=true;
Action.prototype.ipo=[];
Action.prototype.addIpo=function(boneName,ipo){
	this.ipo[boneName]=ipo;
}
Action.prototype.removeCurve=function(boneName){
	delete(this.ipo[boneName]);
}

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

var error=function(message){
	alert(message)
}
var skeleton=function(){
    this.bones=[];
};
skeleton.prototype.bones=[];
skeleton.prototype.action=null;
skeleton.prototype.blendState=null;
skeleton.prototype.blendDuration=null;
skeleton.prototype.actionPlaying=false;
skeleton.prototype.actionStart=0;
skeleton.prototype.addBone=function(bone,parent){
	if(parent) bone.parent=parent;
	this.bones.push(bone);
}
skeleton.prototype.removeBone=function(boneName){
	//problem here with removing parent bones
}
skeleton.prototype.setAction=function(action){
	this.action=action;
    if(!this.action.cache) this.cacheTransforms(action);
    this.actionStart=parseInt(new Date().getTime());
}
skeleton.prototype.blendAction=function(action,duration){
    this.blendState=this.getTransforms();
    this.setAction(action);
    this.blendDuration=duration;
}
skeleton.prototype.getAction=function(action){
	return this.action;
}
//get the value of the curve for a given bone at a given frame
skeleton.prototype.boneValue=function(boneName,curve,frame){
	//set defaults
	var result=0;
	if(curve=="ScaleX" || curve=="ScaleY" || curve=="ScaleZ") result=1;
	if(!this.action) error("No action set");
	if(this.action.ipo[boneName] && this.action.ipo[boneName].curves[curve]){
		result=this.action.ipo[boneName].curves[curve].getValue(frame);
	}
	return result;
}
//get the base transform for the given bone
skeleton.prototype.getBoneTransform=function(idx,frame){
    boneName=this.bones[idx].name;
	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);
	var SCALEY=this.boneValue(boneName,"ScaleY",frame);
	var SCALEZ=this.boneValue(boneName,"ScaleZ",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 TRANS1=transMat(this.bones[idx].x*-1,this.bones[idx].y*-1,this.bones[idx].z*-1);
	var QUAT=quat2rot(QUATX,QUATY,QUATZ,QUATW);
	var LOC=transMat(LOCX,LOCY,LOCZ);
	var SCALE=scaleMat(SCALEX,SCALEY,SCALEZ);
	var TRANS2=transMat(this.bones[idx].x,this.bones[idx].y,this.bones[idx].z);
	return TRANS2.x(LOC).x(QUAT).x(SCALE).x(TRANS1);
}
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;
}
skeleton.prototype.cacheTransforms=function(){
    var base;
    var baseBone;
    var btrans={};
    this.action.cache=[];
    for(var frame=1; frame<=this.action.frames;frame++){
        var transforms={};
        //lookup all the bone transforms first
        for(var i=0; i<this.bones.length;i++){
            //add 0.5 to frame to prevent errors?
            btrans[this.bones[i].name]=this.getBoneTransform(i,frame+0.5);
        }
        
        for(var i=0; i<this.bones.length;i++){
            base=btrans[this.bones[i].name];
            bone=this.bones[i];
            while(bone.parent){
                //cache so we don't need to lookup
                if(!bone.parentIdx) bone.parentIdx=this.boneFromName(bone.parent);
                base=btrans[bone.parent].x(base);
                bone=this.bones[bone.parentIdx];
            }
            transforms[this.bones[i].name]=({name:this.bones[i].name,matrix:base});
        }
        this.action.cache[parseInt(Math.floor(frame))]=transforms;
    }
}
//returns an array containing the transforms of each of the bones
skeleton.prototype.getTransforms=function(){
	//if playing then query the ipo and generate new transforms
	var now=parseInt(new Date().getTime());
    var frame;
    if(this.action.frames>1){
	    frame=((now-this.actionStart)/this.action.frameRate)%(this.action.frames-1)+1; 
    }else{
        frame=1;
    }
	frame=Math.round(frame);
    var transforms=this.action.cache[frame];
    if(this.blendState){
        var blendframe;
        var blendpoint=(now-this.actionStart)/this.blendDuration;
        if(blendpoint>=1){
            this.blendState=null;
        }
        else
        {
            //combine the two sets of transforms
            var newtransforms={};
            for(var i=0; i<this.bones.length;i++){
                newtransforms[this.bones[i].name]={matrix:this.blendState[this.bones[i].name].matrix.x(1-blendpoint).add(transforms[this.bones[i].name].matrix.x(blendpoint))};
            } 
            transforms=newtransforms;            
        }
    }
	return transforms;
}


//vertices normals uv1 uv2 normals boneweights
var meshObject=function(name){
    this.position={x:1,y:1,z:1};
    this.rotation={x:0,y:0,z:0};
    this.GLbuffers=[];
    this.buffers=[];
    this.boneWeights=[];
};
meshObject.prototype.gl=null;
meshObject.prototype.GLbuffers=null;
meshObject.prototype.buffers=null;
meshObject.prototype.GLfaces=null;
meshObject.prototype.faces=null;
meshObject.prototype.skeleton=null;
meshObject.prototype.boneWeights=null;
meshObject.prototype.UV=null;
meshObject.prototype.GLVertexShader=null;
meshObject.prototype.GLFragmentShader=null;
meshObject.prototype.GLShaderProgram=null;
meshObject.prototype.scene=null;
meshObject.prototype.position=null;
meshObject.prototype.rotation=null;
meshObject.prototype.material=null;

meshObject.prototype.setSkeleton=function(skeleton){
	this.skeleton=skeleton;
}
meshObject.prototype.setUV=function(jsArray){
    this.setBuffer("UV",jsArray,2);
}
meshObject.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++;
    }
}
//max of 16 bones if we want hardware acceleration and two UV layers!
meshObject.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});
        else this.buffers[buffer]={name:bufferName,data:jsArray,size:size};
	//if we are already part of a renderer then update the buffer
	if(this.renderer) GLSetBuffer(this.renderer.gl,bufferName,jsArray,size);
}
meshObject.prototype.setFaces=function(jsArray){
	this.faces=jsArray;
	//if we are already part of a renderer then update the buffer
	if(this.renderer) GLSetFaceBuffer(this.renderer.gl);
}
meshObject.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), gl.STATIC_DRAW);
	this.GLfaces.itemSize = 1;
	this.GLfaces.numItems = this.faces.length;
}
meshObject.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;
}
meshObject.prototype.GLInit=function(gl){
	this.GLGenerateShader(gl);
    for(var i=0;i<this.buffers.length;i++){
	this.GLSetBuffer(gl,this.buffers[i].name,this.buffers[i].data,this.buffers[i].size);
    }
	this.GLSetFaceBuffer(gl);
}
meshObject.prototype.setMaterial=function(material){
    this.material=material;
}
//generates and links the shaderProgram
meshObject.prototype.GLGenerateShader=function(gl){
	//create the programs strings
	//Vertex Shader
	var vertexStr="";
    for(var i=0;i<this.buffers.length;i++){
        if(this.buffers[i].size>1)
	        vertexStr=vertexStr+"attribute vec"+this.buffers[i].size+" "+this.buffers[i].name+";\n";
        else
	        vertexStr=vertexStr+"attribute float "+this.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";
	vertexStr=vertexStr+"uniform vec3 uLightingDirection;\n";  
    
  
    for(var i=0; i<this.boneWeights.length; i++){
	vertexStr=vertexStr+"uniform mat4 "+this.boneWeights[i].boneName+"Matrix;\n";  
	vertexStr=vertexStr+"uniform mat4 "+this.boneWeights[i].boneName+"nMatrix;\n";  
    }
	vertexStr=vertexStr+"varying vec3 light;\n"; 
	vertexStr=vertexStr+"varying vec3 halflight;\n"; 
	vertexStr=vertexStr+"varying vec3 n;\n";  
    vertexStr=vertexStr+"varying vec2 UVCoord;\n";
	
	vertexStr=vertexStr+"void main(void)\n";
	vertexStr=vertexStr+"{\n";
	vertexStr=vertexStr+"UVCoord=UV;";
	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.boneWeights.length; i=i+4){
        if(!this.boneWeights[i+1]){
            vertexStr=vertexStr+"+bones"+cnt;
        }else{
            vertexStr=vertexStr+"+bones"+cnt+"["+(i%4)+"]";
        }
        if(this.boneWeights[i+1]) vertexStr=vertexStr+"+bones"+cnt+"["+(i%4+1)+"]";
        if(this.boneWeights[i+2]) vertexStr=vertexStr+"+bones"+cnt+"["+(i%4+2)+"]";
        if(this.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.boneWeights.length; i=i+4){
        if(!this.boneWeights[i+1]){
            vertexStr=vertexStr+"pos += (PMatrix * MVMatrix * "+this.boneWeights[i].boneName+"Matrix * vec4(position, 1.0))*(bones"+cnt+"/totalWeight);\n";
            vertexStr=vertexStr+"norm = ("+this.boneWeights[i].boneName+"nMatrix * vec4(normal, 1.0))*(bones"+cnt+"/totalWeight);\n";
            }else{
            vertexStr=vertexStr+"pos += (PMatrix * MVMatrix * "+this.boneWeights[i].boneName+"Matrix * vec4(position, 1.0))*(bones"+cnt+"["+(i%4)+"]/totalWeight);\n";
            vertexStr=vertexStr+"norm += ("+this.boneWeights[i].boneName+"nMatrix * vec4(normal, 1.0))*(bones"+cnt+"["+(i%4)+"]/totalWeight);\n";
        }
        if(this.boneWeights[i+1]) vertexStr=vertexStr+"pos += (PMatrix * MVMatrix * "+this.boneWeights[i+1].boneName+"Matrix * vec4(position, 1.0))*(bones"+cnt+"["+(i%4+1)+"]/totalWeight);\n";
        if(this.boneWeights[i+2]) vertexStr=vertexStr+"pos += (PMatrix * MVMatrix * "+this.boneWeights[i+2].boneName+"Matrix * vec4(position, 1.0))*(bones"+cnt+"["+(i%4+2)+"]/totalWeight);\n";
        if(this.boneWeights[i+3]) vertexStr=vertexStr+"pos += (PMatrix * MVMatrix * "+this.boneWeights[i+3].boneName+"Matrix * vec4(position, 1.0))*(bones"+cnt+"["+(i%4+3)+"]/totalWeight);\n";
        if(this.boneWeights[i+1]) vertexStr=vertexStr+"norm += ("+this.boneWeights[i+1].boneName+"nMatrix * vec4(normal, 1.0))*(bones"+cnt+"["+(i%4+1)+"]/totalWeight);\n";
        if(this.boneWeights[i+2]) vertexStr=vertexStr+"norm += ("+this.boneWeights[i+2].boneName+"nMatrix * vec4(normal, 1.0))*(bones"+cnt+"["+(i%4+2)+"]/totalWeight);\n";
        if(this.boneWeights[i+3]) vertexStr=vertexStr+"norm += ("+this.boneWeights[i+3].boneName+"nMatrix * vec4(normal, 1.0))*(bones"+cnt+"["+(i%4+3)+"]/totalWeight);\n";
        cnt++;
    }
    vertexStr=vertexStr+"}else{\n";
    vertexStr=vertexStr+"pos = PMatrix * MVMatrix * vec4(position, 1.0);\n";
	vertexStr=vertexStr+"norm = uNMatrix * vec4(normal, 1.0);\n";
    vertexStr=vertexStr+"}\n";
	vertexStr=vertexStr+"gl_Position = pos;\n";
    vertexStr=vertexStr+"vec4 transformedNormal = norm;"
	//vertexStr=vertexStr+"col = max(dot(transformedNormal.xyz, uLightingDirection), 0.0);\n";
	vertexStr=vertexStr+"light = uLightingDirection;\n";
	vertexStr=vertexStr+"halflight = uLightingDirection+vec3(pos.x,pos.y,pos.z);\n";
	vertexStr=vertexStr+"n = vec3(norm.r,norm.g,norm.b);\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();
    }

	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))
	{
		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))
	{
		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);	
}
meshObject.prototype.GLSetUniforms=function(gl){
	//generate and set the modelView matrix
    mvMatrix=this.scene.camera.getViewMatrix();
	mvMatrix=mvMatrix.x(transMat(this.position.x,this.position.y,this.position.z));
    mvMatrix=mvMatrix.x(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 lightingDirection = Vector.create([0.25,0.25,-1]);
    var adjustedLD = lightingDirection.toUnitVector().x(-1);
    try{
    gl.uniform3fv(gl.getUniformLocation(this.GLShaderProgram, "uLightingDirection"), adjustedLD.flatten());
    }
    catch(e){
    //is this right? webkit doing something strange!
    gl.uniform3fv(gl.getUniformLocation(this.GLShaderProgram, "uLightingDirection"), adjustedLD.flatten(),3);
    }
    //set bone transforms
    var boneUniform;
    var transforms;
    for(var i=0; i<this.boneWeights.length; i++){
        boneUniform = gl.getUniformLocation(this.GLShaderProgram, this.boneWeights[i].boneName+"Matrix");
        transforms=this.skeleton.getTransforms();
        gl.uniformMatrix4fv(boneUniform, false, new WebGLFloatArray(transforms[this.boneWeights[i].boneName].matrix.flatten()));
        
        boneUniform = gl.getUniformLocation(this.GLShaderProgram, this.boneWeights[i].boneName+"nMatrix");
        transforms=this.skeleton.getTransforms();
        gl.uniformMatrix4fv(boneUniform, false, new WebGLFloatArray(mvMatrix.x(transforms[this.boneWeights[i].boneName].matrix).inverse().transpose().flatten()));
    }
    
    if(this.material) this.material.textureUniforms(gl,this.GLShaderProgram);
}
meshObject.prototype.GLRender=function(gl){
	var attribslot;
	gl.useProgram(this.GLShaderProgram);
	
	//disable all the attribute initially arrays - do I really need this?
	for(var i=0; i<8; i++) gl.disableVertexAttribArray(i);
	
	//loop though the buffers
    for(i=0; i<this.buffers.length;i++){
		attribslot=gl.getAttribLocation(this.GLShaderProgram, 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);
	}
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.GLfaces);

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

/*
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 Creates a new camera object
* @property {coord} position The position of the camera
* @property {coord} rotation The rotation of the camera
*/
function camera(){
		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);
};
camera.prototype.position=null;
camera.prototype.rotation=null;
camera.prototype.pMatrix=null;
/**
* Function generates the projection matrix based on the 
* camera paramaters
* @return Returns the camera projection matrix
* @type Matrix
*/
camera.prototype.getProjectionMatrix=function(){
	return pMatrix;
};
/**
* Function generates the cameras view matrix
* @return Returns the view matrix based on this camera
* @type Matrix
*/
camera.prototype.getViewMatrix=function(){
	var vMatrix=transMat(-this.position.x,-this.position.y,-this.position.z);
    vMatrix=vMatrix.x(rotMat(this.rotation.x,this.rotation.y,this.rotation.z));
    return vMatrix;
};

/**
* @class Creates a new scene
* @property {camera} camera The camera for the scene
* @property {array} object Array of objects in the scene
* @property {array} lights Array of lights in the scene
*/
function scene(){
	this.objects=[];
	this.lights=[];
	this.camera=new camera();
}
scene.prototype.camera=null;
scene.prototype.objects=null;
scene.prototype.lights=null;
scene.prototype.renderer=null;

scene.prototype.addObject=function(object){	
	object.scene=this;
	this.objects.push(object);
	if(this.renderer) object.GLInit(this.renderer.gl);
}

scene.prototype.addLight=function(object){	
}
//initialize all the objects in this scene
scene.prototype.init=function(gl){
    for(var i=0;i<this.objects.length;i++){
        this.objects[i].GLInit(this.renderer.gl);
    }
}
//destroy all the objects in this scene
scene.prototype.destory=function(gl){
}

scene.prototype.render=function(gl){
	for(var i=0; i<this.objects.length;i++){
		this.objects[i].GLRender(this.renderer.gl);
	}
}


var 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);
};
renderer.prototype.gl=null;
renderer.prototype.scene=null;
renderer.prototype.setScene=function(scene){
	scene.renderer=this;
    this.scene=scene;
    scene.init();
};
renderer.prototype.render=function(){
	this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT)
	this.scene.render();
};




function material(){
	this.layers=[];
	this.textures=[];
	this.lights=[];
	this.color={r:1,g:1,b:1,a:1};
	this.specular={r:1,g:1,b:1};
	this.reflect=1;
	this.shine=1;
}
//material constants
//prarmater mapping
material.COLOR=1; //defuse color
material.NOR=2; //normal map
material.ALPHA=4;  //alpha value
material.SPECULAR=8; //specular map
material.SHINE=16; //specular map
material.REFLECT=32; //reflection map
material.MSKR=64; //use red component as mask
material.MSKG=128; //use green component as mask
material.MSKB=256; //use blue component as mask
material.MSKA=512; //use alpha component as mask

//uv layers
material.UV1=0;
material.UV2=1;
	
material.prototype.layers=null;
material.prototype.textures=null;
material.prototype.color=null;
material.prototype.specular=null;
material.prototype.shine=null;
material.prototype.reflect=null;
material.prototype.lights=null;
	
material.prototype.addLayer=function(texture,mapping,uv,scale){
	this.layers.push({texture:texture,mapping:mapping,uv:uv,scale:scale});
	return this.layers.length-1;
}
//generate the fragment shader program for this material
material.prototype.getFragmentShader=function(lights,ambiantColor){
	var shader="";
    
    
    shader=shader+"varying vec3 light;\n";  
    shader=shader+"varying vec3 halflight;\n";  
    shader=shader+"varying vec3 n;\n";  
    shader=shader+"varying vec2 UVCoord;\n";
    
    //texture uniforms
	for(var i=0; i<this.textures.length;i++){
		shader=shader+"uniform sampler2D TEXTURE"+i+";\n";
	}
    
	shader=shader+"void main(void)\n";
	shader=shader+"{\n";
    shader=shader+"vec4 color = texture2D(TEXTURE"+this.layers[0].texture+", vec2(UVCoord["+(this.layers[0].uv*2)+"],1.0-UVCoord["+(this.layers[0].uv*2+1)+"]));\n";
	shader=shader+"vec4 normalmap=vec4(0,0,0,0);\n"
    shader=shader+"normalmap=texture2D(TEXTURE"+this.layers[1].texture+", vec2(UVCoord["+(this.layers[1].uv*2)+"],1.0-UVCoord["+(this.layers[1].uv*2+1)+"]));\n";
	shader=shader+"normalmap=(normalmap-vec4(0.5,0.5,0.5,0.5))*2.0;\n";
    shader=shader+"vec3 normal = normalize(n);\n";
    shader=shader+"float lightvalue=0.0;\n"; 
    shader=shader+"lightvalue += max(dot(normal,normalize(light)),0.0);\n";
    shader=shader+"lightvalue += 0.5  * pow(max(dot(normalmap.rgb,normalize(halflight)),0.0), 10.0);\n"
	shader=shader+"gl_FragColor = vec4(color.r*lightvalue,color.g*lightvalue,color.b*lightvalue,1.0);\n";
	shader=shader+"}\n";
    
	return shader;
}
//set the texture uniforms for this material
material.prototype.textureUniforms=function(gl,shaderProgram){
	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 hte 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
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);
}

