/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</copyright> */



///////////////////////////////////////////////////////////////////////
//Loading webGL/canvas data
function initWebGl (rootElement, directory) {
	var cvsDataMngr, ninjaWebGlData = JSON.parse((document.querySelectorAll(['script[data-ninja-webgl]'])[0].innerHTML.replace('(', '')).replace(')', ''));
	if (ninjaWebGlData && ninjaWebGlData.data) {
		for (var n=0; ninjaWebGlData.data[n]; n++) {
			ninjaWebGlData.data[n] = unescape(ninjaWebGlData.data[n]);
		}
	}
	//Creating data manager
	cvsDataMngr = new CanvasDataManager();
	//Loading data to canvas(es)
	cvsDataMngr.loadGLData(rootElement, ninjaWebGlData.data, directory);
}

///////////////////////////////////////////////////////////////////////
// Class ShapeRuntime
//      Manages runtime shape display
///////////////////////////////////////////////////////////////////////
function CanvasDataManager()
{
	this.loadGLData = function(root,  valueArray,  assetPath )
	{
		if (assetPath)
			this._assetPath = assetPath.slice();

		var value = valueArray;
		var nWorlds = value.length;
		for (var i=0;  i<nWorlds;  i++)
		{
			var importStr = value[i];

			// there should be some version information in
			// the form of 'v0.0;'  Pull that off.  (the trailing ';' should
			// be in the first 24 characters).
			var index = importStr.indexOf( ';' );
			if ((importStr[0] === 'v') && (index < 24))
			{
				// JSON format.  pull off the version info
				importStr = importStr.substr( index+1 );

				var jObj = JSON.parse( importStr );
				var id = jObj.id;
				if (id)
				{
					var canvas = this.findCanvasWithID( id, root );
					if (canvas)
					{
						new GLRuntime( canvas, jObj,  assetPath );
					}
				}
			}
		}
	}

	this.collectGLData = function( elt,  dataArray )
	{
		if (elt.elementModel && elt.elementModel.shapeModel && elt.elementModel.shapeModel.GLWorld)
		{
			var data = elt.elementModel.shapeModel.GLWorld.export( true );
			dataArray.push( data );
		}

		if (elt.children)
		{
			var nKids = elt.children.length;
			for (var i=0;  i<nKids;  i++)
			{
				var child = elt.children[i];
				this.collectGLData( child, dataArray );
			}
		}
	}

	this.findCanvasWithID = function( id,  elt )
	{
		var cid = elt.getAttribute( "data-RDGE-id" );
		if (cid == id)  return elt;

		if (elt.children)
		{
			var nKids = elt.children.length;
			for (var i=0;  i<nKids;  i++)
			{
				var child = elt.children[i];
				var foundElt = this.findCanvasWithID( id, child );
				if (foundElt)  return foundElt;
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////
// Class GLRuntime
//      Manages runtime fora WebGL canvas
///////////////////////////////////////////////////////////////////////
function GLRuntime( canvas, jObj,  assetPath )
{
    ///////////////////////////////////////////////////////////////////////
    // Instance variables
    ///////////////////////////////////////////////////////////////////////
	this._canvas		= canvas;
	this._context		= null;
	//this._importStr		= importStr;
	this._jObj			= jObj;

	this.renderer		= null;
	this.myScene		= null;
	this.light			= null;
	this.light2			= null;
	this._rootNode		= null;

	this._firstRender	= true;
	this._initialized	= false;

	this._useWebGL		= false;

	// view parameters
	this._fov = 45.0;
	this._zNear = 0.1;
	this._zFar = 100.0;
	this._viewDist = 5.0;

	this.elapsed = 0;

	this._aspect = canvas.width/canvas.height;

	this._geomRoot = null;

	// all "live" materials
	this._materials = [];

		// provide the mapping for the asset directory
		if (assetPath)
		{
			this._assetPath = assetPath.slice();
			if (this._assetPath[this._assetPath.length-1] != '/')
				this._assetPath += '/';
		}

    ///////////////////////////////////////////////////////////////////////
	// accessors
    ///////////////////////////////////////////////////////////////////////
	this.getZNear			= function()		{  return this._zNear;			}
	this.getZFar			= function()		{  return this._zFar;			}
	this.getFOV				= function()		{  return this._fov;			}
	this.getAspect			= function()		{  return this._aspect;			}
	this.getViewDistance	= function()		{  return this._viewDist;		}

	this.get2DContext		= function()		{  return this._context;		}

	this.getViewportWidth	= function()		{  return this._canvas.width;	}
	this.getViewportHeight	= function()		{  return this._canvas.height;	}

    ///////////////////////////////////////////////////////////////////////
	// accessors
    ///////////////////////////////////////////////////////////////////////
	this.loadScene = function()
	{
		var jObj = this._jObj;
		if (!jObj.children || (jObj.children.length != 1))
			throw new Error( "ill-formed JSON for runtime load: " + jObj );
		var root = jObj.children[0];

		// parse the data
		if (jObj.scenedata)
		{
			this._useWebGL = true;

			var rdgeStr = jObj.scenedata;
			this.myScene.importJSON( rdgeStr );
			this.importObjects( root );
			this.linkMaterials( this._geomRoot );
			this.initMaterials();
			this.linkLights();
		}
		else
		{
			this._context = this._canvas.getContext( "2d" );
			this.importObjects( root );
			this.render();
		}
	}

	this.init = function()
    { 
		var ctx1 = g_Engine.ctxMan.handleToObject(this._canvas.rdgeCtxHandle),
			ctx2 = g_Engine.getContext();
		if (ctx1 != ctx2)  console.log( "***** different contexts *****" );
		this.renderer = ctx1.renderer;
      
		// create a camera, set its perspective, and then point it at the origin
		var cam = new camera();
		this._camera = cam;
		cam.setPerspective(this.getFOV(), this.getAspect(), this.getZNear(), this.getZFar());
		cam.setLookAt([0, 0, this.getViewDistance()], [0, 0, 0], vec3.up());
        
		// make this camera the active camera
		this.renderer.cameraManager().setActiveCamera(cam);

		// change clear color
		this.renderer.setClearColor([1.0, 1.0, 1.0, 0.0]);
        
		// create an empty scene graph
		this.myScene = new SceneGraph();

		// load the scene graph data
		this.loadScene();
        
		// Add the scene to the engine - necessary if you want the engine to draw for you
		var name = "myScene" + this._canvas.getAttribute( "data-RDGE-id" ); 
		g_Engine.AddScene(name, this.myScene);

		this._initialized = true;
	}
    
	// main code for handling user interaction and updating the scene   
	this.update = function(dt)
    {
		if (this._initialized)
		{
			if (!dt)  dt = 0.2;
        
			dt = 0.01;	// use our own internal throttle
			this.elapsed += dt;
        
			// changed the global position uniform of light 0, another way to change behavior of a light
			rdgeGlobalParameters.u_light0Pos.set( [5*Math.cos(this.elapsed), 5*Math.sin(this.elapsed), 20]);
        
			// orbit the light nodes around the boxes
			if (this.light )  this.light.setPosition([1.2*Math.cos(this.elapsed*2.0), 1.2*Math.sin(this.elapsed*2.0), 1.2*Math.cos(this.elapsed*2.0)]);
			if (this.light2)  this.light2.setPosition([-1.2*Math.cos(this.elapsed*2.0), 1.2*Math.sin(this.elapsed*2.0), -1.2*Math.cos(this.elapsed)]);

			this.updateMaterials();

			// now update all the nodes in the scene
			this.myScene.update(dt);
		}
    }

	this.updateMaterials = function()
	{
		var nMats = this._materials.length;
		for (var i=0;  i<nMats;  i++)
		{
			var mat = this._materials[i];
			mat.update();
		}
	}

    // defining the draw function to control how the scene is rendered      
	this.draw = function()
    {
		if (this._initialized)
		{
			g_Engine.setContext( this._canvas.rdgeid );

			var ctx = g_Engine.getContext();
			var renderer = ctx.renderer;
			if (renderer.unloadedTextureCount <= 0)
			{
				renderer.disableCulling();
				renderer._clear();
				this.myScene.render();

				if (this._firstRender)
				{
					if (this._canvas.task)
					{
						this._firstRender = false;
						//this._canvas.task.stop();
					}
				}
			}
		}
    }

	this.importObjects = function( jObj,  parent )
	{
		// read the next object
		var gObj = this.importObject( jObj, parent );

		// load the children
		if (jObj.children)
		{
			var nKids = jObj.children.length;
			for (var i=0;  i<nKids;  i++)
			{
				var child = jObj.children[i];
				this.importObjects( child, gObj );
			}
		}
	}

	this.importObject = function( jObj,  parent )
	{
		var type = jObj.type
		var obj;
		switch (type)
		{
			case 1:
				obj = new RuntimeRectangle();
				obj.import( jObj, parent );
				break;

			case 2:		// circle
				obj = new RuntimeOval();
				obj.import( jObj, parent );
				break;

			case 3:		// line
				obj = new RuntimeLine();
				obj.import( jObj, parent );
				break;

			default:
				throw new Error( "Attempting to load unrecognized object type: " + type );
				break;
		}

		if (obj)
			this.addObject( obj, parent );

		return obj;
	}

	this.addObject = function( obj, parent )
	{
		if (!obj)  return;
		obj.setWorld( this );

		if (parent == null)
			this._geomRoot = obj;
		else
			parent.addChild( obj );
	}

	this.linkLights = function()
	{
		var matNode = this.findMaterialNode( "lights", this.myScene.scene );
		if (matNode)
		{
			this.light = matNode.lightChannel[1];
			this.light2 = matNode.lightChannel[2];
		}
	}

	this.linkMaterials = function( obj )
	{
		if (!obj)  return;

		// get the array of materials from the object
		var matArray = obj._materials;
		var nMats = matArray.length;
		for (var i=0;  i<nMats;  i++)
		{
			var mat = matArray[i];
			var nodeName = mat._materialNodeName;
			var matNode = this.findMaterialNode( nodeName, this.myScene.scene );
			if (matNode)
			{
				mat._materialNode = matNode;
				mat._shader = matNode.shaderProgram;
				this._materials.push( mat );
			}
		}
	}

	this.initMaterials = function()
	{
		var nMats = this._materials.length;
		for (var i=0;  i<nMats;  i++)
		{
			var mat = this._materials[i];
			mat.init( this );
		}
	}

	this.remapAssetFolder = function( url )
	{
//		var searchStr = "assets/";
//		var index = url.indexOf( searchStr );
//		var rtnPath = url;
//		if (index >= 0)
//		{
//			rtnPath = url.substr( index + searchStr.length );
//			rtnPath = this._assetPath + rtnPath;
//		}
//		return rtnPath;
		
		return url;
	}

	this.findMaterialNode = function( nodeName,  node )
	{
		if (node.transformNode)
			node = node.transformNode;

		if (node.materialNode)
		{
			if (nodeName === node.materialNode.name)  return node.materialNode;
		}

		if (node.children)
		{
			var nKids = node.children.length;
			for (var i=0;  i<nKids;  i++)
			{
				var child = node.children[i];
				var rtnNode = this.findMaterialNode( nodeName, child );
				if (rtnNode)  return rtnNode;
			}
		}
	}

	this.render = function( obj )
	{
		if (!obj)  obj = this._geomRoot;
		obj.render();

		if (obj.children)
		{
			var nKids = obj.children.length;
			for (var i=0;  i<nKids;  i++)
			{
				var child = obj.children[i];
				if (child)
					this.render( child );
			}
		}
	}

	// start RDGE or load Canvas 2D objects
	if (jObj.scenedata)  this._useWebGL = true;
	if (this._useWebGL)
	{
		var id = canvas.getAttribute( "data-RDGE-id" ); 
		canvas.rdgeid = id;
		g_Engine.registerCanvas(canvas, this);
		RDGEStart( canvas );
	}
	else
	{
		this.loadScene();
	}
}

///////////////////////////////////////////////////////////////////////
// Class RuntimeGeomObj
//      Super class for all geometry classes
///////////////////////////////////////////////////////////////////////
function RuntimeGeomObj()
{
    ///////////////////////////////////////////////////////////////////////
    // Constants
    ///////////////////////////////////////////////////////////////////////
	this.GEOM_TYPE_RECTANGLE		=  1;
	this.GEOM_TYPE_CIRCLE			=  2;
	this.GEOM_TYPE_LINE             =  3;
	this.GEOM_TYPE_PATH			    =  4;
	this.GEOM_TYPE_CUBIC_BEZIER     =  5;
	this.GEOM_TYPE_UNDEFINED		= -1;
	
    ///////////////////////////////////////////////////////////////////////
    // Instance variables
    ///////////////////////////////////////////////////////////////////////
    this._children;

    // stroke and fill colors
    this._strokeColor	= [0,0,0,0];
    this._fillColor		= [0,0,0,0];

	// array of materials
	this._materials = [];

    ///////////////////////////////////////////////////////////////////////
    // Property accessors
    ///////////////////////////////////////////////////////////////////////

	this.geomType	= function()		{  return this.GEOM_TYPE_UNDEFINED;	}

	this.setWorld	= function(w)		{  this._world = w;					}
	this.getWorld	= function()		{  return this._world;				}

    ///////////////////////////////////////////////////////////////////////
    // Methods
    ///////////////////////////////////////////////////////////////////////    
	this.addChild = function( child )
	{
		if (!this._children)  this._children = [];
		this._children.push( child );
	}

    this.import = function()
    {
	}

	this.importMaterials = function(jObj)
	{
		if (!jObj || !jObj.materials)  return;

		var nMaterials = jObj.nMaterials;
		var matArray = jObj.materials;
		for (var i=0;  i<nMaterials;  i++)
		{
			var mat;
			var matObj = matArray[i].material;
			var matNodeName = matArray[i].materialNodeName;
			var shaderName = matObj.material;
			switch (shaderName)
			{
				case "flat":			mat = new RuntimeFlatMaterial();				break;
				case "radialGradient":  mat = new RuntimeRadialGradientMaterial();		break;
				case "linearGradient":  mat = new RuntimeLinearGradientMaterial();		break;
				case "bumpMetal":		mat = new RuntimeBumpMetalMaterial();			break;
				case "uber":			mat = new RuntimeUberMaterial();				break;
				case "plasma":			mat = new RuntimePlasmaMaterial();				break;

				case "deform":
				case "water":
				case "paris":
				case "raiders":
				case "tunnel":
				case "reliefTunnel":
				case "squareTunnel":
				case "twist":
				case "fly":
				case "julia":
				case "mandel":
				case "star":
				case "zinvert":
				case "keleidoscope":
				case "radialBlur":
				case "pulse":			mat = new RuntimePulseMaterial();				break;

				default:
					console.log( "material type: " + materialType + " is not supported" );
					break;
			}

			if (mat)
			{
				mat.import( matObj );
				mat._materialNodeName = matNodeName;
				this._materials.push( mat );
			}
		}
	}

	////////////////////////////////////////////////////////////////////
	// vector function

	this.vecAdd = function( dimen,  a, b )
	{
        var rtnVec;
        if ((a.length < dimen) || (b.length < dimen))
        {
            throw new Error( "dimension error in vecAdd" );
        }

        rtnVec = [];
        for (var i=0;  i<dimen;  i++)
            rtnVec[i] = a[i] + b[i];

        return rtnVec;
    }


	this.vecSubtract =  function( dimen, a, b )
	{
        var rtnVec;
        if ((a.length < dimen) || (b.length < dimen))
        {
            throw new Error( "dimension error in vecSubtract" );
        }

        rtnVec = [];
        for (var i=0;  i<dimen;  i++)
            rtnVec[i] = a[i] - b[i];

        return rtnVec;
    }

    this.vecDot = function( dimen,  v0, v1 )
	{
        if ((v0.length < dimen) || (v1.length < dimen))
        {
            throw new Error( "dimension error in vecDot" );
        }

        var sum = 0.0;
        for (var i=0;  i<dimen;  i++)
            sum += v0[i] * v1[i];

        return sum;
    }

	this.vecMag = function( dimen, vec )
	{
        var sum = 0.0;
        for (var i=0;  i<dimen;  i++)
            sum += vec[i]*vec[i];
        return Math.sqrt( sum );
    }

	this.vecScale = function(dimen, vec, scale)
	{
        for (var i=0;  i<dimen;  i++)
            vec[i] *= scale;

        return vec;
    }

    this.vecNormalize = function(dimen, vec, len)
	{
        var rtnVec;
		if (!len)  len = 1.0;

        var sum = 0.0;
        for (var i=0;  i<dimen;  i++)
            sum += vec[i]*vec[i];
        sum = Math.sqrt( sum );

        if (Math.abs(sum) >= 0.001)
        {
            var scale = len/sum;
            rtnVec = [];
            for (var i=0;  i<dimen;  i++)
                rtnVec[i] = vec[i]*scale;
        }

        return rtnVec;
    },

	this.transformPoint = function( srcPt, mat )
    {
        var pt = srcPt.slice(0);
        var	x = this.vecDot(3,  pt, [mat[0], mat[4], mat[ 8]] ) + mat[12],
            y = this.vecDot(3,  pt, [mat[1], mat[5], mat[ 9]] ) + mat[13],
            z = this.vecDot(3,  pt, [mat[2], mat[6], mat[10]] ) + mat[14];

        return [x, y, z];
    }
	
	this.MatrixIdentity = function(dimen)
	{
		var mat = [];

		for (var i = 0; i<dimen*dimen; i++)  {
			mat.push(0);
		}

		var index = 0;
		for (var j = 0; j<dimen; j++) {
			mat[index] = 1.0;
			index += dimen + 1;
		}
	
		return mat;	
	};

	
	this.MatrixRotationZ = function( angle )
	{
		var mat = this.MatrixIdentity(4);
		//glmat4.rotateZ(mat, angle);
		var sn = Math.sin(angle),
			cs = Math.cos(angle);
		mat[0] = cs;	mat[4] = -sn;
		mat[1] = sn;	mat[5] =  cs;

		return mat;
	};

}

///////////////////////////////////////////////////////////////////////
// Class RuntimeRectangle
///////////////////////////////////////////////////////////////////////
function RuntimeRectangle()
{
	// inherit the members of RuntimeGeomObj
	this.inheritedFrom = RuntimeGeomObj;
	this.inheritedFrom();

	this.import = function( jObj )
	{
		this._xOffset			= jObj.xoff;
		this._yOffset			= jObj.yoff;
		this._width				= jObj.width;
		this._height			= jObj.height;
		this._strokeWidth		= jObj.strokeWidth;
		this._strokeColor		= jObj.strokeColor;
		this._fillColor			= jObj.fillColor;
		this._tlRadius			= jObj.tlRadius;
		this._trRadius			= jObj.trRadius;
		this._blRadius			= jObj.blRadius;
		this._brRadius			= jObj.brRadius;
		this._innerRadius		= jObj.innerRadius;
		this._strokeStyle		= jObj.strokeStyle;
		var strokeMaterialName	= jObj.strokeMat;
		var fillMaterialName	= jObj.fillMat;
		this.importMaterials( jObj.materials );
	}

	this.renderPath = function( inset, ctx )
	{
		// various declarations
		var pt,  rad,  ctr,  startPt, bPts;
		var width  = Math.round(this._width),
			height = Math.round(this._height);

		pt = [inset, inset];	// top left corner

		var tlRad = this._tlRadius; //top-left radius
		var trRad = this._trRadius;
		var blRad = this._blRadius;
		var brRad = this._brRadius;

		if ((tlRad <= 0) && (blRad <= 0) && (brRad <= 0) && (trRad <= 0))
		{
			ctx.rect(pt[0], pt[1], width - 2*inset, height - 2*inset);
		}
		else
		{
			// get the top left point
			rad = tlRad - inset;
			if (rad < 0)  rad = 0;
			pt[1] += rad;
			if (Math.abs(rad) < 0.001)  pt[1] = inset;
			ctx.moveTo( pt[0],  pt[1] );

			// get the bottom left point
			pt = [inset, height - inset];
			rad = blRad - inset;
			if (rad < 0)  rad = 0;
			pt[1] -= rad;
			ctx.lineTo( pt[0],  pt[1] );

			// get the bottom left curve
			if (rad > 0.001)
				ctx.quadraticCurveTo( inset, height-inset,  inset+rad, height-inset );

			// do the bottom of the rectangle
			pt = [width - inset,  height - inset];
			rad = brRad - inset;
			if (rad < 0)  rad = 0;
			pt[0] -= rad;
			ctx.lineTo( pt[0], pt[1] );

			// get the bottom right arc
			if (rad > 0.001)
				ctx.quadraticCurveTo( width-inset, height-inset,  width-inset, height-inset-rad );

			// get the right of the rectangle
			pt = [width - inset,  inset];
			rad = trRad - inset;
			if (rad < 0)  rad = 0;
			pt[1] += rad;
			ctx.lineTo( pt[0], pt[1] );

			// do the top right corner
			if (rad > 0.001)
				ctx.quadraticCurveTo( width-inset, inset,  width-inset-rad, inset );

			// do the top of the rectangle
			pt = [inset, inset]
			rad = tlRad - inset;
			if (rad < 0)  rad = 0;
			pt[0] += rad;
			ctx.lineTo( pt[0], pt[1] );

			// do the top left corner
			if (rad > 0.001)
				ctx.quadraticCurveTo( inset, inset, inset, inset+rad );
			else
				ctx.lineTo( inset, 2*inset );
		}
	}

    this.render = function()
    {
        // get the world
        var world = this.getWorld();
        if (!world)  throw( "null world in rectangle render" );

         // get the context
		var ctx = world.get2DContext();
		if (!ctx)  return;

		// get some dimensions
		var lw = this._strokeWidth;
		var	w = world.getViewportWidth(),
			h = world.getViewportHeight();
		
		// render the fill
		ctx.beginPath();
		if (this._fillColor)
		{
			var c = "rgba(" + 255*this._fillColor[0] + "," + 255*this._fillColor[1] + "," + 255*this._fillColor[2] + "," + this._fillColor[3] + ")";  
			ctx.fillStyle = c;

			ctx.lineWidth	= lw;
			var inset = Math.ceil( lw ) + 0.5;
			this.renderPath( inset, ctx );
			ctx.fill();
			ctx.closePath();
		}

		// render the stroke
		ctx.beginPath();
		if (this._strokeColor)
		{
			var c = "rgba(" + 255*this._strokeColor[0] + "," + 255*this._strokeColor[1] + "," + 255*this._strokeColor[2] + "," + this._strokeColor[3] + ")";  
			ctx.strokeStyle = c;

			ctx.lineWidth	= lw;
			var inset = Math.ceil( 0.5*lw ) + 0.5;
			this.renderPath( inset, ctx );
			ctx.stroke();
			ctx.closePath();
		}
    }
}

///////////////////////////////////////////////////////////////////////
// Class RuntimeLine
///////////////////////////////////////////////////////////////////////
function RuntimeLine()
{
	this.inheritedFrom = RuntimeGeomObj;
	this.inheritedFrom();

	this.import = function( jObj )
	{
		this._xOffset			= jObj.xoff;
		this._yOffset			= jObj.yoff;
		this._width				= jObj.width;
		this._height			= jObj.height;
		this._xAdj			    = jObj.xAdj;
		this._yAdj			    = jObj.yAdj;
		this._strokeWidth		= jObj.strokeWidth;
		this._slope 		    = jObj.slope;
		this._strokeStyle		= jObj.strokeStyle;
		this._strokeColor		= jObj.strokeColor;
		var strokeMaterialName	= jObj.strokeMat;
        this.importMaterials( jObj.materials );
    }

	this.render = function()
	{
		// get the world
		var world = this.getWorld();
		if (!world)  throw( "null world in buildBuffers" );

		 // get the context
		var ctx = world.get2DContext();
		if (!ctx)  return;
 	
		// set up the stroke style
		var lineWidth = this._strokeWidth,
            w = this._width,
            h = this._height;

        var c,
            gradient,
            colors,
            len,
            n,
            position,
            cs;

		ctx.beginPath();
		ctx.lineWidth	= lineWidth;
		if (this._strokeColor) {
            if(this._strokeColor.gradientMode) {
                if(this._strokeColor.gradientMode === "radial") {
                    gradient = ctx.createRadialGradient(w/2, h/2, 0, w/2, h/2, Math.max(w/2, h/2));
                } else {
                    gradient = ctx.createLinearGradient(0, h/2, w, h/2);
                }
                colors = this._strokeColor.color;

                len = colors.length;

                for(n=0; n<len; n++) {
                    position = colors[n].position/100;
                    cs = colors[n].value;
                    gradient.addColorStop(position, "rgba(" + cs.r + "," + cs.g + "," + cs.b + "," + cs.a + ")");
                }

                ctx.strokeStyle = gradient;

            } else {
                c = "rgba(" + 255*this._strokeColor[0] + "," + 255*this._strokeColor[1] + "," + 255*this._strokeColor[2] + "," + this._strokeColor[3] + ")";
                ctx.strokeStyle = c;
            }

			// get the points
			var p0,  p1;
			if(this._slope === "vertical") {
				p0 = [0.5*w, 0];
				p1 = [0.5*w, h];
			} else if(this._slope === "horizontal") {
				p0 = [0, 0.5*h];
				p1 = [w, 0.5*h];
			} else if(this._slope > 0) {
				p0 = [this._xAdj, this._yAdj];
				p1 = [w - this._xAdj,  h - this._yAdj];
			} else {
				p0 = [this._xAdj, h - this._yAdj];
				p1 = [w - this._xAdj,  this._yAdj];
			}
			
			// draw the line
			ctx.moveTo( p0[0],  p0[1] );
			ctx.lineTo( p1[0],  p1[1] );
			ctx.stroke();
		}
    }
}

///////////////////////////////////////////////////////////////////////
// Class RuntimeOval
///////////////////////////////////////////////////////////////////////
function RuntimeOval()
{
	// inherit the members of RuntimeGeomObj
	this.inheritedFrom = RuntimeGeomObj;
	this.inheritedFrom();

	this.import = function( jObj )
	{
		this._xOffset			= jObj.xoff;
		this._yOffset			= jObj.yoff;
		this._width				= jObj.width;
		this._height			= jObj.height;
		this._strokeWidth		= jObj.strokeWidth;
		this._strokeColor		= jObj.strokeColor;
		this._fillColor			= jObj.fillColor;
		this._innerRadius		= jObj.innerRadius;
		this._strokeStyle		= jObj.strokeStyle;
		var strokeMaterialName	= jObj.strokeMat;
		var fillMaterialName	= jObj.fillMat;
		this.importMaterials( jObj.materials );
	}

	this.render = function()
	{
		// get the world
		var world = this.getWorld();
		if (!world)  throw( "null world in buildBuffers" );

		 // get the context
		var ctx = world.get2DContext();
		if (!ctx)  return;

		// declare some variables
		var p0, p1;
		var x0, y1,   x1, y1;

		// create the matrix
		var lineWidth = this._strokeWidth;
		var innerRad  = this._innerRadius;
		var xScale = 0.5*this._width - lineWidth,
			yScale = 0.5*this._height - lineWidth;

		// translate
		var xCtr = 0.5*world.getViewportWidth() + this._xOffset,
			yCtr = 0.5*world.getViewportHeight() + this._yOffset;
		var mat = this.MatrixIdentity( 4 );
		mat[0] = xScale;					mat[12] = xCtr;
						mat[5] = yScale;	mat[13] = yCtr;
		/*
		var mat = [
							[ xScale,     0.0,  0.0,  xCtr],
							[    0.0,  yScale,  0.0,  yCtr],
							[    0.0,     0.0,  1.0,   0.0],
							[    0.0,     0.0,  0.0,   1.0]
						];
		*/

		// get a bezier representation of the circle
		var bezPts = this.circularArcToBezier( [0,0,0],  [1,0,0], 2.0*Math.PI );
		if (bezPts)
		{
			var n = bezPts.length;

			// set up the fill style
			ctx.beginPath();
			ctx.lineWidth = 0;
			if (this._fillColor)
			{
				var c = "rgba(" + 255*this._fillColor[0] + "," + 255*this._fillColor[1] + "," + 255*this._fillColor[2] + "," + this._fillColor[3] + ")";  
				ctx.fillStyle = c;

				// draw the fill
				ctx.beginPath();
				var p = this.transformPoint( bezPts[0],   mat );
				ctx.moveTo( p[0],  p[1] );
				var index = 1;
				while (index < n)
				{
					p0 = this.transformPoint( bezPts[index],  mat );
					p1 = this.transformPoint( bezPts[index+1],  mat );

					x0 = p0[0];  y0 = p0[1];
					x1 = p1[0];  y1 = p1[1];
					ctx.quadraticCurveTo( x0,  y0,  x1, y1 );
					index += 2;
				}

				if ( innerRad > 0.001)
				{
					xScale = 0.5*innerRad*this._width;
					yScale = 0.5*innerRad*this._height;
					mat[0] = xScale;
					mat[5] = yScale;

					// get the bezier points
					var bezPts = this.circularArcToBezier( Vector.create([0,0,0]), Vector.create([1,0,0]), -2.0*Math.PI );
					if (bezPts)
					{
						var n = bezPts.length;
						p = this.transformPoint( bezPts[0],   mat );
						ctx.moveTo( p[0],  p[1] );
						index = 1;
						while (index < n)
						{
							p0 = this.transformPoint( bezPts[index],    mat );
							p1 = this.transformPoint( bezPts[index+1],  mat );

							var x0 = p0[0],  y0 = p0[1],
								x1 = p1[0],  y1 = p1[1];
							ctx.quadraticCurveTo( x0,  y0,  x1, y1 );
							index += 2;
						}
					}
				}

				// fill the path
				ctx.fill();
			}

			// calculate the stroke matrix
			xScale = 0.5*this._width  - 0.5*lineWidth;
			yScale = 0.5*this._height - 0.5*lineWidth;
			mat[0] = xScale;
			mat[5] = yScale;

			// set up the stroke style
			ctx.beginPath();
			ctx.lineWidth	= lineWidth;
			if (this._strokeColor)
			{
				var c = "rgba(" + 255*this._strokeColor[0] + "," + 255*this._strokeColor[1] + "," + 255*this._strokeColor[2] + "," + this._strokeColor[3] + ")";  
				ctx.strokeStyle = c;
			
				// draw the stroke
				p = this.transformPoint( bezPts[0],   mat );
				ctx.moveTo( p[0],  p[1] );
				index = 1;
				while (index < n)
				{
					var p0 = this.transformPoint( bezPts[index],  mat );
					var p1 = this.transformPoint( bezPts[index+1],  mat );

					var x0 = p0[0],  y0 = p0[1],
						x1 = p1[0],  y1 = p1[1];
					ctx.quadraticCurveTo( x0,  y0,  x1, y1 );
					index += 2;
				}

				if (innerRad > 0.01)
				{
					// calculate the stroke matrix
					xScale = 0.5*innerRad*this._width  - 0.5*lineWidth;
					yScale = 0.5*innerRad*this._height - 0.5*lineWidth;
					mat[0] = xScale;
					mat[5] = yScale;
			
					// draw the stroke
					p = this.transformPoint( bezPts[0],   mat );
					ctx.moveTo( p[0],  p[1] );
					index = 1;
					while (index < n)
					{
						var p0 = this.transformPoint( bezPts[index],  mat );
						var p1 = this.transformPoint( bezPts[index+1],  mat );

						var x0 = p0[0],  y0 = p0[1],
							x1 = p1[0],  y1 = p1[1];
						ctx.quadraticCurveTo( x0,  y0,  x1, y1 );
						index += 2;
					}
				}

				// render the stroke
				ctx.stroke();
			}
		}
    }

    ///////////////////////////////////////////////////////////////////////
	// this function returns the quadratic Bezier approximation to the specified
	// circular arc.  The input can be 2D or 3D, determined by the minimum dimension
	// of the center and start point.
	// includedAngle is in radians, can be positiveor negative
	this.circularArcToBezier= function( ctr_, startPt_, includedAngle )
	{
        var dimen = 3;
        var ctr = ctr_.slice();
        var startPt = startPt_.slice();

        // make sure the start point is good
        var pt = this.vecSubtract(dimen, startPt, ctr);
        var rad = this.vecMag(dimen, pt);

        if ((dimen != 3) || (rad <= 0) || (includedAngle === 0))
        {
            if (dimen != 3)  console.log( "circularArcToBezier works for 3 dimensional points only.  Was " + dimen );
            return [ startPt.slice(0), startPt.slice(0), startPt.slice(0) ];
        }

        // determine the number of segments.  45 degree span maximum.
        var nSegs = Math.ceil( Math.abs(includedAngle)/(0.25*Math.PI) );
        if (nSegs <= 0)  return [ startPt.slice(0), startPt.slice(0), startPt.slice(0) ];
        var dAngle = includedAngle/nSegs;

        // determine the length of the center control point from the circle center
        var cs = Math.cos( 0.5*Math.abs(dAngle) ),  sn = Math.sin( 0.5*Math.abs(dAngle) );
        var  c = rad*sn;
        var  h = c*sn/cs;
        var  d = rad*cs + h;

        var rtnPts = [ this.vecAdd(dimen, pt, ctr) ];
        var rotMat = this.MatrixRotationZ( dAngle );
        for ( var i=0;  i<nSegs;  i++)
        {
            // get the next end point
            var pt2 = this.transformPoint( pt, rotMat );

            // get the next center control point
            var midPt = this.vecAdd(3, pt, pt2);
            this.vecScale(dimen, midPt, 0.5);
            midPt = this.vecNormalize( dimen, midPt, d );

            // save the next segment
            rtnPts.push( this.vecAdd(dimen, midPt, ctr) );
            rtnPts.push( this.vecAdd(dimen,   pt2, ctr) );

            // advance for the next segment
            pt = pt2;
        }
        return rtnPts;
	}
}

///////////////////////////////////////////////////////////////////////
// Class RuntimeMaterial
//      Runtime representation of a material.
///////////////////////////////////////////////////////////////////////
function RuntimeMaterial( world )
{
    ///////////////////////////////////////////////////////////////////////
    // Instance variables
    ///////////////////////////////////////////////////////////////////////
	this._name = "GLMaterial";
	this._shaderName = "undefined";

	// variables for animation speed
	this._time = 0.0;
	this._dTime = 0.01;

	// RDGE variables
	this._shader;
	this._materialNode;

    ///////////////////////////////////////////////////////////////////////
    // Property Accessors
    ///////////////////////////////////////////////////////////////////////

	// a material can be animated or not. default is not.  
	// Any material needing continuous rendering should override this method
	this.isAnimated			= function()	{  return false;	}

    ///////////////////////////////////////////////////////////////////////
    // Methods
    ///////////////////////////////////////////////////////////////////////
	this.init = function( world )
	{
	}

	this.update = function( time )
	{
	}

	this.import = function( jObj )
	{
	}
}

function RuntimeFlatMaterial()
{
	// inherit the members of RuntimeMaterial
	this.inheritedFrom = RuntimeMaterial;
	this.inheritedFrom();

	this._name = "FlatMaterial";
	this._shaderName = "flat";

	// assign a default color
	this._color = [1,0,0,1];

    this.import = function( jObj )
    {
		this._color = jObj.color;
    };

	this.init = function( world )
	{
		if (this._shader)
		{
			 this._shader.colorMe["color"].set( this._color );
		}
	}
}

function RuntimePulseMaterial()
{
	// inherit the members of RuntimeMaterial
	this.inheritedFrom = RuntimeMaterial;
	this.inheritedFrom();

	this._name = "PulseMaterial";
	this._shaderName = "pulse";

	this._texMap = 'assets/images/cubelight.png';

	this.isAnimated			= function()	{  return true;	}


	this.import = function( jObj )
	{
		this._texMap = jObj.texture;
        if (jObj.dTime)  this._dTime = jObj.dTime;
	}

	this.init = function( world )
	{
		var material = this._materialNode;
		if (material)
		{
			var technique = material.shaderProgram.default;
			var renderer = g_Engine.getContext().renderer;
			if (renderer && technique)
			{
				if (this._shader && this._shader.default)
				{
					var res = [ renderer.vpWidth,  renderer.vpHeight ];
					technique.u_resolution.set( res );

					var wrap = 'REPEAT',  mips = true;
					this._texMap = world.remapAssetFolder( this._texMap );
					var tex = renderer.getTextureByName(this._texMap, wrap, mips );
					if (tex)
						technique.u_tex0.set( tex );

					this._shader.default.u_time.set( [this._time] );
				}
			}
		}
	}

	// several materials inherit from pulse.
	// they may share this update method
	this.update = function( time )
	{
		var material = this._materialNode;
		if (material)
		{
			var technique = material.shaderProgram.default;
			var renderer = g_Engine.getContext().renderer;
			if (renderer && technique)
			{
				if (this._shader && this._shader.default)
					this._shader.default.u_time.set( [this._time] );
				this._time += this._dTime;
				if (this._time > 200.0)  this._time = 0.0;
			}
		}
	}
}

function RuntimeRadialGradientMaterial()
{
	// inherit the members of RuntimeMaterial
	this.inheritedFrom = RuntimeMaterial;
	this.inheritedFrom();

	this._name = "RadialGradientMaterial";
	this._shaderName = "radialGradient";

	// setup default values
	this._color1 = [1,0,0,1];  this._colorStop1 = 0.0;
	this._color2 = [0,1,0,1];  this._colorStop2 = 0.3;
	this._color3 = [0,1,0,1];  this._colorStop3 = 0.6;
	this._color4 = [0,1,0,1];  this._colorStop4 = 1.0;

	this.init = function()
	{
		var material = this._materialNode;
		if (material)
		{
			var technique = material.shaderProgram.default;
			var renderer = g_Engine.getContext().renderer;
			if (renderer && technique)
			{
				if (this._shader && this._shader.default)
				{
					this._shader.default.u_color1.set( this._color1 );
					this._shader.default.u_color2.set( this._color2 );
					this._shader.default.u_color3.set( this._color3 );
					this._shader.default.u_color4.set( this._color4 );

					this._shader.default.u_colorStop1.set( [this._colorStop1] );
					this._shader.default.u_colorStop2.set( [this._colorStop2] );
					this._shader.default.u_colorStop3.set( [this._colorStop3] );
					this._shader.default.u_colorStop4.set( [this._colorStop4] );

					if (this._angle !== undefined)
						this._shader.default.u_cos_sin_angle.set([Math.cos(this._angle), Math.sin(this._angle)]);
				}
			}
		}
	}

	this.import = function( jObj )
	{
		this._color1	= jObj.color1,
		this._color2	= jObj.color2,
		this._color3	= jObj.color3,
		this._color4	= jObj.color4,
		this._colorStop1	= jObj.colorStop1,
		this._colorStop2	= jObj.colorStop2,
		this._colorStop3	= jObj.colorStop3,
		this._colorStop4	= jObj.colorStop4;

		if (this._angle !== undefined)
			this._angle = jObj.angle;
	}

}

function RuntimeLinearGradientMaterial()
{
	// inherit the members of RuntimeMaterial
	this.inheritedFrom = RuntimeRadialGradientMaterial;
	this.inheritedFrom();

	this._name = "LinearGradientMaterial";
	this._shaderName = "linearGradient";

	// the only difference between linear & radial gradient is the existance of an angle for linear.
	this._angle = 0.0;
}

function RuntimeBumpMetalMaterial()
{
	// inherit the members of RuntimeMaterial
	this.inheritedFrom = RuntimeMaterial;
	this.inheritedFrom();

	this._name = "BumpMetalMaterial";
	this._shaderName = "bumpMetal";

	this._lightDiff = [0.3, 0.3, 0.3, 1.0];
	this._diffuseTexture = "assets/images/metal.png";
	this._specularTexture = "assets/images/silver.png";
	this._normalTexture = "assets/images/normalMap.png";

	this.import = function( jObj )
	{
		this._lightDiff			= jObj.lightDiff;
		this._diffuseTexture	= jObj.diffuseTexture;
		this._specularTexture	= jObj.specularTexture;
		this._normalTexture		= jObj.normalMap;
	}

	this.init = function( world )
	{
		var material = this._materialNode;
		if (material)
		{
			var technique = material.shaderProgram.default;
			var renderer = g_Engine.getContext().renderer;
			if (renderer && technique)
			{
				if (this._shader && this._shader.default)
				{
					technique.u_light0Diff.set( this._lightDiff );

					var tex;
					var wrap = 'REPEAT',  mips = true;
					if (this._diffuseTexture)
					{
						this._diffuseTexture = world.remapAssetFolder( this._diffuseTexture );
						tex = renderer.getTextureByName(this._diffuseTexture, wrap, mips );
						if (tex)  technique.u_colMap.set( tex );
					}
					if (this._normalTexture)
					{
						this._normalTexture = world.remapAssetFolder( this._normalTexture );
						tex = renderer.getTextureByName(this._normalTexture, wrap, mips );
						if (tex)  technique.u_normalMap.set( tex );
					}
					if (this._specularTexture)
					{
						this._specularTexture = world.remapAssetFolder( this._specularTexture );
						tex = renderer.getTextureByName(this._specularTexture, wrap, mips );
						technique.u_glowMap.set( tex );
					}
				}
			}
		}
	}
}

function RuntimeUberMaterial()
{
	// inherit the members of RuntimeMaterial
	this.inheritedFrom = RuntimeMaterial;
	this.inheritedFrom();

	this._MAX_LIGHTS = 4;

	this.init = function(  )
	{
		var material = this._materialNode;
		if (material)
		{
			var technique = material.shaderProgram.defaultTechnique;
			var renderer = g_Engine.getContext().renderer;
			if (renderer && technique)
			{
				if (this._shader && this._shader.defaultTechnique)
				{
					if (this._ambientColor  && technique.u_ambientColor)   technique.u_ambientColor.set(this._ambientColor );
					if (this._diffuseColor  && technique.u_diffuseColor )   technique.u_diffuseColor.set(this._diffuseColor  );
					if (this._specularColor && technique.u_specularColor)   technique.u_specularColor.set(this._specularColor);
					if (this._specularPower && technique.u_specularPower)   technique.u_specularPower.set([this._specularPower]);

					if (this._lights)
					{
						for(var i = 0; i < 4; ++i)
						{
							var light = this._lights[i];
							if (light)
							{
								if(light.type == 'directional')
								{
									technique['u_light'+i+'Dir'].set( light.direction || [ 0, 0, 1 ]);
								}
								else if(light.type == 'spot')
								{
									technique['u_light'+i+'Atten'].set(light.attenuation || [ 1,0,0 ]); 
									technique['u_light'+i+'Pos'].set(light.position || [ 0, 0, 0 ]);
									technique['u_light'+i+'Spot'].set([ Math.cos( ( light.spotInnerCutoff || 45.0 )  * deg2Rad ), 
																		Math.cos( ( light.spotOuterCutoff || 90.0 ) * deg2Rad )]);
								}
								else
								{
									technique['u_light'+i+'Pos'].set(light.position || [ 0, 0, 0 ]);                        
									technique['u_light'+i+'Atten'].set(light.attenuation || [ 1,0,0 ]);                
								}

								// set the common light properties
								technique['u_light'+i+'Color'].set(light.diffuseColor || [ 1,1,1,1 ]);
								technique['u_light'+i+'Specular'].set(light.specularColor || [ 1, 1, 1, 1 ]);           
							}
						}
					}

					// currently not exported
					var uvTransform = [ 2.0, 0, 0, 0, 0, 2.0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1];
					technique.u_uvMatrix.set(uvTransform);
	
					var renderer = g_Engine.getContext().renderer;
					if (this._diffuseMap)
					{
						var tex = renderer.getTextureByName(this._diffuseMap, 'REPEAT');
						technique.s_diffuseMap.set( tex );
					}

					if (this._normalMap)
					{
						var tex = renderer.getTextureByName(this._normalMap, 'REPEAT');
						technique.s_normalMap.set( tex );
					}

					if (this._specularMap)
					{
						var tex = renderer.getTextureByName(this._specularMap, 'REPEAT');
						technique.s_specMap.set( tex );
					}

					if(this._environmentMap)
					{
						var tex = renderer.getTextureByName(this._environmentMap, 'CLAMP');
						technique.s_envMap.set( tex );
						if (this._environmentAmount)
							technique.u_envReflection.set([ this._environmentAmount ] );
					}
				}
			}
		}
	}

	this.update = function( time )
	{
	}

	this.import = function( jObj )
	{
		if (jObj.materialProps)
		{
			this._ambientColor  = jObj.materialProps.ambientColor;
			this._diffuseColor  = jObj.materialProps.diffuseColor;
			this._specularColor = jObj.materialProps.specularColor;
			this._specularPower = jObj.materialProps.specularPower;
		}

		var lightArray = jObj.lights;
		if (lightArray)
		{
			this._lights = [];
			for (var i=0;  i<this._MAX_LIGHTS;  i++)
			{
				var lightObj = lightArray[i];
				if (lightObj)
				{
					var type = lightObj['light'+i];
					if (type)
					{
						var light = new Object;
						switch (type)
						{
							case "directional":
								light.direction = lightObj['light' + i + 'Dir'];
								break;

							case "spot":
								light.position = lightObj['light' + i + 'Pos'];
								light['spotInnerCutoff'] = lightObj['light' + i + 'OuterSpotCutoff'];
								light['spotOuterCutoff'] = lightObj['light' + i + 'InnerSpotCutoff'];
								break;

							case "point":
								light.position = lightObj['light' + i + 'Pos'];
								light.attenuation = lightObj['light' + i + 'Attenuation'];
								break;

							default:
								throw new Error( "unrecognized light type on import: " + type );
								break;
						}

						// common to all lights
						light.diffuseColor  = lightObj['light' + i + 'Color'];
						light.specularColor = lightObj['light' + i + 'SpecularColor'];

						// push the light
						this._lights.push( light );
					}
					else
						this._lights[i] = 'undefined';
				}
			}
		}

		this._diffuseMap		= jObj['diffuseMap'];
		this._normalMap			= jObj['normalMap'];
		this._specularMap		= jObj['specularMap'];
		this._environmentMap	= jObj['environmentMap'];
		if (this._environmentMap)
			this._environmentAmount = jObj['environmentAmount'];
	}
}

function RuntimePlasmaMaterial()
{
	// inherit the members of RuntimeMaterial
	this.inheritedFrom = RuntimeMaterial;
	this.inheritedFrom();

	this.init = function(  )
	{
		this.update();
	}

	this.importJSON = function( jObj )
	{
		this._speed = jObj.speed;
		this._dTime = jObj.dTime;
	}

	this.update = function( time )
	{
		var material = this._materialNode;
		if (material)
		{
			var technique = material.shaderProgram.default;
			var renderer = g_Engine.getContext().renderer;
			if (renderer && technique)
			{
				if (this._shader && this._shader.default)
					this._shader.default.u_time.set( [this._time] );
				this._time += this._dTime;
				if (this._time > 200.0)  this._time = 0.0;
			}
		}
	}
}