/*
 *	Copyright (c) 2010 Two Front Productions
 *	www.twofront.com
 *	
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *	
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *	
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */

/*
 *	added play, stop, addKeyframe, removeKeyframe and setFrame to tfobject, and added TFLayer renamed ObjectsCollide to objectsCollide (although we should move it within TFObject so we pass only the second object...)
*/

function TFApplication (containerName, sourceFile) {
	var that = this;
	var device = {
		browser: navigator.userAgent.toLowerCase(),
	};
	var content = {
		container: document.getElementById(containerName), 
		fullscreenpage: "", 
		tfratio: null
	};
	var assets = {
		activeScene: null,
		audio: {},
		images: {},
		path: "",
		scenes: {},
		scripts: {},
		objs: {},
		shaders: {}
	};
	var keys = {
		map: {
			48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7', 56: '8', 57: '9',
			65: 'a', 66: 'b', 67: 'c', 68: 'd', 69: 'e', 70: 'f', 71: 'g', 72: 'h', 73: 'i', 74: 'j', 75: 'k', 76: 'l', 77: 'm',
			78: 'n', 79: 'o', 80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't', 85: 'u', 86: 'v', 87: 'w', 88: 'x', 89: 'y', 90: 'z',
			9: 'tab', 13: 'enter', 16: 'shift', 17: 'ctrl', 18: 'alt', 27: 'escape', 37: 'left', 38: 'up', 39: 'right', 40: 'down',
			8: 'backspace', 186: 'semicolon', 187: 'equals', 188: 'comma', 189: 'dash', 190: 'period', 191: 'forwardslash',
			192: 'grave comma', 219: 'open bracket', 220: 'backslash', 221: 'close bracket', 222: 'quote'
		}, // we'll fill it in later....
		states: {}
	};
	var pointer = {
		positions: [[0,0]],
		touch: false
	}
	var layers = {};
	var parameterTypes = {
		'ambientlight': 'colour', 'x': 'float', 'y': 'float', 'z': 'float', 'rotation': 'float', 'xrotation': 'float', 'yrotation': 'float', 'zrotation': 'float', 'layer': 'string', 'composite': ["none", "add", "subtract", "difference", "mask"],'compositing': 'boolean', 'subtract': 'boolean', 'width': 'float',
		'scale': 'float', 'scalewidth': 'boolean', 'visible': 'boolean', 'linecolour': 'colour', 'fillcolour': 'colour', 'audio': 'string',
		'curves': ["none", "quick", "bezier"], 'closed': 'boolean', 'radius': 'float', 'text': 'string', 'font': 'string', 'image': 'string', 'scene': 'string',
		'loopaudio': 'boolean', 'shadow': 'boolean', 'shadowblur': 'integer', 'shadowx': 'integer', 'shadowy': 'integer', 'object': 'string', 'colour': 'colour', 'texture': 'string', 'lighttype': ['directional', 'point'], 'active': 'boolean',
		'normals': ['hard', 'smooth', 'included'], 'perspective': 'boolean'
	};
	var globalVariable = {};
	var builtinShaders = {
		fragment: "#ifdef GL_ES\nprecision highp float;\n#endif\n\nvarying vec2 vTextureCoord;\nvarying vec3 vLightWeighting;\n\nuniform sampler2D uSampler;\nuniform bool uUseTexture;\nuniform vec4 uColour;\n\nvoid main(void) {\nvec4 fragColour;\nif (uUseTexture) {\nfragColour = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));\n} else {\nfragColour = uColour;\n}\ngl_FragColor = vec4(fragColour.rgb * vLightWeighting, fragColour.a);\n}",
		vertex: "attribute vec3 aPosition;\nattribute vec3 aNormal;\nattribute vec2 aUV;\n\nuniform mat4 uPositionMatrix;\nuniform mat4 uCameraMatrix;\nuniform mat4 uPerspectiveMatrix;\nuniform mat3 uNormalMatrix;\n\nuniform vec3 uAmbientColour;\n\nuniform vec3 uLightDirection[16];\nuniform vec3 uLightColour[16];\n\nvarying vec2 vTextureCoord;\nvarying vec3 vLightWeighting;\n\nvoid main(void) {\ngl_Position = uPerspectiveMatrix * uCameraMatrix * uPositionMatrix * vec4(aPosition, 1.0);\nvTextureCoord = aUV;\nvec3 transformedNormal = normalize(uNormalMatrix * vec4(aNormal, 0.0).xyz);\n\nvLightWeighting = uAmbientColour;\nfor (int light= 0; light < 16; light++) {\nfloat directionalLightWeighting = max(dot(transformedNormal, uLightDirection[light]), 0.0);\nvLightWeighting += (uLightColour[light] * directionalLightWeighting);\n}\n}"
	};
	///////////// TO BE REPLACED/REMOVED ////////////
	
	var gl;
	
	// these are the builtin attributes we support, they are also buffer names
	var bufferNames = {"aPosition": 3, "aNormal": 3, "aUV": 2};
	// these are the builtin uniforms we support, the ones with Matrix are also mat4 names
	var uniforms = {"uPerspectiveMatrix": true, "uCameraMatrix": true, "uPositionMatrix": true, "uNormalMatrix": true, "uAmbientColour": true, "uLightDirection": true, "uLightColour": true};
	var userUniforms = {};
	
	var matrices = {
		uPerspectiveMatrix: mat4.create(),
		uCameraMatrix: mat4.create(),
		uPositionMatrix: mat4.create()
	};
	var shaderProgram;
	var positionMatrixStack = [];
	function saveMatrix() {
		var copy = mat4.create();
		mat4.set(matrices.uPositionMatrix, copy);
		positionMatrixStack.push(copy);
	}
	function restoreMatrix() {
		if (positionMatrixStack.length != 0) {
			matrices.uPositionMatrix = positionMatrixStack.pop();
		}
	}
	var ambientLight = [.3, .3, .3];
	
	var lastWidth = 0;
	// number of calls to waitingtoload
	// used for image loading
	var calls = 0;
	// this tells us what tfobject the mouse is over
	var currentlyInside = new Object();
	// number of next name (so we don't get duplicates)
	var nextNameNumber = 0;
	// these are used by the preload function
	var loadFunction;
	var loadingInterval;
	var mintime = 0.0;
	///////////// PUBLIC FUNCTION POINTERS ////////////
	this.setParameters = setParameters;
	this.createListener = createListener;
	this.TFGradient = TFGradient;
	this.TFLayer = TFLayer;
	this.getLayers = getLayers;
	this.TFObject = TFObject;
	this.getObject = getObject;
	this.getObjects = getObjects;
	this.getParameters = getParameters;
	this.refreshCanvas = refreshCanvas;
	this.keyPressed = keyPressed;
	this.mousePosition = mousePosition;
	this.parameterType = parameterType;
	this.getDistance = getDistance;
	this.preload = preload;
	this.parameterType = parameterType;
	this.setFitMode = setFitMode;
	this.setResourcePath = setResourcePath;
	this.setFullscreenPage = setFullscreenPage;
	this.fullscreen = fullscreen;
	this.emptyScene = emptyScene;
	this.loadSource = loadSource;
	this.objectsCollide = objectsCollide;
	///////////// PUBLIC FUNCTIONS ////////////
	function TFScene () {
		// one of these is the main ones that is displayed... the others can be used as assets
		var objectManagers = {
			name: {},
			parent: {},
			root: null,
			lights: [],
			cameraStack: [],
		};
		
		this.getManagers = getManagers;
		
		function getManagers() {
			return objectManagers;
		}
		
		objectManagers.root = new TFObject("root", "RootObj")
		return this;
	}
	// this is used to readjust the canvas' dimensions and resolution as necessary
	function TFRatio() {
		var ratio = this;
		var definedDimensions = [0,0];
		var currentDimensions = [0,0];
		var iZoom = 1.0;
		var scale = 1;
		// fit, horizontalFit, verticalFit, none, resize
		var fitType = "horizontalFit";
		
		this.check = function(forceIt) {
			var cWidth = content.container.offsetWidth;
			var cHeight = content.container.offsetHeight;
			var cZoom = 1;
			if (pointer.touch) {
				cZoom = window.devicePixelRatio/window.innerWidth;
				if (Math.abs(window.orientation) == 90 && device.browser.search("android") == -1) cZoom *= screen.height;
				else cZoom *= screen.width;
			}
			// we only continue if the user zoomed or change the dimensions...
			if (cWidth == currentDimensions[0] && cHeight == currentDimensions[1] && cZoom == iZoom && !forceIt) return false;
			// otherwise, we store the new values and adjust each layer
			currentDimensions = [cWidth, cHeight];
			iZoom = cZoom;
			// get the horizontal and vertical pixels for our canvas'
			var hPixels = Math.round(cWidth*cZoom);
			var vPixels = Math.round(cHeight*cZoom);
			// get the scale ratio to fit horizontally, and vertically
			var hScale = 1;
			var vScale = 1;
			if (definedDimensions[0] != 0 && definedDimensions[1] != 0) {
				hScale = hPixels/definedDimensions[0];
				vScale = vPixels/definedDimensions[1];
			}
			//alert(hPixels + " " + vPixels + " " + hScale + " " + vScale);
			for (var l in layers) {
				var lCanvas = layers[l].getCanvases()[0].getCanvas();
				if (l != "opengl") {
					var lContext = layers[l].getCanvases()[0].getContext();
					lCanvas.width = hPixels;
					lCanvas.height = vPixels;
					if (fitType == "horizontalFit" || (fitType == "fit" && hScale < vScale)) {
						lContext.scale(hScale, hScale);
						scale = hScale/cZoom;
					} else if (fitType == "verticalFit" || fitType == "fit") {
						lContext.scale(vScale, vScale);
						scale = vScale/cZoom;
					} else {
						lContext.scale(cZoom, cZoom);
						scale = 1;
					}
				} else if (gl) {
					lCanvas.width = hPixels;
					lCanvas.height = vPixels;
					gl.viewport(0, 0, hPixels, vPixels);
					
					var cam = assets.activeScene.getManagers().cameraStack[0];
					// vertical fov degrees, aspect ratio, close cutoff, farcutoff, ??
					if (cam === undefined || cam.getParameter("perspective")) mat4.perspective(45, hPixels/vPixels, 0.1, 500.0, matrices.uPerspectiveMatrix);
					else {
						mat4.identity(matrices.uPerspectiveMatrix);
						mat4.scale(matrices.uPerspectiveMatrix, [1/(hPixels/2), 1/(vPixels/2), 0]);
					}
				}
			}
			return true; 
		}
		this.setDimensions = function(xDim, yDim) {
			if (xDim != null) definedDimensions[0] = xDim;
			if (yDim != null) definedDimensions[1] = yDim;
			currentDimensions = [0,0];
			if (fitType == "resize") resize(xDim, yDim);
		}
		this.setFitType = function(fType) {
			if (fType == "fit" || fType == "horizontalFit" || fType == "verticalFit" || fType == "resize") {
				fitType = fType;
			} else {
				fitType = "none";
			}
		}
		this.getDimensions = function() {
			return [definedDimensions[0], definedDimensions[1]];
		}
		// zoom on iphone/ipad
		this.getZoom = function() {
			return iZoom;
		}
		// scale if content size is scaled up (because the defined area is bigger)
		this.getScale = function() {
			return scale;
		}
		this.getFitType = function() {
			return fitType;
		}
		function resize(xVal, yVal) {
			if (xVal != null) content.container.style.width = xVal + "px";
			if (yVal != null) content.container.style.height = yVal + "px";
		}
		function Init() {
			definedDimensions = [content.container.offsetWidth, content.container.offsetHeight];
			ratio.check();
		}
		Init();
		return this;
	}
	function setParameters(pars) {
		for (var par in pars) {
			if (pars[par][0] == "width") {
				content.tfratio.setDimensions(pars[par][1], null);
			} else if (pars[par][0] == "height") {
				content.tfratio.setDimensions(null, pars[par][1]);
			} else if (pars[par][0] == "colour") {
				content.container.style.background = pars[par][1];
			} else if (pars[par][0] == "ambientlight") {
				var a = pars[par][1].split("(");
				var b = a[1].split(")");
				var c = b[0].split(",");
				ambientLight[0] = parseFloat(c[0])/255;
				ambientLight[1] = parseFloat(c[1])/255;
				ambientLight[2] = parseFloat(c[2])/255;
			}
		}
	}
	function createListener(gevent, gfunc) {
		assets.activeScene.getManagers().root.createListener(gevent, gfunc);
	}
	function TFGradient(startPoint, endPoint, gradPoints, gradtype) {
		var gradType = "linear";
		if (typeof(gradtype) != "undefined") gradType = gradtype;
		var startP = startPoint;
		var endP = endPoint;
		var gradP = gradPoints;
		var outerThis = this;
		CreateGradient();
		
		// private functions
		function CreateGradient () {
			//alert("creating...");
			// radial gradient need to be handled in a more flexible manour...
			//if (typeof(gradType) != "undefined" && gradType == "radial") outerThis.gradient = canvas.createRadialGradient(startP[0], startP[1], endP[0], startP[0], startP[1], endP[1]);
			var canvas = layers[""].getCanvases()[0].getContext();
			if (typeof(gradType) != "undefined" && gradType == "radial") outerThis.gradient = canvas.createRadialGradient(startP[0], startP[1], 0, startP[0], startP[1], Math.pow(Math.pow(endP[1]-startP[1],2)+Math.pow(endP[0]-startP[0],2),.5));
			else outerThis.gradient = canvas.createLinearGradient(startP[0], startP[1], endP[0], endP[1]);
			var i = 0;
			while (i < gradP.length) {
				//alert(gradP[i][0] + " " + gradP[i][1]); 
				if (typeof(gradP[i][1]) != "object") outerThis.gradient.addColorStop(gradP[i][0], gradP[i][1]);
				i++;
			}
		}
		
		// public functions
		function setType (newType) {
			gradType = newType;
			CreateGradient();
		}
		function getType () {
			return gradType;
		}
		function setEnds (sP, eP) {
			startP = sP;
			endP = eP;
			CreateGradient();
		}
		function setColours (grs) {
			gradP = grs;
			CreateGradient();
		}
		function getEnds () {
			return {"start": startP, "end": endP};
		}
		function getColours () {
			return gradP;
		}
		function addColour (colourPoint, colourValue) {
			gradP.push([colourPoint, colourValue]);
			CreateGradient();
		}
		function removeColour (rpos) {
			gradP.splice(rpos, 1);
			CreateGradient();
		}
		function setColour (indexValue, colourPoint, colourValue) {
			gradP[indexValue] = [colourPoint, colourValue];
			CreateGradient();
		}
		
		this.setType = setType;
		this.getType = getType;
		this.setEnds = setEnds;
		this.setColours = setColours;
		this.getEnds = getEnds;
		this.getColours = getColours;
		this.addColour = addColour;
		this.removeColour = removeColour;
		this.setColour = setColour;
	}
	/*
	 * TFLayers will replace the classic layers.
	 * They give layers there own objects and allow
	 * both translation and tiling.
	 * The translation is much less resource intensive than
	 * object translation and should be used whenever possible.
	 * Especially if you want good performance on mobile devices.
	 */ 
	function TFLayer(ln) {
		var tflayer = this;
		this.translate = translate;
		this.getPosition = getPosition;
		this.getName = getName;
		this.getCanvases = getCanvases;
		this.setDepth = setDepth;
		this.getDepth = getDepth;
		
		var layername = "";
		var canvases = new Array();
		var position = [0,0];
		var depth = 1000;
		
		function setDepth(newDepth) {
			canvases[0].getCanvas().style.zIndex = newDepth;
			depth = newDepth;
		}
		function getDepth() {
			return depth;
		}
		function translate(offset) {
			position[0] += offset[0];
			position[1] += offset[1];
			for (var can in canvases) {
				var canvas = can.getCanvas();
				canvas.style.left = (canvas.offsetLeft+offset[0]) + "px";
				canvas.style.top = (canvas.offsetTop+offset[1]) + "px";
			}
			// now generate and delete canvases as necessary
		}
		function getCanvases() {
			return canvases;
		}
		function getPosition() {
			return [position[0], position[1]];
		}
		function getName() {
			return ln;
		}
		// private members
		function TFGL(canvas) {
			try {
				gl = canvas.getContext("experimental-webgl");
			} catch (err) {}
			if (!gl) return null;
			
			// this should later be stored in a tfobject
			//var buffer;
			
			function initShaders() {
				var fragmentShader = compileShader(gl, "_.fs", builtinShaders.fragment);
				var vertexShader = compileShader(gl, "_.vs", builtinShaders.vertex);
				assets.shaders["_fragment"] = fragmentShader;
				assets.shaders["_vertex"] = vertexShader;
				// create a program to hold the shaders
				shaderProgram = gl.createProgram();
				gl.attachShader(shaderProgram, vertexShader);
				gl.attachShader(shaderProgram, fragmentShader);
				gl.linkProgram(shaderProgram);
				if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
					console.log("Could not initialise shaders");
				}
				// send the program to opengl
				gl.useProgram(shaderProgram);
				
				for (var att in bufferNames) {
					shaderProgram[att] = gl.getAttribLocation(shaderProgram, att);
					gl.enableVertexAttribArray(shaderProgram[att]);
				}
				for (var uni in uniforms) {
					shaderProgram[uni] = gl.getUniformLocation(shaderProgram, uni);
				}
				for (var unif in userUniforms) {
					shaderProgram[unif] = gl.getUniformLocation(shaderProgram, unif);
				}
			}
			function addUniforms(uniObject, shaderText) {
				var reg = new RegExp("uniform (([A-Za-z0-9]*) ([A-Za-z0-9]*));","gi");
				var tmp;
				while (tmp = reg.exec(shaderText)) {
					if (!uniforms[tmp[3]]) uniObject[tmp[3]] = tmp[2];
				};
			}
			// this functionality needs to be redone
			// in the preload function
			/*function getShader(gl, id) {
				// get the shader
				var request; 
				if (window.XMLHttpRequest) { 
					request = new XMLHttpRequest(); 
				} else { 
					request = new ActiveXObject("Microsoft.XMLHTTP"); 
				}
				request.open("GET", assets.path+id, false); 
				request.send(""); 
				var code = request.responseText;
				//// extract uniforms
				addUniforms(userUniforms, code);
				compileShader(gl, code);
			}*/
			function compileShader(gl, id, code) {
				// this needs to be rethought as each program will
				// have different uniforms
				addUniforms(userUniforms, code);
				// compile the shader as the right type
				var shader;
				if (id.indexOf('.fs') != -1) {
					shader = gl.createShader(gl.FRAGMENT_SHADER);
				} else if (id.indexOf('.vs') != -1) {
					shader = gl.createShader(gl.VERTEX_SHADER);
				}
				gl.shaderSource(shader, code);
				gl.compileShader(shader);
				if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
					console.log("log" + gl.getShaderInfoLog(shader));
				}
				return shader;
			}
			function setupGL() {
				// each object should have its own shader... for now we have one per tfapp
				initShaders();
				// make transparent so we can see the main canvas
				gl.clearColor(0.0, 0.0, 0.0, 1.0);
				// hide objects behind other objects
				gl.enable(gl.DEPTH_TEST);
			}
			
			setupGL();
		}
		function TFCanvas(off) {
			var canvas;
			var context;
			var offset = [0,0];
			
			this.getCanvas = getCanvas;
			this.getContext = getContext;
			this.getOffset = getOffset;
			
			function getCanvas() {
				return canvas;
			}
			function getContext() {
				return context;
			}
			function getOffset() {
				return offset;
			}
			// private members
			function Initialize() {
				canvas = document.createElement("canvas");
				canvas.setAttribute("style", "position: absolute; width: 100%; height: 100%;");
				content.container.appendChild(canvas);
				if (ln != "opengl") {
					if (typeof(G_vmlCanvasManager) != "undefined") can = G_vmlCanvasManager.initElement(can);
					context = canvas.getContext("2d");
				} else {
					canvas.width = 640;
					canvas.height = 480;
					context = new TFGL(canvas);
				}
				//////////////////////// SET RESOLUTION AND RATIO HERE (ONCE GLOBAL) //////////////////////////
				// maybe setup better method in future...
				lastWidth = 0;
				content.ratio = 0;
				canvas.style.zIndex = 1000;
			}
			Initialize();
		}
		function Initialize() {
			// set unique name
			if (typeof(layers[ln]) != "undefined") {
				var n = 1;
				while (typeof(layers[ln+""+n]) != "undefined") {
					n++;
				}
				layername = ln+""+n;
			} else {
				layername = ln;
			}
			// add to global index of layers
			layers[layername] = tflayer;
			
			// now create first canvas
			canvases.push(new TFCanvas([0,0]));
			content.tfratio.check(true);
		}
		Initialize();
		return this;
	}
	function getLayers() {
		return layers;
	}
	// each TFObject and TFScript has a TFTimeline
	// in the future we could extend this so that each
	// object/script could have multipe
	function TFTimeline() {
		var frame = 1;
		var properties = {};
		this.addKeyframes = addKeyframes;
		this.getKeyframes = getKeyframes;
		this.getFrame = getFrame;
		this.getParameter = getParameter;
		this.setFrame = setFrame;
		function getKeyframes() {
			return properties;
		}
		function getFrame() {
			return frame;
		}
		function getParameter(par) {
			var low = findFrame(par, frame, -1);
			var high = findFrame(par, frame, +1);
			if (low[0] == 0 && high[0] == 999999) return null;
			else if (low[0] == 0 || low[0] == high[0]) return high[1].value;
			else if (high[0] == 999999) return low[1].value;
			else {
				if (parameterType(par) == "integer" || parameterType(par) == "float") {
					return interpolate(frame, low, high)
				} else if (parameterType(par) == "colour") {
					if (low[1].value && high[1].value) {
						var start = low[1].value.split("(");
						var end = high[1].value.split("(");
						if (start.length > 1 && end.length > 1) {
							start = start[1].split(")");
							end = end[1].split(")");
							start = start[0].split(",");
							end = end[0].split(",");
							var res = [];
							for (var i=0; i<start.length && i<end.length; i++) {
								var st = [low[0], {value: parseFloat(start[i])}];
								for (var p in low[1]) {
									if (p != "value") st[1][p] = low[1][p];
								}
								var en = [high[0], {value: parseFloat(end[i])}];
								for (var p in high[1]) {
									if (p != "value") en[1][p] = high[1][p];
								}
								res.push(interpolate(frame, st, en));
							}
							if (res.length == 3) return "rgb(" + parseInt(res[0]) + ","  + parseInt(res[1]) + ","  + parseInt(res[2]) + ")";
							else if (res.length == 4) return "rgba(" + parseInt(res[0]) + ","  + parseInt(res[1]) + ","  + parseInt(res[2]) + ","  + res[3] + ")";
						} else {
							return low[1].value;
						}
					} else {
						return null;
					}
				} else {
					return low[1].value;
				}
			}
		}
		function setFrame(newframe) {
			frame = newframe;
		}
		function addKeyframes(fr, prop) {
			// properties is in the following form {propertyName: {value, easeIn, easeOut, enabled}}
			for (var p in prop) {
				if (!properties[p]) properties[p] = {};
				properties[p][fr] = prop[p];
			}
		}
		/////// PRIVATE MEMBERS ///////
		function findFrame(par, start, inter) {
			var kf = null;
			var cur = 0;
			if (inter == 1) cur = 999999;
			if (properties[par] && properties[par]) {
				for (var f in properties[par]) {
					var prop = properties[par][f];
					if ((prop.enabled || typeof(prop.enabled) == "undefined") && ((inter == -1 && f <= start && f > cur) || (inter == 1 && f >= start && f < cur))) {
						kf = prop;
						cur = f;
					}
				}
			}
			return [cur, kf];
		}
		function interpolate(frame, low, high) {
			low[1].value = parseFloat(low[1].value);
			high[1].value = parseFloat(high[1].value);
			if (!low[1].easeOut && !high[1].easeIn) {
				var val = low[1].value+( (high[1].value-low[1].value) * ((frame-low[0])/(high[0]-low[0])) );
				return val;
			} else {
				var p1 = {x: parseFloat(low[0]), y: low[1].value};
				var p2 = {x: parseFloat(high[0]), y: high[1].value};
				var b1 = p1;
				var b2 = p2;
				if (low[1].easeOut) {
					b1 = {x: (p1.x+p2.x)/2, y: low[1].value};
				}
				if (high[1].easeIn) {
					b2 = {x: (p1.x+p2.x)/2, y: high[1].value};
				}
				var cx = 3*(b1.x-p1.x);
				var bx = (3*(b2.x-b1.x))-cx;
				var ax = p2.x-p1.x-cx-bx;
				var cy = 3*(b1.y-p1.y);
				var by = (3*(b2.y-b1.y))-cy;
				var ay = p2.y-p1.y-cy-by;
				// we approximate the curve by breaking it into 10 straight peices
				var before = [0,0];
				var after = [0,0];
				for (var t=0.0; t<=1; t+=.1) {
					after[0] = ax*Math.pow(t,3) + bx*Math.pow(t,2) + cx*t + p1.x;
					after[1] = ay*Math.pow(t,3) + by*Math.pow(t,2) + cy*t + p1.y;
					if (after[0] < frame) {
						before[0] = after[0];
						before[1] = after[1];
					} else {
						break;
					}
				}
				return before[1] + ( ((after[1]-before[1])/(after[0]-before[0])) * (frame-before[0]) );
			}
		}
		
		return this;
	}
	function TFObject(obtype, objname, someparam, addToScene) {	
		////////////////////// MOVE CODE TO NEW INITIALIZE FUNCTION AND STREAMLINE THIS LEVELS "GLOBAL" VARIABLES (like we did at the TFApp level) ///////////////////
		var thisObj = this;
		// private variables
		var manager;
		var tfscene;
		var name;
		if (obtype != "root") {
			if (typeof(addToScene) == "undefined") {
				// if no scene is specified we assign it to the activeScene		
				manager = assets.activeScene.getManagers();
				tfscene = assets.activeScene;
			} else {
				// otherwise we assign the object to the specified scene
				manager = addToScene.getManagers();
				tfscene = addToScene;
			}
			// lets figure out an acceptable name...
			if (typeof(objname) != "undefined" && typeof(manager.name[objname]) == "undefined") name = objname;
			else if (typeof(manager.name[objname]) != "undefined") {
				var num = 0;
				while (typeof(manager.name[objname + num]) != "undefined") {
					num++;
				}
				name = objname + num;
			} else {
				name = "Object" + nextNameNumber;
				while (typeof(manager.name[name]) != "undefined") {
					nextNameNumber++;
					name = "Object" + nextNameNumber;
				}
				nextNameNumber++;
			}
		} else {
			name = objname;
		}
		// the bounding box is mostly used to speed up collision detection
		// but can also be made visible, these are the elements: (rendered, x1, y1, x2, y2)
		var boundingbox = [false, 1000000000, 1000000000, -1000000000, -1000000000];
		var showPivot = false;
		var showPoints = false;
		var showControls = false;
		
		// we keep an separate instance for each object so that we can have multiple of the same sound
		// playing at once
		var actualAudio;
		
		// this is to replace all the buffer vars
		var buffers = {};
		// might include smooth, hard, and included
		var calculatedNormals = {};
		// this stores the used textures
		var textures = {};
		// shaderproperties
		var shaderProperties = {};

		var objectType = obtype;
		var points = new Array();
		var scripts = new Array();
		var tftimeline = new TFTimeline();
		var children = new Array();
		
		var interval; 
		
		var currentState = new Object();
		
		if (!gl && (obtype == "gl" || obtype == "light" || obtype == "camera")) {
			new TFLayer("opengl");
		}
		if (obtype != "gl" && obtype != "light" && obtype != "camera") {
			currentState.x = 0;
			currentState.y = 0;
			currentState.rotation = 0.0;
			currentState.scale = 1.0;
			if (typeof(obtype) != "undefined" && obtype != "root") {
				currentState.layer = "";
				if (typeof(obtype) != "undefined") {
					if (obtype != "audio" && obtype != "image" && obtype != "null") {
						currentState.width = 1;
						currentState.scalewidth = true;
						currentState.linecolour = "rgb(0,0,0)";
						currentState.fillcolour = "none";
						currentState.shadow = false;
						currentState.shadowblur = 5;
						currentState.shadowx = 5;
						currentState.shadowy = 5;
					}
					if (obtype != "audio" && obtype != "null") {
						currentState.visible = true;
						//currentState.compositing = false;
						currentState.composite = "none"
						//currentState.subtract = false;
					} else if (obtype == "audio") {
						currentState.audio = "";
						currentState.loopaudio = false;
					}
					if (obtype == "line") {
						currentState.curves = "none";
						currentState.closed = false;
					} else if (obtype == "rectangle") {
						//currentState.collider = false;
					} else if (obtype == "circle") {
						currentState.radius = 1;
					} else if (obtype == "text") {
						currentState.text = "Hello World";
						currentState.font = "1em Arial";
						currentState.linecolour = "none";
						currentState.fillcolour = "rgb(0,0,0)";
					} else if (obtype == "image") {
						// image must be set using setparameters...
						currentState.image = "";
						} else if (obtype == "pointer") {
						currentState.scene = "";
					}
				}
			}
		} else if (obtype == "gl") {
			currentState.x = 0;
			currentState.y = 0;
			currentState.z = 10;
			currentState.xrotation = 0.0;
			currentState.yrotation = 0.0;
			currentState.zrotation = 0.0;
			currentState.scale = 1.0;
			currentState.object = "";
			//currentState.colour = "rgb(255,255,255)";
			currentState.normals = "smooth";
			
			for (var uni in userUniforms) {
				if (userUniforms[uni] == "bool") shaderProperties[uni] = false;
				else if (userUniforms[uni] == "vec4") shaderProperties[uni] = "1,1,1,1";
				else shaderProperties[uni] = "";
			}
			this.getShaderProperties = function() {
				return shaderProperties;
			}
			this.getShaderPropertyTypes = function() {
				return userUniforms;
			}
			function LoadTexture(prop, val) {
				textures[prop] = gl.createTexture();
				textures[prop].image = new Image();
				textures[prop].image.onload = function() {
					console.log("texture loaded as " + prop);
					gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
					gl.bindTexture(gl.TEXTURE_2D, textures[prop]);
					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[prop].image);
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
					gl.generateMipmap(gl.TEXTURE_2D);
					gl.bindTexture(gl.TEXTURE_2D, null);
					refreshCanvas();
				}
				textures[prop].image.src = assets.path + val;
			}
			this.setShaderProperty = function(prop, val) {
				shaderProperties[prop] = val;
				//console.log(userUniforms[prop]);
				if (userUniforms[prop] == "sampler2D") {
					//console.log(userUniforms[prop]);
					new LoadTexture(prop, val);
				}
			}
			
			// each buffer lines up to an attrib
			for (var att in bufferNames) {
				buffers[att] = gl.createBuffer();
				buffers[att].itemSize = bufferNames[att];
				buffers[att].numItems = 0;
			}
			
			this.getBuffers = function() {
				return buffers;
			}
			this.getTextures = function() {
				return textures;
			}
		} else if (obtype == "light") {
			currentState.lighttype = "directional";
			// these will represent position for point lights
			// and direction for directional lights
			currentState.x = 1.0;
			currentState.y = -1.0;
			currentState.z = 1.0;
			currentState.colour = "rgb(255,255,255)";
			tfscene.getManagers().lights.push(thisObj);
		} else if (obtype == "camera") {
			//currentState.active = true;
			currentState.x = 0.0;
			currentState.y = 0.0;
			currentState.z = 0.0;
			currentState.xrotation = 0.0;
			currentState.yrotation = 0.0;
			currentState.zrotation = 0.0;
			currentState.perspective = true;
			tfscene.getManagers().cameraStack.push(thisObj);
		}
			
		// public method pointers
		this._addChild = _addChild;
		this.addPoint = addPoint;
		this.addPoints = addPoints;
		this.TFScript = TFScript;
		this.changeDepth = changeDepth;
		this.childDepth = childDepth;
		this.createListener = createListener;
		this.deleteObject = deleteObject;
		this.duplicateObject = duplicateObject;
		this.getBounds = getBounds;
		this.getChildren = getChildren;
		this.getExtras = getExtras;
		this.getKeyframes = getKeyframes;
		this.getSmoothKeys = getSmoothKeys;
		this.getListeners = getListeners;
		this.getName = getName;
		this.getParameter = getParameter;
		this.getParameters = getParameters;
		this.getParent = getParent;
		this.getPoints = getPoints;
		this.getScene = getScene;
		this.getScripts = getScripts;
		this.getTimeline = getTimeline;
		// if we have a point we want to know relative to an object
		// all we do is perform the objects transformation matrix on that point
		// also, for translation, if we unperform the transormation matrix on the delta
		// we will see how to change it's position relative to the world...
		// also, if we do the recipricol of a objs parent matrix on the child we can put it back onto the root object
		this.getTransformationMatrix = getTransformationMatrix;
		this.getTotalRotationAndScale = getTotalRotationAndScale;
		this.getType = getType;
		this.mousePosition = mousePosition;
		this.movePivot = movePivot;
		this.pointInside = pointInside;
		this.playAudio = playAudio;
		this._removeChild = _removeChild;
		this.removeChildren = removeChildren;
		this.removeKeyframe = removeKeyframe;
		this.removePoint = removePoint;
		this.rotate = rotate;
		this.scale = scale;
		this.setExtras = setExtras;
		this.setFrame = setFrame;
		this.setName = setName;
		this.setParameter = setParameter;
		this.setParameters = setParameters;
		this.setParent = setParent;
		this.setPoint = setPoint;
		this.stopAudio = stopAudio;
		this.translate = translate;
		this.localTranslate = localTranslate;
		this.closestLimit = closestLimit;
		
		var objectListeners = new Object();
		
		function mousePosition() {
			var mat = getTransformationMatrix();
			mat4.inverse(mat);
			var mpos = that.mousePosition();
			var npos = [mpos[0], mpos[1], 0];
			mat4.multiplyVec3(mat, npos);
			return npos;
		}
		function closestLimit(pnt) {
			var tmat = getTransformationMatrix();
			var mat = mat4.create();
			mat4.inverse(tmat, mat);
			var vec = [pnt[0], pnt[1], 0];
			mat4.multiplyVec3(mat, vec);
			pnt = vec;
			
			var dist = 999999;
			var delt = null;
			
			var last = [points[0][0], points[0][1]];
			for (var i=1; i<points.length; i++) {
				var current = [points[i][0], points[i][1]];
				if (last[0]-current[0] == 0) {
					// vertical
					var dif = pnt[0]-current[0];
					if (Math.abs(dif) < dist) {
						if ((pnt[1]<=last[1] && pnt[1]>=current[1]) || (pnt[1]>=last[1] && pnt[1]<=current[1])) {
							dist = Math.abs(dif);
							delt = [-dif,0, 0];
						}
					}
				} else if (last[1]-current[1] == 0) {
					// horizontal
					var dif = pnt[1]-current[1];
					if (Math.abs(dif) < dist) {
						if (((pnt[0]<=last[0] && pnt[0]>=current[0]) || (pnt[0]>=last[0] && pnt[0]<=current[0]))) {
							dist = Math.abs(dif);
							delt = [0,-dif, 0];
						}
					}
				} else {
					var slope1 = (last[1]-current[1])/(last[0]-current[0]);
					var b1 = last[1]+(last[0]*slope1);
					var slope2 = -slope1;
					var b2 = pnt[1]+(pnt[0]*slope2);
					var xpos = (b2-b1)/(2*slope1);
					var ypos = (slope1*xpos)+b1;
					if (((xpos<=last[0] && xpos>=points[i][0]) || (xpos>=last[0] && xpos<=points[i][0])) && ((ypos<=last[1] && ypos>=points[i][1]) || (ypos>=last[1] && ypos<=points[i][1]))) {
						var xdif = xpos-pnt[0];
						var ydif = ypos-pnt[1];
						var dis = Math.pow(Math.pow(xdif,2)+Math.pow(ydif,2),.5);
						if (dis < dist) {
							dist = dis;
							delt = [xdif, ydif, 0];
						}
					}
				}
				last = current;
			}
			var delt2 = ClosestPoint(pnt);
			if (Math.pow(Math.pow(delt2[0],2) + Math.pow(delt2[1],2),.5) <  dist) {
				mat4.multiplyVec3(tmat, delt2);
				return delt2;
			}
			mat4.multiplyVec3(tmat, delt);
			return delt;
		}
		// support function
		// not publically exposed
		function ClosestPoint(pnt) {
			var dist = 999999;
			var vec = null;
			
			for (var i=0; i<points.length; i++) {
				var dx = (points[i][0]) - pnt[0];
				var dy = (points[i][1]) - pnt[1];
				var di = Math.pow(Math.pow(dx,2) + Math.pow(dy,2),.5);
				if (di < dist) {
					vec = [dx, dy, 0];
					dist = di;
				}
			}
			return vec;
		}
		function getTransformationMatrix() {
			// either get parents transformationmatrix
			// or if there is no parent create a fresh one
			var par = getParent();
			var mat;
			if (par) {
				//console.log("inheriting matrix");
				mat = par.getTransformationMatrix();
			} else {
				//console.log("creating matrix");
				mat = mat4.create();
				mat4.identity(mat);
			}
			// now do this objects transformations on the matrix
			var trans = [currentState.x, currentState.y];
			if (currentState.z !== undefined) trans.push(currentState.z);
			else trans.push(0);
			var rot = [];
			if (currentState.rotation !== undefined) {
				rot.push(0); rot.push(0);
				//console.log("rot="+currentState.rotation);
				rot.push(currentState.rotation);
			} else {
				rot.push(currentState.xrotation);
				rot.push(currentState.yrotation);
				rot.push(currentState.zrotation);
			}
			var sc = currentState.scale;
			mat4.translate(mat, [trans[0], trans[1], trans[2]]);
			mat4.rotate(mat, rot[0], [1, 0, 0]);
			mat4.rotate(mat, rot[1], [0, 1, 0]);
			mat4.rotate(mat, rot[2], [0, 0, 1]);
			mat4.scale(mat, [sc, sc, sc]);
			return mat;
		}
		function getTotalRotationAndScale() {
			var par = getParent();
			var rotsca;
			if (par) {
				rotsca = par.getTotalRotationAndScale();
			} else {
				rotsca = {rotation: 0.0, scale: 1.0};
			}
			if (currentState.rotation !== undefined) rotsca.rotation += currentState.rotation
			rotsca.scale *= currentState.scale;
			return rotsca;
		}
		function addPoint(givenpoint) {
			points.push(givenpoint);
			
			// keep our bounding box up to date
			if (givenpoint[0] < boundingbox[1]) boundingbox[1] = givenpoint[0];
			if (givenpoint[0] > boundingbox[3]) boundingbox[3] = givenpoint[0];
			if (givenpoint[1] < boundingbox[2]) boundingbox[2] = givenpoint[1];
			if (givenpoint[1] > boundingbox[4]) boundingbox[4] = givenpoint[1];
		}
		function addPoints(givenpoints) {
			var i = 0;
			while (i < givenpoints.length) {
				points.push(givenpoints[i]);
				
				// keep our bounding box up to date
				if (givenpoints[i][0] < boundingbox[1]) boundingbox[1] = givenpoints[i][0];
				if (givenpoints[i][0] > boundingbox[3]) boundingbox[3] = givenpoints[i][0];
				if (givenpoints[i][1] < boundingbox[2]) boundingbox[2] = givenpoints[i][1];
				if (givenpoints[i][1] > boundingbox[4]) boundingbox[4] = givenpoints[i][1];
				
				i++;
			}
		}
		function TFScript (scriptName, runscript, sharedVariable) {
			var scriptInstance;
			
			this.getShared = getShared;
			this.getName = getName;
			this.isRunning = isRunning;
			this._destroy = destroy;
			var thisScript = this;
			function getShared() {
				return sharedVariable;
			}
			function getName() {
				return scriptName;
			}
			function isRunning() {
				return runscript;
			}
			function destroy() {
				// This method is not exposed since if it were used without destroying the TFObject,
				// the script would also need to be removed from the TFObjects "scripts" registry.
				if (typeof(scriptInstance) != "undefined" && typeof(scriptInstance.destructor) != "undefined") {
					scriptInstance.destructor();
				}
			}
			function Initialize() {
				if (typeof(sharedVariable) == "undefined") sharedVariable = {};
				// scripts don't do background loading, so we can do these steps linearly...
				if (typeof(assets.scripts[scriptName]) == "undefined") {
					preload(null, [[scriptName]]);
				}
				// run the script unless specifically told not to
				// theObj = TFObject, that = TFApplication, assets... = code to run,
				// sharedVariables is the variable this script shares, and globalVar is the single variable all scripts share 
				if (typeof(runscript) == "undefined" || runscript == true) scriptInstance = new _RunScript(thisObj, that, assets.scripts[scriptName], sharedVariable, globalVariable);
				scripts.push(thisScript);
			}
			Initialize();
			return this;
		}
		function createListener (gevent, gfunc) {
			if (typeof(objectListeners[gevent]) == "undefined") objectListeners[gevent] = new Array();
			objectListeners[gevent].push(gfunc);
		}
		function getBounds () {
			return [boundingbox[1], boundingbox[2], boundingbox[3], boundingbox[4]];
		}
		function getKeyframes() {
			return keyframes;
		}
		function getSmoothKeys() {
			return keyframeBeziers;
		}
		function getListeners () {
			return objectListeners;
		}
		function getName() {
			return name;
		}
		function getParameter(param) {
			return currentState[param];
		}
		function getParameters() {
			return currentState;
		}
		function getParent () {
			if (manager !== undefined && manager.parent[name] !== undefined) return manager.parent[name];
			else return null;
		}
		function getPoints() {
			return points;
		}
		function getScene() {
			return tfscene;
		}
		function getScripts() {
			return scripts;
		}
		function getTimeline() {
			return tftimeline;
		}
		function getType() {
			return objectType;
		}
		function playAudio() {
			if (typeof(actualAudio) != "undefined" && typeof(actualAudio.play) != "undefined") actualAudio.play();
		}
		function removePoint(pointNum) {
			if (points[pointNum][0] == boundingbox[1] || points[pointNum][0] == boundingbox[3] || points[pointNum][1] == boundingbox[2] || points[pointNum][1] == boundingbox[4]) {
				points.splice(pointNum, 1);
				boundingbox[1] = 1000000000;
				boundingbox[2] = 1000000000;
				boundingbox[3] = -1000000000;
				boundingbox[4] = -1000000000;
				var i = 0;
				while (i < points.length) {
					// keep our bounding box up to date
					if (points[i][0] < boundingbox[1]) boundingbox[1] = points[i][0];
					if (points[i][0] > boundingbox[3]) boundingbox[3] = points[i][0];
					if (points[i][1] < boundingbox[2]) boundingbox[2] = points[i][1];
					if (points[i][1] > boundingbox[4]) boundingbox[4] = points[i][1];
					i++;
				}
			} else {
				if (points[pointNum][0] < boundingbox[1]) boundingbox[1] = points[pointNum][0];
				if (points[pointNum][0] > boundingbox[3]) boundingbox[3] = points[pointNum][0];
				if (points[pointNum][1] < boundingbox[2]) boundingbox[2] = points[pointNum][1];
				if (points[pointNum][1] > boundingbox[4]) boundingbox[4] = points[pointNum][1];
				points.splice(pointNum, 1);
			}
		}
		function removeKeyframe(frame) {
			if (typeof(keyframeBeziers[frame]) != "undefined") delete keyframeBeziers[frame];
			delete keyframes[frame];
		}
		function setName(newname) {
			if (newname != name) {
				if (typeof(getObject(newname)) != "object") {
					manager.name[newname] = manager.name[name];
					manager.parent[newname] = manager.parent[name];
					delete manager.name[name];
					delete manager.parent[name];
					name = newname;
				} else return false;
			}
			return true;
		}
		// private function?
		function getNormal(points) {
			var vec1 = [
				points[1][0]-points[0][0],
				points[1][1]-points[0][1],
				points[1][2]-points[0][2]
			];
			var vec2 = [
				points[2][0]-points[0][0],
				points[2][1]-points[0][1],
				points[2][2]-points[0][2]
			];
			var a = vec1[1]*vec2[2] - vec2[1]*vec1[2];
			var b = vec1[2]*vec2[0] - vec2[2]*vec1[0];
			var c = vec1[0]*vec2[1] - vec2[0]*vec1[1];
			// normalize
			var len = Math.pow(Math.pow(a,2)+Math.pow(b,2)+Math.pow(c,2), .5);
			a /= len; b /= len; c /= len;
			return [a, b, c];
		}
		function getSmoothNormals(normalsInvolved, normalStack) {
			var nvertices = [];
			var ninv = {};
			for (var n in normalsInvolved) {
				var norms = normalsInvolved[n];
				var a=0; var b=0; var c=0;
				for (var i=0; i<norms.length; i++) {
					/*console.log(norms.length);
					console.log(":: " + norms[i][0] + "," + norms[i][1] + "," + norms[i][2]);*/
					a += norms[i][0];
					b += norms[i][1];
					c += norms[i][2];
				}
				var len = Math.pow(Math.pow(a,2)+Math.pow(b,2)+Math.pow(c,2), .5);
				a /= len;
				b /= len;
				c /= len;
				ninv[n] = [a,b,c];
				//console.log(n + ": " + ninv[n]);
			}
			for (var i=0; i<normalStack.length; i++) {
				var nu = normalStack[i];
				nvertices.push(ninv[nu][0]);
				nvertices.push(ninv[nu][1]);
				nvertices.push(ninv[nu][2]);
			}
			return nvertices;
		}
		function setParameter(param, givenvalue, dochildren) {
			var pty = parameterType(param);
			var canvas = layers[""].getCanvases()[0].getContext();
			if (typeof(pty) != "undefined") {
				if (pty == "integer" || pty == "float") givenvalue = parseFloat(givenvalue);
				if (param == "image") {
					if (!assets.images[givenvalue]) {
						preload(refreshCanvas, [[givenvalue]]);
					}
				} else if (param == "object") {
				if (getParameter("object") != givenvalue) {	
					var trangulate = false;
					if (!assets.objs[givenvalue]) {
						preload(null, [[givenvalue]]);
					}
					
					// raw vertices
					var verts = [];
					// vertices organized to draw faces
					var vertices = [];
					
					var tverts = [];
					var tvertices = [];
					
					// hard
					var hnvertices = [];
					// soft
					var snvertices = [];
					
					// this stores an array of normals to average for each point
					var normalsInvolved = {};
					// this holds the order in which to push the normals to nvertices (likely multiple times for each)
					var normalStack = [];
					
					var lines = assets.objs[givenvalue].split("\n");
					for (var l in lines) {
						var eles = lines[l].split(" ");
						if (eles[0] == "v") {
							verts.push(parseFloat(eles[1]));
							verts.push(parseFloat(eles[2]));
							verts.push(parseFloat(eles[3]));
						} else if (eles[0] == "vt") {
							tverts.push(parseFloat(eles[1]));
							tverts.push(parseFloat(eles[2]));
						} else if (eles[0] == "f") {
							// get the vertice numbers to push to normalStack
							var vertNums = [];
							// the 3 point involved in a 3x3 matrix, used to get the normal
							var points = [];
							for (var i=1; i<=3; i++) {
								points.push([]);
								var portions = eles[i].split("/");
								vertNums.push(portions[0]);
								var e = (portions[0]-1)*3;
								for (var j=0; j<3; j++) {
									vertices.push(verts[e+j]);
									points[points.length-1].push(verts[e+j]);
								}
								// if texture coords...
								if (portions[1]) {
									var e = (portions[1]-1)*2;
									for (var j=0; j<2; j++) {
										tvertices.push(tverts[e+j]);
									}
								} else {
									tvertices.push(0);
									tvertices.push(0);
								}								
							}
							var norm = getNormal(points);
							// store a copy of the normal for each point it affects
							// if we're using the smoothed version
							for (var i=0; i<vertNums.length; i++) {
								if (normalsInvolved[vertNums[i]] === undefined) {
									normalsInvolved[vertNums[i]] = [];
								}
								normalsInvolved[vertNums[i]].push(norm);
								normalStack.push(vertNums[i]);
							}
							// and push three copies (one for each vert)
							// the is for hard normals
							for (var j=0; j<3; j++) {
								for (var i=0; i<norm.length; i++) {
									hnvertices.push(norm[i]);
								}
							}
						}
					}
					snvertices = getSmoothNormals(normalsInvolved, normalStack);
				
					// create the needed buffers using the data from the obj
					for (var b in bufferNames) {
						var buf = gl.createBuffer();
						gl.bindBuffer(gl.ARRAY_BUFFER, buf);
						var verts;
						if (b == "aPosition") verts = vertices;
						else if (b == "aNormal") verts = snvertices;
						else if (b == "aUV") verts = tvertices;
						gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
						buf.itemSize = bufferNames[b];
						buf.numItems = verts.length/bufferNames[b];
						buffers[b] = buf;
					}
					calculatedNormals["smooth"] = buffers["aNormal"];
					var buf = gl.createBuffer();
					gl.bindBuffer(gl.ARRAY_BUFFER, buf);
					gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(hnvertices), gl.STATIC_DRAW);
					buf.itemSize = 3;
					buf.numItems = hnvertices.length/3;
					calculatedNormals["hard"] = buf;
				}
				} else if (param == "normals") {
					if (calculatedNormals[givenvalue]) buffers.aNormal = calculatedNormals[givenvalue];
				} else if (param == "scene") {
					if (typeof(assets.scenes[givenvalue]) == "undefined") _loadSource(givenvalue);
				} else if (param == "audio") {
					if (givenvalue in assets.audio) {
						actualAudio = new Audio(assets.audio[givenvalue].src);
						actualAudio.load();
					} else {
						// we don't have to create a separate unique
						// instance for this object since we know noone
						// is using the copy stored in assets.audio yet
						preload(null, [[givenvalue]]);
						actualAudio = assets.audio[givenvalue];
					}
					if (actualAudio) actualAudio.loop = currentState["loopaudio"];
				} else if (param == "loopaudio") {
					if (typeof(actualAudio) != "undefined") actualAudio.loop = givenvalue;
				} else if (param == "text") {
					canvas.font = currentState.font;
					var wantedLines = givenvalue.split("<br>");
					var maxwidth = 0;
					for (var w in wantedLines) {
						var wid = canvas.measureText(wantedLines[w]).width;
						if (wid > maxwidth) maxwidth = wid;
					}
					boundingbox[1] = 0;
					boundingbox[2] = 0;
					boundingbox[3] = maxwidth;
					boundingbox[4] = wantedLines.length*canvas.measureText("e").width*2;
				} else if (param == "font") {
					canvas.font = givenvalue;
					var wantedLines = currentState.text.split("<br>");
					var maxwidth = 0;
					for (var w in wantedLines) {
						var wid = canvas.measureText(wantedLines[w]).width;
						if (wid > maxwidth) maxwidth = wid;
					}
					boundingbox[1] = 0;
					boundingbox[2] = 0;
					boundingbox[3] = maxwidth;
					boundingbox[4] = wantedLines.length*canvas.measureText("e").width*2;
				} else if (param == "layer" && givenvalue != "" && typeof(layers[givenvalue]) == "undefined") {
					new TFLayer(givenvalue);
				}
				currentState[param] = givenvalue;
			}
		}
		function setParameters(params, dochildren) {
			var i = 0;
			while (i < params.length) {
				setParameter(params[i][0], params[i][1], dochildren)
				i++;
			}
		}
		function setParent (nParent) {
			var myMatrix = getTransformationMatrix();
			var parentMatrix = nParent.getTransformationMatrix();
			var myRotation = getTotalRotationAndScale();
			var parentRotation = nParent.getTotalRotationAndScale();
			//console.log(myMatrix);
			//console.log(parentMatrix);
			if (name in manager.parent) manager.parent[name]._removeChild(name);
			nParent._addChild(thisObj);
			manager.parent[getName()] = nParent;
			
			/*console.log(myMatrix);
			console.log(parentMatrix);*/
			var invParent = mat4.create();
			mat4.inverse(parentMatrix, invParent);
			//console.log(invParent);
			var newMat = mat4.create();
			mat4.multiply(invParent, myMatrix, newMat);
			var vec = [0,0,0];//[parseFloat(getParameter("x")),parseFloat(getParameter("y")),0];
			mat4.multiplyVec3(newMat, vec);
			//console.log(newMat);
			if (getParameter("z") !== undefined) {
				setParameters([["x", vec[0]], ["y", vec[1]], ["z", vec[2]]]);
			} else {
				setParameters([["x", vec[0]], ["y", vec[1]]]);
			}
			
			if (currentState.rotation) setParameter("rotation", myRotation.rotation-parentRotation.rotation);
			if (currentState.scale) setParameter("scale", myRotation.scale/parentRotation.scale);
		}
		function setPoint (pointNum, pointPos) {
			if (points[pointNum][0] == boundingbox[1] || points[pointNum][0] == boundingbox[3] || points[pointNum][1] == boundingbox[2] || points[pointNum][1] == boundingbox[4]) {
				points[pointNum] = pointPos;
				boundingbox[1] = 1000000000;
				boundingbox[2] = 1000000000;
				boundingbox[3] = -1000000000;
				boundingbox[4] = -1000000000;
				var i = 0;
				while (i < points.length) {
					// keep our bounding box up to date
					if (points[i][0] < boundingbox[1]) boundingbox[1] = points[i][0];
					if (points[i][0] > boundingbox[3]) boundingbox[3] = points[i][0];
					if (points[i][1] < boundingbox[2]) boundingbox[2] = points[i][1];
					if (points[i][1] > boundingbox[4]) boundingbox[4] = points[i][1];
					i++;
				}
			} else {
				if (pointPos[0] < boundingbox[1]) boundingbox[1] = pointPos[0];
				if (pointPos[0] > boundingbox[3]) boundingbox[3] = pointPos[0];
				if (pointPos[1] < boundingbox[2]) boundingbox[2] = pointPos[1];
				if (pointPos[1] > boundingbox[4]) boundingbox[4] = pointPos[1];
				points[pointNum] = pointPos;
			}
		}
		function stopAudio() {
			if (typeof(actualAudio) != "undefined" && typeof(actualAudio.stop) != "undefined") actualAudio.pause();
		}
		function setVolume(newVolume) {
			if (typeof(actualAudio) != "undefined" && typeof(actualAudio.volume) != "undefined") actualAudio.volume = newVolume;
		}
		function translate(amount) {
			amount[0] = parseFloat(amount[0]);
			amount[1] = parseFloat(amount[1]);
			currentState.x += amount[0];
			currentState.y += amount[1];
		}
		function localTranslate(amount) {
			var ourTranslation = [currentState.x, currentState.y, currentState.z];
			var mat = mat4.create();
			mat4.identity(mat);
			mat4.translate(mat, ourTranslation);
			mat4.rotate(mat, currentState.xrotation, [1,0,0]);
			mat4.rotate(mat, currentState.yrotation, [0,1,0]);
			mat4.rotate(mat, currentState.zrotation, [0,0,1]);
			//console.log(mat);
			mat4.translate(mat, amount);
			var pos = [0,0,0];
			mat4.multiplyVec3(mat, pos);
			currentState.x = pos[0];
			currentState.y = pos[1];
			currentState.z = pos[2];
		}
		function rotate(amount) {
			amount = parseFloat(amount);
			currentState.rotation += amount;
		}
		function scale(amount) {
			amount = parseFloat(amount);
			currentState.scale *= amount;
		}
		function movePivot (newPos) {
			var i = 0;
			var xdiff = getParameter("x")-(newPos[0]);
			var ydiff = getParameter("y")-(newPos[1]);
						
			var xdelt = (xdiff*Math.cos(currentState.rotation)) + (ydiff*Math.sin(currentState.rotation));
			var ydelt = -(xdiff*Math.sin(currentState.rotation)) + (ydiff*Math.cos(currentState.rotation));
			xdelt /= getParameter("scale");
			ydelt /= getParameter("scale");
			while (i < points.length) {
				points[i][0] += xdelt;
				points[i][1] += ydelt;
				i++;
			}
			boundingbox[1] += xdelt;
			boundingbox[3] += xdelt;
			boundingbox[2] += ydelt;
			boundingbox[4] += ydelt;
			currentState.x = newPos[0];
			currentState.y = newPos[1];
			
			i=0;
			while (i < children.length) {
				children[i].translate([xdiff, ydiff]);
				i++;
			}
		}
		function setExtras (boundbox, sPivot, sPoints, sControls) {
			boundingbox[0] = boundbox;
			if (typeof(sPivot) != "undefined") showPivot = sPivot;
			if (typeof(sPoints) != "undefined") showPoints = sPoints;
			if (typeof(sControls) != "undefined") showControls = sControls;
		}
		function getExtras () {
			return { bounds: boundingbox, pivot: showPivot, points: showPoints, controls: showControls };
		}
		function pointInside(gPoint) {
			// we haven't added code to handle these yet...
			var tp = getType();
			
			// if the objects pivot isn't at x=0 and y=0, we must compensate
			// we change the given point rather than all of the objects points for efficiency
			var givenPoint = [gPoint[0], gPoint[1], 0];
			var mat = mat4.create();
			mat4.inverse(getTransformationMatrix(), mat);
			mat4.multiplyVec3(mat, givenPoint);
			
			if (tp == "circle") {
				if (getDistance(givenPoint, getPoints()[0]) <= getParameter("radius")) return true;
				else return false;
			} else {
				
				var total = 0.0;
				var lastang = 0.0;
				
				// check if point is inside rectangular bounds
				// if it isn't we don't have to do anything else
				// will increase to 2 if it is, otherwise 0
				var inbounds = 1;
				
				var i = 0;
				// counts the number of times we've used the first point (we want to start and end on the first point)
				var finished = 0;
				while (finished != 2) {
					var xoff;
					var yoff;
					var curang = 0.0;
				
					if (inbounds == 1) {
						// as bounding box is stored differently we create a special case
						// for each one...
						if (i == 0 || i == 4) {
							xoff = boundingbox[1] - givenPoint[0];
							yoff = boundingbox[2] - givenPoint[1];
						} else if (i == 1) {
							xoff = boundingbox[1] - givenPoint[0];
							yoff = boundingbox[4] - givenPoint[1];
						} else if (i == 2) {
							xoff = boundingbox[3] - givenPoint[0];
							yoff = boundingbox[4] - givenPoint[1];
						} else if (i == 3) {
							xoff = boundingbox[3] - givenPoint[0];
							yoff = boundingbox[2] - givenPoint[1];
						} else if (total >= -3 && total <= 3) {
							return false;
						} else {
							if (getType() == "rectangle" || getType() == "text") return true;
							i = 0;
							total = 0.0;
							inbounds = 2;
							finished = 0;
							lastang = 0.0;
						}
					} 
					
					if (inbounds == 2) {
						xoff = points[i][0] - givenPoint[0];
						yoff = points[i][1] - givenPoint[1];
						// this ensures we never divide by 0
						// the error is not substantial
						if (xoff == 0) xoff++;
						if (yoff == 0) yoff++; 
					}
					
					if ((xoff < 0 && yoff < 0) || (xoff > 0 && yoff > 0)) curang = Math.abs(Math.atan(yoff/xoff));
					else curang = Math.abs(Math.atan(xoff/yoff));
					
					// get an angle from 0 to 359.9 degrees instead of from 0 to 89.9 (in radians of course)
					if (xoff < 0 && yoff < 0) curang += Math.PI;
					if (xoff < 0 && yoff > 0) curang += (Math.PI/2);
					if (xoff > 0 && yoff < 0) curang += (1.5*Math.PI);
					
					// we don't want to add anything to the total angle if this is the first point (as we're only interested in the delta)
					if (finished != 0) {
						// we want to add the smallest possible angle
						// these cases are necessary so that points at 10 degrees and 350 degrees don't get an angle of 340, but insead 20
						var di = curang - lastang;
						var dj = curang - (lastang + (2*Math.PI));
						var dk = curang - (lastang - (2*Math.PI));
					
						var ia = Math.abs(di);
						var ja = Math.abs(dj);
						var ka = Math.abs(dk);
					
						if (ia < ja && ia < ka) total += di;
						else if (ja < ia && ja < ka) total += dj;
						else total += dk;
					}
					
					// remember this angle so that we can get the next delta
					lastang = curang;
					if (i == 0) finished++;
					i++;
					// if we've gone through all the points go back and use the first point once more
					if (i == points.length && inbounds == 2) i = 0;
				}
				
				// if the total is a multiple of two pi we're inside, if it's zero we're outside
				// we use 1 instead of !=0 just to give room on both sides for rounding errors
				if (total >= 3.0 || total <= -3.0) {
					return true;
				}
				else return false;
			}
		}
		function setFrame(newframe) {
			tftimeline.setFrame(newframe);
			var pars = getParameters();
			for (var p in pars) {
				var val = tftimeline.getParameter(p);
				if (val !== null && val !== undefined) {
					setParameter(p, val);
				}
			}
		}
		function deleteObject() {
			if (name in manager.parent) manager.parent[name]._removeChild(name);
			else manager.root._removeChild(name);
			
			delete manager.name[name];
			delete manager.parent[name];
			
			while (children.length > 0) {
				children[0].deleteObject();
			}
		}
		function duplicateObject() {
			var newname = thisObj.getName();
			newname += " copy";
			while (typeof(manager.name[newname]) == "object") {
				newname += " copy";
			}
			var newobj = new TFObject(thisObj.getType(), newname);
			for (var i=0; i<points.length; i++) {
				newobj.addPoint([points[i][0], points[i][1]]);
			}
			for (var par in currentState) {
				newobj.setParameter(par, currentState[par]);
			}
			// add the scripts...
			var scrs = thisObj.getScripts();
			for (var sc in scrs) {
				var nm = scrs[sc].getName();
				var run = scrs[sc].isRunning();
				var sh = scrs[sc].getShared();
				new newobj.TFScript(nm, run, sh);
			}
			var children = thisObj.getChildren();
			for (var child in children) {
				var newone = children[child].duplicateObject();
				// now we add to the newly duplicated object
				newone.setParent(newobj);
			}
			return newobj;
		}
		function _addChild(newChild) {
			children.push(newChild);
		}
		function getChildren() {
			return children;
		}
		function _removeChild(childName) {
			var i = 0;
			while (i < children.length) {
				if (children[i].getName() == childName) {
					children.splice(i,1);
					return;
				}
				i++;
			}
		}
		function removeChildren() {
			children = new Array();
		}
		function changeDepth(setType, setParameter) {
			manager.parent[name].childDepth(name, setType, setParameter);
		}
		function childDepth (childName, setType, setParameter) {
			var h = 0;
			var movingObject;
			var pos;
			while (h < children.length) {
				if (children[h].getName() == childName) {
					movingObject = children[h];
					pos = h;
				}
				h++;
			}
			if (setType == "moveBy" && pos+setParameter < children.length && pos+setParameter >= 0) {
				var i = 0;
				while (i < setParameter) {
					children[pos+i] = children[pos+i+1];
					i++;
				}
				while (i > setParameter) {
					children[pos+i] = children[pos+i-1];
					i--;
				}
				children[pos+i] = movingObject;
			}
		}
		// private members
		function nextFrame() {
			setFrame(currentFrame+1);
		}
		
		// managing of root objects is done at the TFScene level
		// other objects are assigned to a scene here
		if (obtype != "root") {
			manager.root._addChild(this);
			manager.parent[name] = manager.root;
			manager.name[name] = this;
		}
		
		// this is to ensure the correct bounds
		if (currentState.text) setParameter("text", currentState.text);
		
		if (typeof(someparam) != "undefined" && someparam != null) setParameters(someparam);
		
		return this;
	}
	function getObject(name) {
		return assets.activeScene.getManagers().name[name];
	}
	function getObjects() {
		return assets.activeScene.getManagers().root.getChildren();
	}
	function getParameters() {
		// resolution = pixels of canvas...
		// width and height = prefered width and height
		var dimensions = content.tfratio.getDimensions();
		var can = layers[""].getCanvases()[0].getCanvas();
		var col = content.container.style.background;
		if (col == "") col = "white";
		return {width: dimensions[0], height: dimensions[1], xresolution: can.width, yresolution: can.height, colour: col, ambientlight: "rgb("+parseInt(ambientLight[0]*255)+","+parseInt(ambientLight[1]*255)+","+parseInt(ambientLight[2]*255)+")"};
	}
	function refreshCanvas(lname, tObjs, thescene) {
		var firs = false;
		if (typeof(tObjs) == "undefined") {
			// if we had to adjust dimensions everything needs to be redrawn (even if the user defined a single layer)
			if (content.tfratio.check() == true) lname = undefined;
			var dime = getParameters();
			var zoom = content.tfratio.getZoom();
			
			// setup all layers or the defined layer (depending whether a specific layer was defined)
			if (typeof(lname) == "undefined") {
				for (var mc in layers) {
					if (mc != "opengl") {
					var cont = layers[mc].getCanvases()[0].getContext();
					cont.clearRect(0,0,dime.width,dime.height);
					}
				}
			} else {
				if (lname != "opengl") layers[lname].getCanvases()[0].getContext().clearRect(0,0,dime.width,dime.height);
			}
			// now do the opengl layer if no layer was defined or opengl was defined...
			if (gl && (lname === undefined || lname === "opengl")) {
				var cam = assets.activeScene.getManagers().cameraStack[0];
				
				// now clear and setup the gl layer
				gl.clearColor(0, 0.0, 0.0, 0.0);
				// set view and clear
				gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
				// zero out these matrices
				mat4.identity(matrices.uCameraMatrix);
				mat4.identity(matrices.uPositionMatrix);
				
				// send uAmbientLight
				gl.uniform3f(
					shaderProgram.uAmbientColour,
					ambientLight[0],
					ambientLight[1],
					ambientLight[2]
				);
				var lights = assets.activeScene.getManagers().lights;
				// send uLightDirection
				// +x +y -z
				var larr = [];
				var lcol = [];
				for (var i=0; i<16 && i<lights.length; i++) {
					var p = lights[i].getParameters();
					var c = p.colour;
					c = c.split("(");
					c = c[1].split(")");
					c = c[0].split(",");
					var lDir = [p.x, p.y, -p.z];
					var nDir = vec3.create();
					vec3.normalize(lDir, nDir);
					vec3.scale(nDir, -1);
					larr = larr.concat([nDir[0], nDir[1], nDir[2]]);
					lcol = lcol.concat([parseFloat(c[0])/255, parseFloat(c[1])/255, parseFloat(c[2])/255]);
				}
				/*console.log(larr);
				console.log(lcol);*/
				//console.log("a");
				gl.uniform3fv(shaderProgram.uLightDirection, larr);
				// send uLightColour
				gl.uniform3fv(shaderProgram.uLightColour, lcol);
				
				// send uPerspectiveMatrix
				gl.uniformMatrix4fv(shaderProgram.uPerspectiveMatrix, false, matrices.uPerspectiveMatrix);
				// send uCameraMatrix
				if (cam) {
					var p = cam.getParameters();
					mat4.rotate(matrices.uCameraMatrix, p.xrotation, [1, 0, 0]);
					mat4.rotate(matrices.uCameraMatrix, p.yrotation, [0, 1, 0]);
					mat4.rotate(matrices.uCameraMatrix, p.zrotation, [0, 0, 1]);
					mat4.translate(matrices.uCameraMatrix, [-p.x, -p.y, p.z]);
				}
				gl.uniformMatrix4fv(shaderProgram.uCameraMatrix, false, matrices.uCameraMatrix);
			}
			
			tObjs = assets.activeScene.getManagers().root.getChildren();
			firs = true;
		}
		// display each object
		for (var i = 0; i < tObjs.length; i++) {
			// save every layers state
			var pars = tObjs[i].getParameters();
			for (var l in layers) {
				if (l != "opengl") {
					var can = layers[l].getCanvases()[0].getContext();
					can.save();
					can.translate(pars.x, pars.y);
					if (pars.rotation !== undefined) can.rotate(pars.rotation);
					else if (pars.zrotation !== undefined) can.rotate(pars.rotation)
					if (pars.scale) can.scale(pars.scale, pars.scale);
				}
			}
			if (gl) {
				saveMatrix();
				if (pars.z !== undefined) mat4.translate(matrices.uPositionMatrix, [pars.x, pars.y, -pars.z]);
				else mat4.translate(matrices.uPositionMatrix, [pars.x, pars.y, 0]);
				if (pars.xrotation !== undefined) {
					mat4.rotate(matrices.uPositionMatrix, pars.xrotation, [1, 0, 0]);
					mat4.rotate(matrices.uPositionMatrix, pars.yrotation, [0, 1, 0]);
					mat4.rotate(matrices.uPositionMatrix, pars.zrotation, [0, 0, 1]);
				} else if (pars.rotation !== undefined) {
					mat4.rotate(matrices.uPositionMatrix, pars.rotation, [0, 0, 1]);
				}
				if (pars.scale !== undefined) {
					var sc = pars.scale;
					mat4.scale(matrices.uPositionMatrix, [sc, sc, sc]);
				}
			}
			
			
			if (tObjs[i].getParameter("z") === undefined) {
				var objLayer = tObjs[i].getParameter("layer");
				if (typeof(lname) == "undefined" || objLayer == lname) {
					var objCanvas = layers[objLayer].getCanvases()[0].getContext();
					
					objCanvas.save();
					var composite = tObjs[i].getParameter("composite");
					if (composite == "mask") {
						objCanvas.globalCompositeOperation = "source-atop";
					} else if (composite == "difference") {
						objCanvas.globalCompositeOperation = "destination-out";
					} else if (composite == "add") {
						objCanvas.globalCompositeOperation = "lighter";
					} else if (composite == "subtract") {
						objCanvas.globalCompositeOperation = "darker";
					}
					
					var fc = tObjs[i].getParameter("fillcolour");
					var lc = tObjs[i].getParameter("linecolour");
					if (typeof(fc) != "object") objCanvas.fillStyle = fc;
					else {
						objCanvas.fillStyle = fc.gradient;
					}
					if (typeof(lc) != "object") objCanvas.strokeStyle = lc;
					else objCanvas.strokeStyle = lc.gradient;
					
					if (tObjs[i].getParameter("scalewidth")) objCanvas.lineWidth = tObjs[i].getParameter("width");
					else if (tObjs[i].getParameter("width")) objCanvas.lineWidth = tObjs[i].getParameter("width")/tObjs[i].getParameter("scale");
					
					if (tObjs[i].getParameter("shadow")) {
						objCanvas.shadowColor = "black";
						objCanvas.shadowOffsetX = tObjs[i].getParameter("shadowx");
						objCanvas.shadowOffsetY = tObjs[i].getParameter("shadowy");
						objCanvas.shadowBlur = tObjs[i].getParameter("shadowblur");
					}
					
					if (tObjs[i].getParameter("visible") && tObjs[i].getType() != "pointer") {
						objCanvas.beginPath();
						if (tObjs[i].getType() == "line") {
							objCanvas.lineCap = 'round';
							for (var j = 0; j < tObjs[i].getPoints().length; j++) {
								if (j == 0) {
									objCanvas.moveTo(tObjs[i].getPoints()[0][0], tObjs[i].getPoints()[0][1]);
								}
								
								if (tObjs[i].getParameter("curves") == "none") {
									var xp = tObjs[i].getPoints()[j][0];
									var yp = tObjs[i].getPoints()[j][1];
									objCanvas.lineTo(xp, yp);
								} else if (typeof(tObjs[i].getPoints()[j+1]) != "undefined" && tObjs[i].getParameter("curves") == "bezier") {
									var pone = tObjs[i].getPoints()[j];
									var ptwo = tObjs[i].getPoints()[j+1];
									if (typeof(pone[2]) == "undefined") {
										pone.push(0);
										pone.push(0);
									}
									if (typeof(ptwo[2]) == "undefined") {
										ptwo.push(0);
										ptwo.push(0);
									}
									//alert(ptwo[3]);
									var onex = Math.cos(pone[2])*pone[3];
									var oney = Math.sin(pone[2])*pone[3];
									var twox = -Math.cos(ptwo[2])*ptwo[3];
									var twoy = -Math.sin(ptwo[2])*ptwo[3];
									//alert(onex + " " + oney + " " + twox + " " + twoy);
									objCanvas.bezierCurveTo(pone[0]+onex, pone[1]+oney, ptwo[0]+twox, ptwo[1]+twoy, ptwo[0], ptwo[1]);
									/*var ntx = tObjs[i].getPoints()[j][0] - ((tObjs[i].getPoints()[j][0] - tObjs[i].getPoints()[j+1][0])/2);
									var nty = tObjs[i].getPoints()[j][1] - ((tObjs[i].getPoints()[j][1] - tObjs[i].getPoints()[j+1][1])/2);
									objCanvas.quadraticCurveTo(tObjs[i].getPoints()[j][0], tObjs[i].getPoints()[j][1], ntx, nty);*/
								} else if (typeof(tObjs[i].getPoints()[j+1]) != "undefined") {
									// quick curves
									var ntx = tObjs[i].getPoints()[j][0] - ((tObjs[i].getPoints()[j][0] - tObjs[i].getPoints()[j+1][0])/2);
									var nty = tObjs[i].getPoints()[j][1] - ((tObjs[i].getPoints()[j][1] - tObjs[i].getPoints()[j+1][1])/2);
									objCanvas.quadraticCurveTo(tObjs[i].getPoints()[j][0], tObjs[i].getPoints()[j][1], ntx, nty);
								} else {
									objCanvas.lineTo(tObjs[i].getPoints()[j][0], tObjs[i].getPoints()[j][1]);
								}
							}
							if (tObjs[i].getParameter("closed")) objCanvas.closePath();
							if (tObjs[i].getParameter("fillcolour") != "none") objCanvas.fill();
							objCanvas.shadowColor = "transparent";
							if (tObjs[i].getParameter("linecolour") != "none") objCanvas.stroke();
						} else if (tObjs[i].getType() == "rectangle") {
							if (tObjs[i].getParameter("fillcolour") != "none") {
								var pnts = tObjs[i].getPoints();
								objCanvas.fillRect(pnts[0][0], pnts[0][1], pnts[1][0]-pnts[0][0], pnts[1][1]-pnts[0][1]);
							}
							objCanvas.shadowColor = "transparent";
							if (tObjs[i].getParameter("linecolour") != "none") {
								var pnts = tObjs[i].getPoints();
								objCanvas.strokeRect(pnts[0][0], pnts[0][1], pnts[1][0]-pnts[0][0], pnts[1][1]-pnts[0][1]);
							}
						} else if (tObjs[i].getType() == "circle") {
							objCanvas.beginPath();
							var cnt = tObjs[i].getPoints()[0];
							objCanvas.arc(cnt[0], cnt[1], tObjs[i].getParameter("radius"), 0, Math.PI*1.99, false);
							objCanvas.closePath();
							if (tObjs[i].getParameter("fillcolour") != "none") objCanvas.fill();
							objCanvas.shadowColor = "transparent";
							if (tObjs[i].getParameter("linecolour") != "none") objCanvas.stroke();
						} else if (tObjs[i].getType() == "text") {
							objCanvas.textBaseline = "top";
							objCanvas.font = tObjs[i].getParameter("font");
							var wantedText = tObjs[i].getParameter("text");
							// lets add line break support
							var voffset = objCanvas.measureText("e").width*2;
							var wantedLines = wantedText.split("<br>");
							var k = 0;
							while (k < wantedLines.length) {
								if (tObjs[i].getParameter("fillcolour") != "none") objCanvas.fillText(wantedLines[k], 0, k*voffset);
								objCanvas.shadowColor = "transparent";
								if (tObjs[i].getParameter("linecolour") != "none") objCanvas.strokeText(wantedLines[k], 0, k*voffset);
								k++;
							}
						} else if (tObjs[i].getType() == "image") {
							var ti = assets.images[tObjs[i].getParameter("image")];
							if (typeof(ti) != "undefined" && ti.complete) {
								var pnt = tObjs[i].getPoints()[0];
								objCanvas.drawImage(ti, pnt[0], pnt[1]);
							}
						}
					} else if (tObjs[i].getType() == "pointer") {
						if (typeof(assets.scenes[tObjs[i].getParameter("scene")]) != "undefined") refreshCanvas(lname, assets.scenes[tObjs[i].getParameter("scene")].getManagers().root.getChildren(), assets.scenes[tObjs[i].getParameter("scene")]);
					}
					
					objCanvas.restore();
					
					var bnds = tObjs[i].getExtras().bounds;
					objCanvas.lineWidth = 1;
					if (bnds[0]) {
						objCanvas.strokeStyle = "rgb(200,200,200)";
						objCanvas.strokeRect(bnds[1]-10, bnds[2]-10, bnds[3]-bnds[1]+20, bnds[4]-bnds[2]+20);
					}
					
					if (tObjs[i].getExtras().pivot) {
						objCanvas.fillStyle = "black";
						objCanvas.beginPath();
						var cnt = tObjs[i].getPoints()[0];
						objCanvas.arc(0, 0, 3/tObjs[i].getParameter("scale"), 0, Math.PI*1.99, false);
						objCanvas.closePath();
						objCanvas.fill();
						/*var po = tObjs[i].getParameters();
						objCanvas.fillRect(-2, -2,4,4);*/
					}
					
					if (tObjs[i].getExtras().points && !tObjs[i].getExtras().controls) {
						// show points as well (for now)
						var pnts = tObjs[i].getPoints();
						for (var p in pnts) {
							objCanvas.fillStyle = "blue";
							objCanvas.fillRect(pnts[p][0]-1.5, pnts[p][1]-1.5,3,3);
						}
					}
					
					if (tObjs[i].getExtras().controls) {
						// show points as well (for now)
						var pnts = tObjs[i].getPoints();
						for (var p in pnts) {
							if (typeof(pnts[p][3]) != "undefined") {
								var onex = Math.cos(pnts[p][2])*pnts[p][3];
								var oney = Math.sin(pnts[p][2])*pnts[p][3];
								objCanvas.fillStyle = "red";
								objCanvas.beginPath();
								objCanvas.moveTo(pnts[p][0]+onex, pnts[p][1]+oney);
								objCanvas.lineTo(pnts[p][0]-onex, pnts[p][1]-oney);
								objCanvas.stroke();
								objCanvas.closePath();
								objCanvas.fillRect(pnts[p][0]+onex-1.5, pnts[p][1]+oney-1.5,3,3);
								objCanvas.fillRect(pnts[p][0]-onex-1.5, pnts[p][1]-oney-1.5,3,3);
							}
							objCanvas.fillStyle = "blue";
							objCanvas.fillRect(pnts[p][0]-2, pnts[p][1]-2,4,4);
						}
					}
				}
			} else if (tObjs[i].getBuffers) {
				var glob = tObjs[i];
				
				var buffers = glob.getBuffers();
				for (var b in buffers) {
					gl.bindBuffer(gl.ARRAY_BUFFER, buffers[b]);
					gl.vertexAttribPointer(shaderProgram[b], buffers[b].itemSize, gl.FLOAT, false, 0, 0);
				}
				
				// bind back to the points
				gl.bindBuffer(gl.ARRAY_BUFFER, buffers["aPosition"]);
				// send uPositionMatrix
				gl.uniformMatrix4fv(shaderProgram.uPositionMatrix, false, matrices.uPositionMatrix);
				// send uNormalMatrix
				var normalMatrix = mat3.create();
				mat4.toInverseMat3(matrices.uPositionMatrix, normalMatrix);
				mat3.transpose(normalMatrix);
				gl.uniformMatrix3fv(shaderProgram.uNormalMatrix, false, normalMatrix);
				var textureNum = 0;
				for (var u in userUniforms) {
					var pr = tObjs[i].getShaderProperties()[u];
					if (userUniforms[u] == "bool") {
						gl.uniform1i(
							shaderProgram[u],
							pr
						);
					} else if (userUniforms[u] == "sampler2D") {
						//console.log(glob.getTextures()[u]);
						// send uSample as texture 0
						gl.uniform1i(shaderProgram[u], textureNum);
						gl.activeTexture(gl["TEXTURE" + textureNum]);
						gl.bindTexture(gl.TEXTURE_2D, glob.getTextures()[u]);
						textureNum++;
					} else {
						var parts = pr.split("(");
						var rgba = false;
						if (parts.length > 1) {
							parts = parts[1].split(")");
							rgba = true;
						}
						var vals = parts[0].split(",");
						if (rgba) {
							gl.uniform4f(
								shaderProgram[u],
								parseFloat(vals[0])/255,
								parseFloat(vals[1])/255,
								parseFloat(vals[2])/255,
								parseFloat(vals[3])
							);
						} else {
							gl.uniform4f(
								shaderProgram[u],
								parseFloat(vals[0]),
								parseFloat(vals[1]),
								parseFloat(vals[2]),
								parseFloat(vals[3])
							);
						}
					}
				}
				// draw the bound buffer
				gl.drawArrays(gl.TRIANGLES, 0, buffers["aPosition"].numItems);
			}
			
			refreshCanvas(lname, tObjs[i].getChildren());
			// restore every layers state
			for (var l in layers) {
				if (l != "opengl") {
					var can = layers[l].getCanvases()[0].getContext();
					can.restore();
				}
			}
			if (gl) {
				restoreMatrix();
			}
		}
	}
	function keyPressed(keyName) {
		if (keyName in keys.states && keys.states[keyName]) return true;
		return false;
	}
	function mousePosition () {
		return [pointer.positions[0][0], pointer.positions[0][1]];
	}
	function parameterType (parametername) {
		return parameterTypes[parametername];
	}
	function getDistance (pone, ptwo) {
		return Math.pow( Math.pow(pone[0]-ptwo[0], 2) + Math.pow(pone[1]-ptwo[1], 2) , .5);
	}
	function preload (loadFunc, imgArray, minimum) {
		var i = 0;
		while (i < imgArray.length) {
			var filename = imgArray[i][0];
			if (imgArray[i].length > 1) filename = imgArray[i][1];
			//alert("preload file: "+filename);
			if (filename.indexOf(".js") != -1) {
				// this is a script asset...
				// we wait for scripts to be loaded
				// rather than downloading them in the background
				if (window.XMLHttpRequest) {
					xhttp = new XMLHttpRequest();
				} else {
					xhttp = new ActiveXObject("Microsoft.XMLHTTP");
				}
				//xhttp.overrideMimeType("text/xml");
				xhttp.open("GET", assets.path + filename, false);
				xhttp.send("");
				var thescript = xhttp.responseText;
				assets.scripts[imgArray[i][0]] = thescript;
			} else if (filename.indexOf(".tfc") != -1) {
				// loads and arranges the file using the loadSource function
			} else if (filename.indexOf(".m4a") != -1 || filename.indexOf(".mp3") != -1 || filename.indexOf(".ogg") != -1 || filename.indexOf(".oga") != -1 || filename.indexOf(".wav") != -1 || filename.indexOf(".aac") != -1) {
				// audio is loaded in the background
				if (!!(document.createElement('audio').canPlayType)) {
					assets.audio[filename] = new Audio(assets.path + filename);
					assets.audio[filename].load();
				}
			} else if (filename.indexOf(".obj") != -1) {
				// this is a script asset...
				// we wait for scripts to be loaded
				// rather than downloading them in the background
				if (window.XMLHttpRequest) {
					xhttp = new XMLHttpRequest();
				} else {
					xhttp = new ActiveXObject("Microsoft.XMLHTTP");
				}
				//xhttp.overrideMimeType("text/xml");
				xhttp.open("GET", assets.path + filename, false);
				xhttp.send("");
				var theobj = xhttp.responseText;
				assets.objs[imgArray[i][0]] = theobj;
			} else {
				// images are loaded in the background
				assets.images[imgArray[i][0]] = new Image();
				assets.images[imgArray[i][0]].src = assets.path + filename;
			}
			i++;
		}
		
		if (loadFunc != null) {
			calls = 0;
			loadFunction = loadFunc;
		
			// sometimes we might want to wait at least x seconds
			// even if it finishes loading before that
			// like if we display a logo on a load screen
			if (typeof(minimum) != "undefined") mintime = minimum;
			else mintime = 0.0;
		
			loadingInterval = setInterval(waitingToLoad, 50);
		}
	}
	function GetAssets () {
		return assets.images;
	}
	function parameterType (givenParame) {
		return parameterTypes[givenParame];
	}
	function setFitMode(fittype) {
		content.tfratio.setFitType(fittype);
	}
	/* 
	 * All assets are expected to be at the same
	 * general path (they could be further nested,
	 * but that nesting would be defined in the
	 * specific assets path)
	 */
	function setResourcePath(resourcepath) {
		assets.path = resourcepath;
		//alert(assets.path);
	}
	function setFullscreenPage(fsPage) {
		content.fullscreenpage = assets.path + fsPage;
	}
	function fullscreen() {
		if (content.fullscreenpage != "") window.location = content.fullscreenpage;
	}
	function emptyScene() {
		var obs = getObjects();
		for (var ob in obs) {
			obs[ob].deleteObject();
		}
	}
	// THIS WILL BE RENAMED LOADSCENE IN THE FUTURE!!!
	// WE ARE LEAVING IT AS IS TO REMOVE THE NEED TO MODIFY TF CREATE
	function loadSource(sceneName, executeCode) {
		// does loadSource (if necessary) plus setting the newly loaded TFScene as the active one 
		// loadSource will need to be slightly revamped to be more generic...
		var scenePars;
		if (typeof(assets.scenes[sceneName]) == "undefined") {
			scenePars = _loadSource(sceneName, executeCode);
			// currently scenePars are only set on the first load... that's no good...
			setParameters(scenePars);
		}
		// now that we know it has been loaded lets set it as the active scene...
		assets.activeScene = assets.scenes[sceneName];
		// now refresh the canvas, and the new scene should be displayed...
		refreshCanvas();
	}
	// generic load for both tfc's meant to be used as scenes and assets
	function _loadSource(source, exec, subassetpath, xmlOb, parOb, tfs) {
		var scenePars = [];
		
		var mparent;
		if (typeof(xmlOb) == "undefined") {		
			if (window.XMLHttpRequest) {
				xhttp = new XMLHttpRequest();
			} else {
				xhttp = new ActiveXObject("Microsoft.XMLHTTP");
			}
			xhttp.open("GET", assets.path + source, false);
			if (xhttp.overrideMimeType) xhttp.overrideMimeType("text/xml");
			xhttp.send("");
			var xmlDoc;
			if ((xhttp.overrideMimeType && !window.XMLHttpRequest) || xhttp.overrideMimeType) {
				var xmlDoc = xhttp.responseXML;
			} else {
				// this is essentially a special case for ie8
				// microsoft decided to remove overrideMimeType so,
				// since the tfc extension is non-standard we'll have issues
				// on most servers without this special case...
				xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
				xmlDoc.async="false";
				xmlDoc.loadXML(xhttp.responseText);
			}
					
			mparent = xmlDoc.getElementsByTagName("parent")[0];
			// setup the actual canvas...
// THIS HAS TO BE SAVED TO THE
// NEW TFSCENES SOMEHOW, AND LOADED
// WHEN WE SWITCH ACTIVE SCENES...			
			for(var i=0; i<mparent.attributes.length; i++) {
				var pname = mparent.attributes[i].name;
				var pvalue = mparent.attributes[i].value;
				scenePars.push([pname, pvalue]);
				//setParameters([[pname, pvalue]]);
			}
		} else {
			mparent = xmlOb;
		}
		
		// now lets create a new TFScene to store what we load in...
		// we're loading it as an asset.  It's not up to us whether this
		// asset is used as the main scene...
		var tfScene;
		if (!tfs) {
			tfScene = new TFScene();
			assets.scenes[source] = tfScene;
		} else {
			tfScene = tfs;
		}
		// ALL OBJECTS CREATED MUST HAVE THIS TFSCENE DEFINED....

		for(var i=0; i<mparent.childNodes.length; i++) {
			if (mparent.childNodes[i].nodeName.toUpperCase()=="O") {
				var myobj = mparent.childNodes[i];
				var mno = new TFObject(myobj.getAttribute("type"), myobj.getAttribute("name"), [], tfScene);
//				if (typeof(parOb) != "undefined") mno = new TFObject(myobj.getAttribute("type"), myobj.getAttribute("name"), [], tfScene);
//				else mno = new TFObject(myobj.getAttribute("type"), myobj.getAttribute("name"), [], tfScene);
				
				for(var j=0; j<myobj.attributes.length; j++) {
					if (myobj.attributes[j].name != "type" && myobj.attributes[j].name != "name") {
						var pt = parameterType(myobj.attributes[j].name);
						if (typeof(pt) != "undefined" && pt == "boolean") {
							if (myobj.attributes[j].value == "true") mno.setParameter(myobj.attributes[j].name, true);
							else mno.setParameter(myobj.attributes[j].name, false);
						} else {
							if (myobj.attributes[j].name == "image") {
								if (typeof(GetAssets()[myobj.attributes[j].value]) == "undefined") {
									if (typeof(subassetpath) != "undefined") preload(refreshCanvas, [[myobj.attributes[j].value, subassetpath+myobj.attributes[j].value]]);
									else preload(refreshCanvas, [[myobj.attributes[j].value, myobj.attributes[j].value]]);
								}
								mno.setParameter(myobj.attributes[j].name, myobj.attributes[j].value);
							} else {
								mno.setParameter(myobj.attributes[j].name, myobj.attributes[j].value);
							}
						}
					}
				}
				
				for(var j=0; j<myobj.childNodes.length; j++) {
					// this is for shader properties
					if (myobj.childNodes[j].nodeName.toUpperCase()=="SH") {
						if (mno.setShaderProperty) {
							var attribs = myobj.childNodes[j].attributes;
							for(var k=0; k<attribs.length; k++) {
								if (attribs[k].value == "true") mno.setShaderProperty(attribs[k].name, true);
								else if (attribs[k].value == "false") mno.setShaderProperty(attribs[k].name, false);
								else mno.setShaderProperty(attribs[k].name, attribs[k].value);
							}
						}
					}
					// this is for gradients
					if (myobj.childNodes[j].nodeName.toUpperCase()=="GR") {
						var mypoints = myobj.childNodes[j];
						var par = "";
						var sx = 0; var sy = 0;
						var ex = 0; var ey = 0;
						var typ = "";
						for(var k=0; k<mypoints.attributes.length; k++) {
							if (mypoints.attributes[k].name == "par") par = mypoints.attributes[k].value;
							else if (mypoints.attributes[k].name == "type") typ = mypoints.attributes[k].value;
							else if (mypoints.attributes[k].name == "sx") sx = mypoints.attributes[k].value;
							else if (mypoints.attributes[k].name == "sy") sy = mypoints.attributes[k].value;
							else if (mypoints.attributes[k].name == "ex") ex = mypoints.attributes[k].value;
							else if (mypoints.attributes[k].name == "ey") ey = mypoints.attributes[k].value;
						}
						var grad = new TFGradient([sx,sy], [ex,ey], [], typ);
						for(var k=0; k<mypoints.childNodes.length; k++) {
							if (mypoints.childNodes[k].nodeName.toUpperCase()=="G") {
								var thepoint = mypoints.childNodes[k];
								var pos; var col;
								for(var m=0; m<thepoint.attributes.length; m++) {
									if (thepoint.attributes[m].name == "pos") pos = thepoint.attributes[m].value;
									else if (thepoint.attributes[m].name == "col") col = thepoint.attributes[m].value;
								}
								grad.addColour(pos, col);
							}
						}
						mno.setParameter(par, grad);
					}
					
					if (myobj.childNodes[j].nodeName.toUpperCase()=="PS") {
						var mypoints = myobj.childNodes[j];
						for(var k=0; k<mypoints.childNodes.length; k++) {
							if (mypoints.childNodes[k].nodeName.toUpperCase()=="P") {
								var apoint = mypoints.childNodes[k];
								//if (mno.getType() == "circle") {
								//	mno.setPoint(0, [parseInt(apoint.getAttribute("x")), parseInt(apoint.getAttribute("y"))]);
								//} else {
									if (!apoint.getAttribute("d")) mno.addPoint([parseInt(apoint.getAttribute("x")), parseInt(apoint.getAttribute("y"))]);
									else mno.addPoint([parseInt(apoint.getAttribute("x")), parseInt(apoint.getAttribute("y")), parseFloat(apoint.getAttribute("r")), parseInt(apoint.getAttribute("d"))]);
								//}
							}
						}
					}
					if (myobj.childNodes[j].nodeName.toUpperCase()=="KS") {
						var myprops = myobj.childNodes[j];
						for(var p=0; p<myprops.childNodes.length; p++) {
							if (myprops.childNodes[p].nodeName.toUpperCase()=="PR") {
								var aprop = myprops.childNodes[p];
								var pname = aprop.getAttribute("name");
								for (var k=0; k<aprop.childNodes.length; k++) {
									var akey = aprop.childNodes[k];
									var kvals = {};
									var framenum = akey.getAttribute("f");
									for(var m=0; m<akey.attributes.length; m++) {
										var attrib = akey.attributes[m];
										if (attrib.name != "f") {
											if (attrib.value == "true") kvals[attrib.name] = true;
											else if (attrib.value == "false") kvals[attrib.name] = false;
											else kvals[attrib.name] = attrib.value;
										}
									}
									var pararr = {};
									pararr[pname] = kvals;
									mno.getTimeline().addKeyframes(framenum, pararr);
								}
							}
						}
					}
// WHEN SHOULD WE AND SHOULDN'T WE RUN
// SCRIPTS OF ASSETS THAT AREN'T THE
// MAIN SCENE!!!!
					if (myobj.childNodes[j].nodeName.toUpperCase()=="S") {
						var myscript = myobj.childNodes[j];
						var sname = "";
						var spars = new Object();
						for(var l=0; l<myscript.attributes.length; l++) {
							var attr = myscript.attributes[l];
							if (attr.name != "name") {
								spars[attr.name] = attr.value;
							} else {
								sname = attr.value;
								//alert(sname);
							}
						}
						//alert("my script was added\nisn't that delightful!!!");
						if (typeof(exec) == "undefined" || exec == true) new mno.TFScript(sname, true, spars);
						else new mno.TFScript(sname, false, spars);
					}
					// add gradients back in here later
					// for now it's bloat to cause problems
					if (myobj.childNodes[j].nodeName.toUpperCase()=="C") {
						// for now we make it flat... fix this later
						_loadSource("", exec, subassetpath, myobj.childNodes[j], mno, tfScene);
					}
					if (typeof(parOb) != "undefined") mno.setParent(parOb, false);
				}
				
				// don't make it flat!!!!
				//if (typeof(innerOb) == "undefined" && typeof(mno) != "undefined") objectManagers.root.push(mno);
				//else if (typeof(mno) != "undefined") innerOb.push(mno);
			} else if (mparent.childNodes[i].nodeName.toUpperCase()=="L") {
				// this is used to set the depth of layers
				var node = mparent.childNodes[i];
				var lname = "";
				var ldepth = 1000;
				for(var m=0; m<node.attributes.length; m++) {
					var pname = node.attributes[m].name;
					var pvalue = node.attributes[m].value;
					if (pname == "name") lname = pvalue;
					else if (pname == "depth") ldepth = pvalue;
				}
				var lay;
				if (!layers[lname]) lay = new TFLayer(lname);
				else lay = layers[lname];
				lay.setDepth(parseInt(ldepth));
			}
		}
		if (typeof(xmlOb) == "undefined") return scenePars;
	}
	function objectsCollide(obOne, obTwo) {
		var centerOne = [(obOne.getBounds()[0]+obOne.getBounds()[2])/2, (obOne.getBounds()[1]+obOne.getBounds()[3])/2];
		var centerTwo = [(obTwo.getBounds()[0]+obTwo.getBounds()[2])/2, (obTwo.getBounds()[1]+obTwo.getBounds()[3])/2];
		var centerDif = [centerOne[0]-centerTwo[0], centerOne[1]-centerTwo[1]];
		var smallestDist = -999999;
		var smallestVect;
		var perp = getNormals(obOne);
		var firstLoop = true;
		for (var i=0; i<perp.length; i++) {
			// we look for somewhere that the two projections don't overlap
			// if they overlap on all perpendiculars then the objects overlap
			var projOne = GetPolygonProjection(obOne, perp[i]);
			var projTwo = GetPolygonProjection(obTwo, perp[i]);
			var dist = DistanceBetweenProjections(projOne, projTwo);
			if (dist > 0) {
				//console.log(perp[i]);
				return false;
			}
			else if (dist > smallestDist) {
				smallestDist = dist;
				smallestVect = perp[i];
				// this ensures we don't push the polygon in the wrong direction
				if ((centerDif[0]*smallestVect[0])+(centerDif[1]*smallestVect[1]) < 0) smallestVect = [-smallestVect[0], -smallestVect[1]];
			}
			if (firstLoop == true && i == perp.length-1) {
				perp = getNormals(obTwo);
				i = -1;
				firstLoop = false;
			}
		}
		var ret = [smallestVect[0]*smallestDist, smallestVect[1]*smallestDist];
		return ret;
	}
	///////////// PRIVATE FUNCTIONS ////////////
	function getNormals(ob) {
		var perp = [];
		var pnts = ob.getPoints();
		var pOne;
		var pTwo = pnts[0];
		pTwo = [pTwo[0]+ob.getParameter("x"),pTwo[1]+ob.getParameter("y")];
		for (var i=1; i <= pnts.length; i++) {
			var j = i;
			if (j == pnts.length) j = 0;
			pOne = pTwo;
			pTwo = pnts[j];
//			pTwo = [pTwo[0]+ob.getParameter("x"),pTwo[1]+ob.getParameter("y")];
//			pTwo[0] += ob.getParameter("x");
//			pTwo[1] += ob.getParameter("y");
			var vector = [pOne[0]-pTwo[0], pOne[1]-pTwo[1]];
			// rotate by 90 degrees
			vector = [-vector[1], vector[0]];
			// set to magnitude of 1
			var mag = Math.pow(Math.pow(vector[0],2)+Math.pow(vector[1],2), .5);
			if (mag > 0.01) vector = [vector[0]/mag, vector[1]/mag];
			else vector = [1,0];
			perp.push(vector);
		}
		return perp;
	}
	function GetPolygonProjection(ob, vector) {
		var min = 99999999;
		var max = -99999999;
		var pnts = ob.getPoints();
		for (var i=0; i<pnts.length; i++) {
			var num = ((pnts[i][0]+ob.getParameter("x"))*vector[0])+((pnts[i][1]+ob.getParameter("y"))*vector[1])
			if (num < min) min = num;
			if (num > max) max = num;
		}
		return [min, max];
	}
	function DistanceBetweenProjections(pA, pB) {
		if (pA[0] < pB[0]) {
	        return pB[0] - pA[1];
	    } else {
	        return pA[0] - pB[1];
	    }
	}
	function Initialize() {
		assets.activeScene = new TFScene();
		// detects whether we're on a touch based device
		try {
			document.createEvent("TouchEvent");
			pointer.touch = true;
		} catch (e) {
			pointer.touch = false;
		}
		// sets listeners for touch, general, and IE browsers
		// this will check for user defined listeners that might apply
		function MDown () { RunUserEvents("mousedown", mousePosition()); }
		function MUp () { RunUserEvents("mouseup", mousePosition()); }
		function MMove () { RunUserEvents("mousemove", mousePosition()); }
		function GoFullscreen() {
			fullscreen();
			e.preventDefault();
		}
		// consider adding mouseover and mouseout in the future...
		if (pointer.touch == true) {
			content.container.addEventListener("contextmenu", GoFullscreen, false);
			document.addEventListener("keydown", KeyDownFunction, false);
			document.addEventListener("keyup", KeyUpFunction, false);
			window.addEventListener('orientationchange', function(){ lastWidth = 0; });
			window.addEventListener('resize', function(){ lastWidth = 0; });
			// disables scrolling
			content.container.addEventListener('touchstart', function(e){ e.preventDefault(); });
			content.container.addEventListener('touchmove', function(e){ e.preventDefault(); });
			content.container.addEventListener("touchstart", SetMouse, false);
			content.container.addEventListener("touchmove", SetMouse, false);
			content.container.addEventListener("touchstart", MDown, false);
			content.container.addEventListener("touchend", MUp, false);
			content.container.addEventListener("touchmove", MMove, false);
		} else {
			if (content.container.addEventListener) {
				content.container.addEventListener("contextmenu", GoFullscreen, false);
				document.addEventListener("keydown", KeyDownFunction, false);
				document.addEventListener("keyup", KeyUpFunction, false);
				document.body.addEventListener("mousemove", SetMouse, false);
				content.container.addEventListener("mousedown", MDown, false);
				content.container.addEventListener("mouseup", MUp, false);
				content.container.addEventListener("mousemove", MMove, false);
			} else if (document.body.attachEvent) {
				content.container.attachEvent("contextmenu", GoFullscreen);
				document.attachEvent("onkeydown", KeyDownFunction);
				document.attachEvent("onkeyup", KeyUpFunction);
				document.body.attachEvent("onmousemove", SetMouse);
				content.container.attachEvent("onmousedown", MDown);
				content.container.attachEvent("onmouseup", MUp);
				content.container.attachEvent("onmousemove", MMove);
			}
		}
		content.tfratio = new TFRatio();
		new TFLayer("");
	}
	function waitingToLoad () {
		// we don't want it to flash too quickly in some instances...
		calls++;
		if (calls < mintime*10) return;
		
		for (var el in assets.images) {
			if (!assets.images[el].complete) return;
		}
		for (var el in assets.audio) {
			if (!assets.audio[el].complete) return;
		}
		clearInterval(loadingInterval);
		if (typeof(loadFunction) == "function") loadFunction();
	}
	function KeyDownFunction(e) {
		var KeyID = (window.event) ? event.keyCode : e.keyCode;
		if (KeyID in keys.map) {
			keys.states[keys.map[KeyID]] = true;
		}
	}
	function KeyUpFunction(e) {
		var KeyID = (window.event) ? event.keyCode : e.keyCode;
		if (KeyID in keys.map) {
			keys.states[keys.map[KeyID]] = false;
		}
	}
	function IsNumeric() {	
		var ValidChars = "0123456789.-";
		var IsNumber=true;
		var Char;
		for (i = 0; i < sText.length && IsNumber == true; i++) { 
			Char = sText.charAt(i); 
			if (ValidChars.indexOf(Char) == -1) {
				IsNumber = false;
			}
		}
		return IsNumber;
	}
	function SetMouse (ev) {
		if (pointer.touch == true) {
			var touch = ev.touches[ev.touches.length-1];
			var container = content.container;
			var leftOff = 0;
			var topOff = 0;
			while (container.offsetParent) {
				leftOff += container.offsetLeft - container.scrollLeft; 
				topOff += container.offsetTop - container.scrollTop;
				container = container.offsetParent;
			}
			pointer.positions[0][0] = (touch.pageX - leftOff);
			pointer.positions[0][1] = (touch.pageY - topOff);
		} else {
			if (!ev || !document.addEventListener) {
				ev = window.event;
				pointer.positions[0][0] = (window.event.x-can.offsetLeft);
				pointer.positions[0][1] = (window.event.y-can.offsetTop);
			} else {
				var container = content.container;
				var leftOff = 0;
				var topOff = 0;
				while (container.offsetParent) {
					leftOff += container.offsetLeft - container.scrollLeft; 
					topOff += container.offsetTop - container.scrollTop;
					container = container.offsetParent;
				}
				pointer.positions[0][0] = (ev.pageX - leftOff);
				pointer.positions[0][1] = (ev.pageY - topOff);
			}
			
		}
		var can = layers[""].getCanvases()[0].getCanvas();
// WE NEED TO READD THIS
// WITH THE NEW SYSTEM!!!
// OTHERWISE MOUSE CLICKS
// WILL BE MESSED UP IN
// SITUATIONS
		pointer.positions[0][0] /= content.tfratio.getScale();
		pointer.positions[0][1] /= content.tfratio.getScale();
		/*pointer.positions[0][0] /= (content.ratio/(can.width/can.offsetWidth));//window.devicePixelRatio);
		pointer.positions[0][1] /= (content.ratio/(can.width/can.offsetWidth));//window.devicePixelRatio);*/
		//alert(pointer.positions[0][0] + ", " + pointer.positions[0][1]);
	}
	function RunUserEvents (eventType, mPos, cObjs) {
		// this checks if there are any events added using TFObject.createListener that need to be handled
		if (typeof(cObjs) == "undefined") {
			cObjs = getObjects();
			// lets also do listeners for the root object...
			if (typeof(assets.activeScene.getManagers().root.getListeners()[eventType]) != "undefined") {
				var j = 0;
				while (j < assets.activeScene.getManagers().root.getListeners()[eventType].length) {
					assets.activeScene.getManagers().root.getListeners()[eventType][j]();
					j++;
				}
			}
		}
		var i = 0;
		while (i < cObjs.length) {
			// either we have listeners of the right type
			// or we moved the mouse and have mouseenter or mouseexit events
			if (
				typeof(cObjs[i].getListeners()[eventType]) != "undefined" || 
				(
					eventType == "mousemove" && 
					(
						typeof(cObjs[i].getListeners()["mouseenter"]) != "undefined" || 
						typeof(cObjs[i].getListeners()["mouseexit"]) != "undefined"
					) 
				)
			) {
				if (cObjs[i].pointInside(mPos)) {
					if (typeof(cObjs[i].getListeners()[eventType]) != "undefined") {
						var j = 0;
						while (j < cObjs[i].getListeners()[eventType].length) {
							cObjs[i].getListeners()[eventType][j](cObjs[i].getName());
							j++;
						}
					}
					if (eventType == "mousemove" && (typeof(currentlyInside[cObjs[i].getName()]) == "undefined" || currentlyInside[cObjs[i].getName()] == 0) ) {
						// we just came inside the object
						if (typeof(cObjs[i].getListeners()["mouseenter"]) != "undefined") {
							var j = 0;
							while (j < cObjs[i].getListeners()["mouseenter"].length) {
								cObjs[i].getListeners()["mouseenter"][j](cObjs[i].getName());
								j++;
							}
						}
						currentlyInside[cObjs[i].getName()] = 1;
					}
				} else {
					// we might have left the object... ie. mouseexit
					if (typeof(cObjs[i].getListeners()["mouseexit"]) != "undefined" && currentlyInside[cObjs[i].getName()] == 1) {
						var j = 0;
						while (j < cObjs[i].getListeners()["mouseexit"].length) {
							cObjs[i].getListeners()["mouseenter"][j](cObjs[i].getName());
							j++;
						}
						currentlyInside[cObjs[i].getName()] = 0;
					}
				}
			}
			RunUserEvents(eventType, mPos, cObjs[i].getChildren());
			i++;
		}
	}
	
	Initialize();
	return this;
}

// this is outside of TFApplication to prevent variable conflicts and "sandbox" the users code
function _RunScript (object, canvas, _script, shared, global) {
	eval(_script);
	return this;
}

