/*
*	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.
*/

// since we're removing timeline animation for the initial release
// take out the following in addition to the functions:
// var selectedFrame = 0;
// var keyframes = new Object();
// the following are the functions to take out functions:
// Play();
// StopPlaying();
// SetFrame();
// IncrementFrame();
// AddKeyframe();

function TFApplication (canvasName, sourceFile) {
	var can = document.getElementById(canvasName);
	can.innerHTML = "";
	var canvas = can.getContext("2d");
	
	document.onkeydown = KeyDownFunction;
	document.onkeyup = KeyUpFunction;
	
	// private variables
	// these store references to TFObjects
	// only this one isn't flat
	// these 3 don't have to be touched when we change parents...
	var nameHash = new Object();
	var buttonsArr = new Array();
	var buttonOver = new Object();
	var objectsArr = new TFObject("root", "RootObj");
	// do this one
	// same as nameHash, except it stores the parent...
	var parentHash = new Object();
	var parentTransformations = new Object();
	
	var loadedImages = new Object();
	
	var otherCanvas = new Object();
	//var changedCanvas = new 
	
	var selectedFrame = 0;
	var nextNameNumber = 0;
	// private key states
	var keyConversions = new Object();
	keyConversions[48] = '0'; keyConversions[49] = '1'; keyConversions[50] = '2'; keyConversions[51] = '3'; keyConversions[52] = '4'; keyConversions[53] = '5'; keyConversions[54] = '6'; keyConversions[55] = '7'; keyConversions[56] = '8'; keyConversions[57] = '9'; keyConversions[65] = 'a'; keyConversions[66] = 'b'; keyConversions[67] = 'c'; keyConversions[68] = 'd'; keyConversions[69] = 'e'; keyConversions[70] = 'f'; keyConversions[71] = 'g'; keyConversions[72] = 'h'; keyConversions[73] = 'i'; keyConversions[74] = 'j'; keyConversions[75] = 'k'; keyConversions[76] = 'l'; keyConversions[77] = 'm'; keyConversions[78] = 'n'; keyConversions[79] = 'o'; keyConversions[80] = 'p'; keyConversions[81] = 'q'; keyConversions[82] = 'r'; keyConversions[83] = 's'; keyConversions[84] = 't'; keyConversions[85] = 'u'; keyConversions[86] = 'v'; keyConversions[87] = 'w'; keyConversions[88] = 'x'; keyConversions[89] = 'y'; keyConversions[90] = 'z'; keyConversions[9] = 'tab'; keyConversions[13] = 'enter'; keyConversions[16] = 'shift'; keyConversions[17] = 'ctrl'; keyConversions[18] = 'alt'; keyConversions[27] = 'escape'; keyConversions[37] = 'left'; keyConversions[38] = 'up'; keyConversions[39] = 'right'; keyConversions[40] = 'down'; keyConversions[8] = 'backspace'; keyConversions[186] = 'semicolon'; keyConversions[187] = 'equals'; keyConversions[188] = 'comma'; keyConversions[189] = 'dash'; keyConversions[190] = 'period'; keyConversions[191] = 'forward slash'; keyConversions[192] = 'grave comma'; keyConversions[219] = 'open bracket'; keyConversions[220] = 'backslash'; keyConversions[221] = 'close bracket'; keyConversions[222] = 'quote';
	var parameterTypes = new Object();
	parameterTypes['x'] = "integer"; parameterTypes['y'] = "integer"; parameterTypes['rotation'] = "float"; parameterTypes['layer'] = "string"; parameterTypes['width'] = "integer"; parameterTypes['scale'] = "float"; parameterTypes['scalewidth'] = "boolean"; parameterTypes['visible'] = "boolean"; parameterTypes['linecolour'] = "colour"; parameterTypes['fillcolour'] = "colour"; parameterTypes['audio'] = "file"; parameterTypes['quadratic'] = "boolean"; parameterTypes['closed'] = "boolean"; parameterTypes['radius'] = "integer"; parameterTypes['text'] = "string"; parameterTypes['font'] = "font";
	var KeyStates = new Object();	
	var mousePos = new Array();
	if (can.addEventListener) can.addEventListener("mousedown", CheckButtons, false);
	else if (can.attachEvent) can.attachEvent("onmousedown", CheckButtons);
	document.body.onmousemove = SetMouse;
	
	// public method pointers
	this.SetBackground = SetBackground;
	this.Dimensions = Dimensions;
	this.CreateLayer = CreateLayer;
	this.LinearGradient = LinearGradient;
	this.RadialGradient = RadialGradient;
	this.TFObject = TFObject;
	this.GetObject = GetObject;
	this.GetObjects = GetObjects;
	this.RefreshCanvas = RefreshCanvas;
	this.LoadSource = LoadSource;
	this.Play = Play;
	this.StopPlaying = StopPlaying;
	this.KeyPressed = KeyPressed;
	this.SetFrame = SetFrame;
	this.IncrementFrame = IncrementFrame;
	this.MousePosition = MousePosition;
	this.ParameterType = ParameterType;
	this.GetDistance = GetDistance;
	this.Preload = Preload;
	
	// public methods
	function SetBackground(givenColour) {
		can.style.background = givenColour;
	}
	function Dimensions() {
		var dim = new Object();
		dim.width = can.width;
		dim.height = can.height;
		return dim;
	}
	function CreateLayer (layerName, tcan) {
		var newCanvas;
		if (typeof(tcan) == "undefined") {
			// get absolute position of current canvas
			var container = can;
			var leftOff = 0;
			var topOff = 0;
			while (container.offsetParent) {
				leftOff += container.offsetLeft - container.scrollLeft; 
				topOff += container.offsetTop - container.scrollTop;
				container = container.offsetParent;
			}
			
			newCanvas = document.createElement('canvas');
			newCanvas.setAttribute("width", can.width);
			newCanvas.setAttribute("height", can.height);
			newCanvas.setAttribute("style", "position: absolute; top: " + topOff + "; left: " + leftOff + ";");
			// excanvas needs help finding dynamically created canvas's
			if (typeof(G_vmlCanvasManager) != "undefined") G_vmlCanvasManager.initElement(newCanvas);
			document.body.appendChild(newCanvas);
		} else {
			newCanvas = document.getElementById(tcan);
		}
		
		otherCanvas[layerName] = newCanvas.getContext("2d");
		
		if (newCanvas.addEventListener) newCanvas.addEventListener("mousedown", CheckButtons, false);
		else if (newCanvas.attachEvent) newCanvas.attachEvent("onmousedown", CheckButtons);
	}
	function LinearGradient(startPoint, endPoint, gradPoints) {
		var grad = canvas.createLinearGradient(startPoint[0], startPoint[1], endPoint[0], endPoint[1]);
		var i = 0;
		while (i < gradPoints.length) {
			grad.addColorStop(gradPoints[i][0], gradPoints[i][1]);
			i++;
		}
		return grad;
	}
	function RadialGradient(centrePoint, rad, gradPoints) {
		var grad = canvas.createRadialGradient(centrePoint[0], centrePoint[1], rad[0], centrePoint[0], centrePoint[1], rad[1]);
		var i = 0;
		while (i < gradPoints.length) {
			grad.addColorStop(gradPoints[i][0], gradPoints[i][1]);
			i++;
		}
		return grad;
	}
	//////////////////// make parenting work
	function TFObject(obtype, objname, someparam, objParent) {	
		var thisObj = this;
		var parentObj;
		// private variables
		var name;
		if (typeof(objname) != "undefined" && typeof(nameHash[name]) == "undefined") name = objname;
		else {
			name = "Object" + nextNameNumber;
			nextNameNumber++;
		}
		// 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 buttonFunction;
		var over = false;
		var overFunction;
		var outFunction;
	
		var actualImage;
		
		//////////////////////////// Setup Parental Transformations object ////////////////////
		// set this to children when...
		//				SetParameter scale, x, y, rotation											todo
		//				SetParameters...															todo
		//				SetFrame																	todo
		// add parent to parents cumalitive when...
		//				new TFObject																done
		//				AddChild																	todo
		// this is handled automatically for rendering but needs to be done for...
		// 				collision detection
		//				movepivot
		//				setparent
		//				... and translate tool in TFDesigner... (shh, i'm not just building this library for my designer!!)
		
		var objectType = obtype;
		var points = new Array();
		var children = new Array();
		var keyframes = new Object();
		var currentState = new Object();
		if (typeof(obtype) != "undefined" && obtype != "root") {
			currentState.x = 0;
			currentState.y = 0;
			currentState.rotation = 0.0;
			currentState.scale = 1.0;
			currentState.layer = "";
			if (typeof(obtype) != "undefined") {
				if (obtype != "audio") {
					currentState.width = 1;
					currentState.scalewidth = true;
					currentState.visible = true;
					currentState.linecolour = "rgb(0,0,0)";
					currentState.fillcolour = "none";
				} else {
					currentState.audio = "notset.m4a"
				}
				if (obtype == "line") {
					currentState.quadratic = false;
					currentState.closed = false;
				} else if (obtype == "rectangle") {
					currentState.collider = false;
				} else if (obtype == "circle") {
					currentState.radius = 1;
					currentState.centre = [0, 0];
				} 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...
					// if it is passed when creating the TFObject...
					// that is taken care of 4 lines below
				}
			}
		}
			
		// public method pointers
		this.SetParent = SetParent;
		this.GetParent = GetParent;
		this.SetParameter = SetParameter;
		this.SetParameters = SetParameters;
		this.GetParameter = GetParameter;
		this.GetParameters = GetParameters;
		this.GetParentalTransformation = GetParentalTransformation;
		this.SetName = SetName;
		this.GetName = GetName;
		this.GetType = GetType;
		this.AddPoint = AddPoint;
		this.AddPoints = AddPoints;
		this.SetPoint = SetPoint;
		this.GetPoints = GetPoints;
		this.RemovePoint = RemovePoint;
		this.AddKeyframe = AddKeyframe;
		this.Translate = Translate;
		this.Rotate = Rotate;
		this.Scale = Scale;
		this.MovePivot = MovePivot;
		this.SetExtras = SetExtras;
		this.GetExtras = GetExtras;
		this.PointInside = PointInside;
		this.GetImage = GetImage;
		this.DeleteObject = DeleteObject;
		this.MakeButton = MakeButton;
		this.ButtonFunctions = ButtonFunctions;
		this.GetOver = GetOver;
		this.ClosestPoint = ClosestPoint;
		this.DistanceTo = DistanceTo;
		
		this.AddChild = AddChild;
		this.GetChildren = GetChildren;
		this.RemoveChild = RemoveChild;
		this.RemoveChildren = RemoveChildren;
		this.ChildDepth = ChildDepth;
		this.ChangeDepth = ChangeDepth;
		
		//public methods
		function SetParent (nParent) {
			//alert(GetName());
			//alert(nParent.GetName());
			parentHash[name].RemoveChild(name);
			nParent.AddChild(thisObj);
		}
		function GetParent () {
			if (name in parentHash) return parentObj;
			else return false;
		}
		function GetParentalTransformation () {
			return parentTransformations[name];
		}
		function SetParameter(param, givenvalue, dochildren) {
			if (param == "image") {
				if (givenvalue in loadedImages) {
					actualImage = loadedImages[givenvalue];
				} else {
					loadedImages[givenvalue] = new Image();
					loadedImages[givenvalue].src = givenvalue;
					actualImage = loadedImages[givenvalue];
				}
			}
			if (typeof(dochildren) == "undefined") {
				if (param == "rotation") {
					ParentRotate(thisObj, givenvalue-currentState[param]);
				} else if (param == "scale") {
					ParentScale(thisObj, givenvalue/currentState[param]);
				} else if (param == "x") {
					ParentTranslate(thisObj, givenvalue-currentState[param], 0);
				} else if (param == "y") {
					ParentTranslate(thisObj, 0, givenvalue-currentState[param]);
				}
			}
			currentState[param] = givenvalue;
		}
		function SetParameters(params) {
			var i = 0;
			while (i < params.length) {
				currentState[params[i][0]] = params[i][1];
				if (params[i][0] == "image") {
					if (params[i][1] in loadedImages) {
						actualImage = loadedImages[params[i][1]];
					} else {
						loadedImages[params[i][1]] = new Image();
						loadedImages[params[i][1]].src = params[i][1];
						actualImage = loadedImages[params[i][1]];
					}
				} else if (params[i][0] == "rotation") {
					ParentRotate(thisObj, params[i][1]-currentState[params[i][0]]);
				} else if (params[i][0] == "scale") {
					ParentScale(thisObj, params[i][1]-currentState[params[i][0]]);
				} else if (params[i][0] == "x") {
					ParentTranslate(thisObj, params[i][1]-currentState[params[i][0]], 0);
				} else if (params[i][0] == "y") {
					ParentTranslate(thisObj, 0, params[i][1]-currentState[params[i][0]]);
				}
				i++;
			}
		}
		function GetParameter(param) {
			return currentState[param];
		}
		function GetParameters() {
			return currentState;
		}
		function SetName(newname) {
			nameHash[newname] = nameHash[name];
			parentTransformations[newname] = parentTransformations[name];
			parentHash[newname] = parentHash[name];
			delete nameHash[name];
			delete parentTransformations[name];
			delete parentHash[name];
			if (name in buttonsArr) {
				buttonsArr[newname] = buttonsArr[name];
				buttonOver[newname] = buttonOver[name];
				delete buttonsArr[name];
				delete buttonsOver[name]
			}
			
			name = newname;
		}
		function GetName() {
			return name;
		}
		function GetType() {
			return objectType;
		}
		function SetName(newname) {
			nameHash[newname] = nameHash[name];
			delete nameHash[name];
			name = newname;
		}
		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 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 GetPoints() {
			return points;
		}
		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 AddKeyframe(framenum, pairs) {
			var i = 0;
			while (i < pairs.length) {
				keyframes[framenum][pairs[i][0]] = pairs[i][1];
				i++;
			}
		}
		function Translate(amount, setChildren) {
			currentState.x += amount[0];
			currentState.y += amount[1];
			// pass on to child data structures
			if (typeof(setChildren) == "undefined" || setChildren == true) ParentTranslate(thisObj, amount[0], amount[1]);
		}
		function Rotate(amount) {
			currentState.rotation += amount;
			// pass on to child data structures
			ParentRotate(thisObj, amount);
		}
		function Scale(amount) {
			currentState.scale *= amount;
			// pass on to child data structures
			ParentScale(thisObj, amount);
		}
		function MovePivot (newPos) {
			var i = 0;
			var xdiff = GetParameter("x")-(newPos[0]-parentTransformations[name].x);
			var ydiff = GetParameter("y")-(newPos[1]-parentTransformations[name].y);
			
			var pr = parentTransformations[name].rotation;
			
			var xdelt = (xdiff*Math.cos(currentState.rotation+pr)) + (ydiff*Math.sin(currentState.rotation+pr));
			var ydelt = -(xdiff*Math.sin(currentState.rotation+pr)) + (ydiff*Math.cos(currentState.rotation+pr));
			xdelt /= GetParameter("scale")*parentTransformations[name].scale;
			ydelt /= GetParameter("scale")*parentTransformations[name].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]-parentTransformations[name].x;
			currentState.y = newPos[1]-parentTransformations[name].y;
			
			i=0;
			while (i < children.length) {
				var pr = parentTransformations[children[i].GetName()];
				pr.x -= xdiff;
				pr.y -= ydiff;
				children[i].Translate([xdiff, ydiff], false);
				i++;
			}
		}
		function SetExtras (boundbox, sPivot) {
			boundingbox[0] = boundbox;
			if (typeof(sPivot) != "undefined") showPivot = sPivot;
		}
		function GetExtras () {
			return { bounds: boundingbox, pivot: showPivot };
		}
		function PointInside(gPoint) {
			// 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 parTran = parentTransformations[name];
			var givenPoint = new Object();
			givenPoint[0] = gPoint[0];
			givenPoint[1] = gPoint[1];
			var xOff = -(currentState.x+parTran.x);
			var yOff = -(currentState.y+parTran.y);
			xOff /= (currentState.scale*parTran.scale);
			yOff /= (currentState.scale*parTran.scale);
			//xOff = (xdiff*Math.cos(-rd)) + (ydiff*Math.sin(-rd));
			//yOff = -(xdiff*Math.sin(-rd)) + (ydiff*Math.cos(-rd));
//////////////////			
			givenPoint[0] += xOff;
			givenPoint[1] += yOff;
			
			var total = 0.0;
			var lastang = 0.0;
			var curang = 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 = 2;
			
			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 && inbounds > 0) {
				var xoff;
				var yoff;
			
				if (inbounds == 1) {
					// as bounding box is stored differently we create a special case
					// for each one...
					if (i == 0) {
						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 (i == 4) {
						xoff = boundingbox[1] - givenPoint[0];
						yoff = boundingbox[2] - givenPoint[1];
					} else if (total >= -1 && total <= 1) {
						alert(total);
						return false;
					} else {
						alert(total);
						if (GetType() == "rectangle") return true;
						i = 0;
						total = 0.0;
						inbounds = 2;
						finished = 0;
					}
				}
				
				if (inbounds == 2) {
					xoff = points[i][0] - givenPoint[0];
					yoff = points[i][1] - givenPoint[1];
				}
				
				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 < Math.PI) total += di;
					else if (ja < Math.PI) total += dj;
					else if (ka < Math.PI) 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 >= 1.0 || total <= -1.0) return true;
			else return false;
		}
		function DistanceTo (gPoint) {
			var cp = ClosestPoint(gPoint);
			var pp = ClosestPerpendicular(gPoint);
			if (cp < pp) return cp;
			else return pp;
		}
		function ClosestPoint(gPoint) {
			// for objects with a high point density this can be used in place of DistanceTo.
			var closest = 1000000000;
			i = 0;
			while (i < points) {
				var xoff = points[i][0] - gPoint[0];
				var yoff = points[i][1] - gPoint[1];
				var newone = Math.pow( Math.pow(xoff, 2) + Math.pow(yoff, 2) , .5);
				if (newone < closest) closest = newone;
				i++;
			}
			return closest;
		}
		function GetImage () {
			return actualImage;
		}
		function DeleteObject() {
			if (name in parentHash) parentHash[name].RemoveChild(name);
			else objectsArr.RemoveChild(name);
			
			var i = 0;
			while (i < buttonsArr.length) {
				if (buttonsArr[i].GetName() == name) buttonsArr.splice(i, 1); 
				i++;
			}
			
			if (name in buttonOver) delete buttonOver; 
			delete nameHash[name];
			delete parentHash[name];
			
			while (children.length > 0) {
				children[0].DeleteObject();
			}
		}
		function MakeButton (buttonfunc, overfunc, outfunc) {
			buttonFunction = buttonfunc;
			overFunction = overfunc;
			outFunction = outfunc;
			buttonsArr.push(this);
		}
		function ButtonFunctions () {
			if (typeof(outFunction) != "undefined") return { onclick: buttonFunction, onenter: overFunction, onexit: outFunction, length: 3 };
			else if (typeof(overFunction) != "undefined") return { onclick: buttonFunction, onenter: overFunction, length: 2 };
			else return { onclick: buttonFunction, length: 1 };
		}
		function GetOver () {
			return buttonOver[name];
		}
		function AddChild(newChild) {
			children.push(newChild);
			parentHash[newChild.GetName()] = thisObj;
			
			if (GetType() != "root") {
				// add up local and parental transformations of new parent
				var myTrans = GetParentalTransformation();
				var lParams = GetParameters();
				var newTrans = new Object();
				newTrans.rotation = myTrans.rotation + lParams.rotation;
				newTrans.scale = myTrans.scale * lParams.scale;
				newTrans.x = myTrans.x + lParams.x;
				newTrans.y = myTrans.y + lParams.y;
				
				// add up all old transformations
				var oldPar = newChild.GetParentalTransformation();
				var oldLoc = newChild.GetParameters();
				var oldTrans = new Object();
				oldTrans.rotation = oldPar.rotation + oldLoc.rotation;
				oldTrans.scale = oldPar.scale * oldLoc.scale;
				oldTrans.x = oldPar.x + oldLoc.x;
				oldTrans.y = oldPar.y + oldLoc.y;
				
				////////////// ROTATIONS EFFECT ON POSITION
				// new parental translation still needs to be found
				// newTrans currently stores parental translation relative to the parents pivot
				// we need it relative to the childs pivot
				var xDelta = oldTrans.x - newTrans.x;
				var yDelta = oldTrans.y - newTrans.y;
				if (xDelta != 0 || yDelta != 0) {
					var initangle;
					if ((xDelta < 0 && yDelta < 0) || (xDelta > 0 && yDelta > 0)) initangle = Math.abs(Math.atan(yDelta/xDelta));
					else initangle = Math.abs(Math.atan(xDelta/yDelta));
					// get an angle from 0 to 359.9 degrees instead of from 0 to 89.9 (in radians of course)
					if (xDelta < 0 && yDelta < 0) initangle += Math.PI;
					if (xDelta < 0 && yDelta > 0) initangle += (Math.PI/2);
					if (xDelta > 0 && yDelta < 0) initangle += (1.5*Math.PI);				
					// new angle
					initangle -= (newTrans.rotation);
					// hypotenuse distance stayed the same
					var hypot = Math.pow(Math.pow(yDelta, 2) + Math.pow(xDelta, 2), .5);
					// get new rise and run from ratio and hypotenuse
					newTrans.x = Math.cos(initangle)*hypot;
					newTrans.y = Math.sin(initangle)*hypot;
				}
				/////////////// SCALES EFFECT ON POSITION
				newTrans.x /= newTrans.scale;
				newTrans.y /= newTrans.scale;
				
				// newChildTransformations + newParentTransformations = oldChildTransformations + oldParentTransformations
				// this is so that the object "stays still"
				var newLoc = new Object();
				newChild.SetParameter("rotation", oldTrans.rotation - newTrans.rotation, false);
				newChild.SetParameter("scale", oldTrans.scale/newTrans.scale, false);
				newChild.SetParameter("x", newTrans.x, false);
				newChild.SetParameter("y", newTrans.y, false);
					
				// add new parental transformation to array
				// what we have above is the counter to all of these (plus the orgional)
				parentTransformations[newChild.GetName()] = {rotation: newTrans.rotation, scale: newTrans.scale, x: oldTrans.x-newTrans.x, y: oldTrans.y-newTrans.y};
			} else {
				parentTransformations[newChild.GetName()] = {x: 0, y: 0, rotation: 0.0, scale: 1.0};
			}
		}
		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) {
			parentHash[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 ClosestPerpendicular (point) {
			// DistanceTo uses this in conjunction with ClosestPoint.
			// this by itself is fairly useless though (so it's private) since without also using ClosestPoint
			// it won't necessarily find the minimum distance or a distance at all
			var smallestVal = 1000000000;
			i = 1;
			while (i < points.length) {
				var xdelta = points[i][0] - points[i-1][0];
				var ydelta = points[i][1] - points[i-1][1];
				// rise/run
				var slope = ydelta/xdelta;
				// b = y - mx
				var offset = points[i][1] - (slope*points[i][0]);
				
				// get a line passing through the given point that's perpendicular to the one calculated above
				var perpOffset = point[1] - (-slope*point[0]);
				
				// mx+b1 = -mx+b2
				// x = (b2-b1)/(2*m)
				var xpoint = (perpOffset-offset)/(2*slope);
				var ypoint = (slope*xpoint) + offset;
				
				// now calculate distance between given and calculate points
				var dx = point[0]-xpoint;
				var dy = point[1]-ypoint;
				var newVal = Math.pow( Math.pow(dx,2) + Math.pow(dy,2) ,.5);
				if (newVal < smallestVal) smallestVal = newVal; 
				
				i++;
			}
			return smallestVal;
		}
		
		if ((typeof(objParent) == "undefined" || objParent == null) && obtype != "root") {			
			objectsArr.AddChild(this);
			parentHash[name] = objectsArr;
		} else if (obtype != "root") {
			objParent.AddChild(this);
			parentHash[name] = objParent;
		}
		
		if (obtype != "root") nameHash[name] = this;
		
		if (typeof(someparam) != "undefined" && someparam != null) SetParameters(someparam);
		
		return this;
	}
	function GetObject(name) {
		return nameHash[name];
	}
	function GetObjects() {
		return objectsArr.GetChildren();
	}
	function RefreshCanvas(lname, tObjs) {
		if (typeof(tObjs) == "undefined") {
			if (typeof(lname) == "undefined") {
				canvas.clearRect(0,0,can.width,can.height);
				for (var mc in otherCanvas) {
					otherCanvas[mc].clearRect(0,0,can.width,can.height);
				}
			} else if (lname == "") {
				canvas.clearRect(0,0,can.width,can.height);
			} else {
				otherCanvas[lname].clearRect(0,0,can.width,can.height);
			}
			tObjs = objectsArr.GetChildren();
		}
		// display each object
		for (var i = 0; i < tObjs.length; i++) {
			var objLayer = tObjs[i].GetParameter("layer");
		if (typeof(lname) == "undefined" || objLayer == lname) {
			var objCanvas;
			if (objLayer == "") {
				objCanvas = canvas;
			} else {
				objCanvas = otherCanvas[objLayer]
			}
			objCanvas.save();
			objCanvas.translate(tObjs[i].GetParameter("x")+parentTransformations[tObjs[i].GetName()].x, tObjs[i].GetParameter("y")+parentTransformations[tObjs[i].GetName()].y);
			objCanvas.rotate(tObjs[i].GetParameter("rotation")+parentTransformations[tObjs[i].GetName()].rotation);
			objCanvas.scale(tObjs[i].GetParameter("scale")*+parentTransformations[tObjs[i].GetName()].scale, tObjs[i].GetParameter("scale")*+parentTransformations[tObjs[i].GetName()].scale);
			
			var bnds = tObjs[i].GetExtras().bounds;
			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";
				var po = tObjs[i].GetParameters();
				objCanvas.fillRect(-2, -2,4,4);
			}
			
			objCanvas.fillStyle = tObjs[i].GetParameter("fillcolour");
			if (tObjs[i].GetParameter("scalewidth")) objCanvas.lineWidth = tObjs[i].GetParameter("width");
			else objCanvas.lineWidth = tObjs[i].GetParameter("width")/tObjs[i].GetParameter("scale");
			objCanvas.strokeStyle = tObjs[i].GetParameter("linecolour");
			
			if (tObjs[i].GetParameter("visible")) {
				objCanvas.beginPath();
				if (tObjs[i].GetType() == "line") {
					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]);
						} else {
							if (!tObjs[i].GetParameter("quadratic")) {
								objCanvas.lineTo(tObjs[i].GetPoints()[j][0], tObjs[i].GetPoints()[j][1]);
							} else if (typeof(tObjs[i].GetPoints()[j+1]) != "undefined") {
								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();
					if (tObjs[i].GetParameter("linecolour") != "none") objCanvas.stroke();
				} else if (tObjs[i].GetType() == "rectangle") {
					if (tObjs[i].GetParameter("fillcolour") != "none") objCanvas.fillRect(tObjs[i].GetPoints()[0][0], tObjs[i].GetPoints()[0][1], tObjs[i].GetPoints()[1][0]-tObjs[i].GetPoints()[0][0], tObjs[i].GetPoints()[1][1]-tObjs[i].GetPoints()[0][1]);
					if (tObjs[i].GetParameter("linecolour") != "none") objCanvas.strokeRect(tObjs[i].GetPoints()[0][0], tObjs[i].GetPoints()[0][1], tObjs[i].GetPoints()[1][0]-tObjs[i].GetPoints()[0][0], tObjs[i].GetPoints()[1][1]-tObjs[i].GetPoints()[0][1]);
				} else if (tObjs[i].GetType() == "circle") {
					objCanvas.beginPath();
					var cnt = tObjs[i].GetParameter("centre");
					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();
					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 wantedLines = wantedText.split("\n");
					var k = 0;
					while (k < wantedLines.length) {
						if (tObjs[i].GetParameter("fillcolour") != "none") objCanvas.fillText(wantedLines[k], 0, k*20);
						if (tObjs[i].GetParameter("linecolour") != "none") objCanvas.strokeText(wantedLines[k], 0, k*20);
						k++;
					}
				} else if (tObjs[i].GetType() == "image") {
					var ti = tObjs[i].GetImage();
					if (typeof(ti) != "undefined" && ti.complete) {
						objCanvas.drawImage(ti, 0, 0);
					}
				}
			}
			objCanvas.restore();
		}
			// now do children, while maintaining transormations (ie. this is after objCanvas.restore() as we use global coordinates)
			RefreshCanvas(lname, tObjs[i].GetChildren());
		}
	}
	/////////////////////// Add this for launch of TF Designer
	function LoadSource(source, xmlOb) {
		var mparent;
		if (typeof(xmlOb) == "undefined") {		
			if (window.XMLHttpRequest) {
				xhttp = new XMLHttpRequest();
			} else {
				xhttp = new ActiveXObject("Microsoft.XMLHTTP");
			}
			xhttp.open("GET", source, false);
			xhttp.send("");
			xmlDoc = xhttp.responseXML;
					
			mparent = xmlDoc.getElementsByTagName("parent")[0];
		} else {
			mparent = xmlOb;
		}

		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"));
				
				for(var j=0; j<myobj.childNodes.length; j++) {
					if (myobj.childNodes[j].nodeName.toUpperCase()=="POINTS") {
						var mypoints = myobj.childNodes[j];
						for(var k=0; k<mypoints.childNodes.length; k++) {
							if (mypoints.childNodes[k].nodeName.toUpperCase()=="P") {
								mno.AddPoint([parseInt(apoint.getAttribute("x")), parseInt(apoint.getAttribute("y"))]);
							}
						}
					}
					if (myobj.childNodes[j].nodeName.toUpperCase()=="KEYFRAMES") {
						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];
								var np = new Object;
								for(var l=0; l<apoint.attributes.length; l++) {
									if (apoint.attributes[l].name != "id") {
										if (IsNumeric(apoint.attributes[l].value)) {
											mno.SetParameter(apoint.attributes[l].name, parseFloat(apoint.attributes[l].value));
										} else if (apoint.attributes[l].value == "true" || apoint.attributes[l].value == "false") {
											if (apoint.attributes[l].value == "true") mno.SetParameter(apoint.attributes[l].name, true);
											else mno.SetParameter(apoint.attributes[l].name, false);
										} else {
											mno.SetParameter(apoint.attributes[l].name, apoint.attributes[l].value);
										}
									}
								}
								mno.SetKeyframe(parseInt(apoint.getAttribute("id")), np);
							}
						}
					}
					// add gradients back in here later
					// for now it's bloat to cause problems
					if (myobj.childNodes[j].nodeName.toUpperCase()=="CHILDREN") {
						// for now we make it flat... fix this later
						LoadXML("", myobj.childNodes[j]);
					}
				}
				
				// don't make it flat!!!!
				//if (typeof(innerOb) == "undefined" && typeof(mno) != "undefined") objectsArr.push(mno);
				//else if (typeof(mno) != "undefined") innerOb.push(mno);
			}
		}
	
		if (typeof(xmlOb) != "undefined") return;
		RefreshCanvas();

	}
	////////////////////// Untested
	function Play(startFrame, endFrame, playType) {		
		selectedFrame = startFrame;
		var stopit = setInterval("IncrementFrame(" + startFrame + ", " + endFrame + ", '" + playType + "')", 42);
		
		this.Stop = Stop;
		
		function Stop() {
			clearInterval(stopit);
		}	
	}
	function StopPlaying() {
		clearInterval(stopit);
		downFrame = false;
	}
	function KeyPressed(keyName) {
		if (keyName in KeyStates && KeyStates[keyName]) return true;
		return false;
	}
	///////////////////// untested
	function SetFrame(frameNum) {	
		var mFlat = FlattenH(objectsArr);
		selectedFrame = frameNum;
	
		for (var i=0; i < mFlat.length; i++) {
		
			var q = selectedFrame;
			var t = selectedFrame;
			while ((q in mFlat[i].keyframes) == false && q > 0) {
				q--;
			}
			while ((t in mFlat[i].keyframes) == false && t <= 100) {
				t++;
			}
			if (q != 0 && t != 101 && q != t) {
				for (var mpar in mFlat[i].keyframes[0]) {
					if (IsNumeric(mFlat[i].keyframes[0][mpar])) {
						mFlat[i].keyframes[0][mpar] = mFlat[i].keyframes[q][mpar] - ((mFlat[i].keyframes[q][mpar]-mFlat[i].keyframes[t][mpar])/Math.abs(q-t))*(selectedFrame-q);
					} else if (/^rgb/.test(mFlat[i].keyframes[0][mpar])) {
						var sml = mFlat[i].keyframes[q][mpar].split(/[\(\),]/);
						var lrg = mFlat[i].keyframes[t][mpar].split(/[\(\),]/);
						var mr = Math.abs(sml[1] - ((sml[1]-lrg[1])/Math.abs(q-t))*(selectedFrame-q)).toFixed(0);
						var mg = Math.abs(sml[2] - ((sml[2]-lrg[2])/Math.abs(q-t))*(selectedFrame-q)).toFixed(0);
						var mb = Math.abs(sml[3] - ((sml[3]-lrg[3])/Math.abs(q-t))*(selectedFrame-q)).toFixed(0);
						mFlat[i].keyframes[0][mpar] = "rgb(" + mr + "," + mg + "," + mb + ")";
					}
				}
			} else if (q == t) {
				for (var mpar in mFlat[i].keyframes[0]) {
					mFlat[i].keyframes[0][mpar] = mFlat[i].keyframes[frameNum][mpar];
				}
			}
		}
	
		RefreshCanvas();
	}
	/////////////////////// untested
	var downFrame = false;
	function IncrementFrame (startFrame, endFrame, playType) {
		var sFr;
		if (!downFrame) sFr = selectedFrame+1;
		else sFr = selectedFrame-1;
		
		if (sFr == fEnd+1 && playType == "loop") {
			sFr = fStart;
		} else if (sFr < startFrame && playType == "pong") {
			sFr = fStart+1;
			downFrame = false;
		} else if (sFr > fEnd && playType == "pong") {
			sFr = fEnd-1;
			downFrame = true;
		} else if (sFr == fEnd+1) {
			clearInterval(stopit);
			return;
		}
		setFrame(sFr);
	}
	function MousePosition () {
		return [mousePos[0], mousePos[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);
	}
	var loadFunction;
	var loadingInterval;
	var mintime = 0.0;
	function Preload (loadFunc, imgArray, minimum) {
		var i = 0;
		while (i < imgArray.length) {
			loadedImages[imgArray[i][0]] = new Image();
			if (imgArray[i].length > 1) loadedImages[imgArray[i][0]].src = imgArray[i][1];
			else loadedImages[imgArray[i][0]].src = imgArray[i][0];
			i++;
		}
		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);
	}
	
	// private methods
	var calls = 0;
	var waitingToLoad = function () {
		// we don't want it to flash too quickly in some instances...
		calls++;
		if (calls < mintime*10) return;
		
		for (var el in loadedImages) {
			if (!loadedImages[el].complete) return;
		}
		clearInterval(loadingInterval);
		loadFunction();
	}
	function KeyDownFunction(e) {
		var KeyID = (window.event) ? event.keyCode : e.keyCode;
		if (KeyID in keyConversions) {
			KeyStates[keyConversions[KeyID]] = true;
		}
	}
	function KeyUpFunction(e) {
		var KeyID = (window.event) ? event.keyCode : e.keyCode;
		if (KeyID in keyConversions) {
			KeyStates[keyConversions[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 (!ev) {
			ev = window.event;
			mousePos[0] = window.event.x-can.offsetLeft;
			mousePos[1] = window.event.y-can.offsetTop;
		} else {
			var container = can;
			var leftOff = 0;
			var topOff = 0;
			while (container.offsetParent) {
				leftOff += container.offsetLeft - container.scrollLeft; 
				topOff += container.offsetTop - container.scrollTop;
				container = container.offsetParent;
			}
			mousePos[0] = ev.pageX - leftOff;
			mousePos[1] = ev.pageY - topOff;
		}
		
		var i = 0;
		while (i < buttonsArr.length) {
			if (buttonsArr[i].PointInside( MousePosition() ) && !buttonsArr[i].GetOver()) {
				if (buttonsArr[i].ButtonFunctions().length > 1) buttonsArr[i].ButtonFunctions().onenter(buttonsArr[i].GetName(), [buttonsArr[i].GetParameter("x"),buttonsArr[i].GetParameter("y")]);
				buttonOver[buttonsArr[i].GetName()] = true;
			} else if ( !buttonsArr[i].PointInside(MousePosition()) && buttonsArr[i].GetOver() ) {
				if (buttonsArr[i].ButtonFunctions().length > 2) buttonsArr[i].ButtonFunctions().onexit(buttonsArr[i].GetName(), [buttonsArr[i].GetParameter("x"),buttonsArr[i].GetParameter("y")]);
				buttonOver[buttonsArr[i].GetName()] = false;
			}
			i++;
		}
	}
	function CheckButtons () {
		var i = 0;
		while (i < buttonsArr.length) {
			if (buttonsArr[i].PointInside( MousePosition() )) {
				buttonsArr[i].ButtonFunctions().onclick(buttonsArr[i].GetName(), [buttonsArr[i].GetParameter("x"),buttonsArr[i].GetParameter("y")]);
			}
			i++;
		}
	}
	function ParentTranslate(obj, xd, yd, sd) {
		var children = obj.GetChildren();
		var i = 0;
		while (i < children.length) {
			parentTransformations[children[i].GetName()].x += xd;
			parentTransformations[children[i].GetName()].y += yd;
			ParentTranslate(children[i], xd, yd);
			i++;
		}
	}
	function ParentScale(obj, sd) {
		var children = obj.GetChildren();
		var i = 0;
		while (i < children.length) {
//////// THIS SHOULD SOMEHOW GO INTO THE PARENTAL TRANSFORMATION..../////////////
/////// LETS IGNORE IT FOR NOW....///////////
			var xDelta = children[i].GetParameter("x"); //parentTransformations[children[i].GetName()].scale;
			var yDelta = children[i].GetParameter("y");//parentTransformations[children[i].GetName()].scale;
			parentTransformations[children[i].GetName()].scale *= sd;
			var xNew = xDelta*sd;
			var yNew = yDelta*sd;
			//parentTransformations[children[i].GetName()].x += (xNew - xDelta);
			//parentTransformations[children[i].GetName()].y += (yNew - yDelta);
			children[i].SetParameters([["x", xNew], ["y", yNew]]);
			ParentScale(children[i], sd);
			i++;
		}
	}
	function ParentRotate(obj, rd, addX, addY) {
		var children = obj.GetChildren();
		var i = 0;
		while (i < children.length) {
//////// THIS TOO SHOULD SOMEHOW GO INTO THE PARENTAL TRANSFORMATION..../////////////
/////// LETS IGNORE IT FOR NOW....///////////
			parentTransformations[children[i].GetName()].rotation += rd;
			//////////////////// rotations affect on position
			var xdiff = children[i].GetParameter("x");
			var ydiff = children[i].GetParameter("y");
			if (typeof(addY) != "undefined") {
				xdiff += addX;
				ydiff += addY;
			}
			
			var xdelt = (xdiff*Math.cos(-rd)) + (ydiff*Math.sin(-rd));
			var ydelt = -(xdiff*Math.sin(-rd)) + (ydiff*Math.cos(-rd));
						
				children[i].SetParameters([["x", xdelt], ["y", ydelt]]);
				//parentTransformations[children[i].GetName()].x -= (xNew - xDelta);
				//parentTransformations[children[i].GetName()].x -= (yNew - yDelta);
			//////////////////////////////
			ParentRotate(children[i], rd, xdiff, ydiff);
			i++;
		}
	}
	
	if (typeof(sourceFile) != "undefined") LoadSource(sourceFile);
}
