text: /*
   * MotionGuidePlugin
   * Visit http://createjs.com/ for documentation, updates and examples.
   *
   * Copyright (c) 2010 gskinner.com, inc.
   *
   * 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.
   */
  
  /**
   * @module TweenJS
   */
  
  // namespace:
  this.createjs = this.createjs||{};
  
  (function() {
  	"use strict";
  
  	/**
  	 * A TweenJS plugin for working with motion guides. Defined paths which objects can follow or orient along.
  	 *
  	 * To use the plugin, install the plugin after TweenJS has loaded. To define a path, add
  	 *
  	 * 		createjs.MotionGuidePlugin.install();
  	 *
  	 * <h4>Example</h4>
  	 *
  	 * 		// Using a Motion Guide
  	 * 		createjs.Tween.get(target).to({guide:{ path:[0,0, 0,200,200,200, 200,0,0,0] }},7000);
  	 * 		// Visualizing the line
  	 * 		graphics.moveTo(0,0).curveTo(0,200,200,200).curveTo(200,0,0,0);
  	 *
  	 * Each path needs pre-computation to ensure there's fast performance. Because of the pre-computation there's no
  	 * built in support for path changes mid tween. These are the Guide Object's properties:<UL>
  	 * 		<LI> path: Required, Array : The x/y points used to draw the path with a moveTo and 1 to n curveTo calls.</LI>
  	 * 		<LI> start: Optional, 0-1 : Initial position, default 0 except for when continuing along the same path.</LI>
  	 * 		<LI> end: Optional, 0-1 : Final position, default 1 if not specified.</LI>
  	 * 		<LI> orient: Optional, string : "fixed"/"auto"/"cw"/"ccw"<UL>
  	 *				<LI>"fixed" forces the object to face down the path all movement (relative to start rotation),</LI>
  	 * 				<LI>"auto" rotates the object along the path relative to the line.</LI>
  	 * 				<LI>"cw"/"ccw" force clockwise or counter clockwise rotations including Adobe Flash/Animate-like
  	 * 				behaviour. This may override your end rotation value.</LI>
  	 * 		</UL></LI>
  	 * </UL>
  	 * Guide objects should not be shared between tweens even if all properties are identical, the library stores
  	 * information on these objects in the background and sharing them can cause unexpected behaviour. Values
  	 * outside 0-1 range of tweens will be a "best guess" from the appropriate part of the defined curve.
  	 *
  	 * @class MotionGuidePlugin
  	 * @constructor
  	 */
  	function MotionGuidePlugin() {
  		throw("MotionGuidePlugin cannot be instantiated.")
  	}
  	var s = MotionGuidePlugin;
  
  
  // static properties:
  	/**
  	 * @property priority
  	 * @protected
  	 * @static
  	 */
  	s.priority = 0; // high priority, should run sooner
  
  	/**
  	 * READ-ONLY. A unique identifying string for this plugin. Used by TweenJS to ensure duplicate plugins are not installed on a tween.
  	 * @property ID
  	 * @type {String}
  	 * @static
  	 * @readonly
  	 */
  	s.ID = "MotionGuide";
  
  // static methods
  	/**
  	 * Installs this plugin for use with TweenJS. Call this once after TweenJS is loaded to enable this plugin.
  	 * @method install
  	 * @static
  	 */
  	s.install = function() {
  		createjs.Tween._installPlugin(MotionGuidePlugin);
  		return createjs.Tween.IGNORE;
  	};
  
  	/**
  	 * Called by TweenJS when a new property initializes on a tween.
  	 * See {{#crossLink "SamplePlugin/init"}}{{/crossLink}} for more info.
  	 * @method init
  	 * @param {Tween} tween
  	 * @param {String} prop
  	 * @param {any} value
  	 * @return {any}
  	 * @static
  	 */
  	s.init = function(tween, prop, value) {
  		if(prop == "guide") {
  			tween._addPlugin(s);
  		}
  	};
  
  	/**
  	 * Called when a new step is added to a tween (ie. a new "to" action is added to a tween).
  	 * See {{#crossLink "SamplePlugin/step"}}{{/crossLink}} for more info.
  	 * @method step
  	 * @param {Tween} tween
  	 * @param {TweenStep} step
  	 * @param {Object} props
  	 * @static
  	 */
  	s.step = function(tween, step, props) {
  		for (var n in props) {
  			if(n !== "guide") { continue; }
  
  			var guideData = step.props.guide;
  			var error = s._solveGuideData(props.guide, guideData);
  			guideData.valid = !error;
  
  			var end = guideData.endData;
  			tween._injectProp("x", end.x);
  			tween._injectProp("y", end.y);
  
  			if(error || !guideData.orient) { break; }
  
  			var initRot = step.prev.props.rotation === undefined ? (tween.target.rotation || 0) : step.prev.props.rotation;
  
  			guideData.startOffsetRot = initRot - guideData.startData.rotation;
  
  			if(guideData.orient == "fixed") {
  				// controlled rotation
  				guideData.endAbsRot = end.rotation + guideData.startOffsetRot;
  				guideData.deltaRotation = 0;
  			} else {
  				// interpreted rotation
  
  				var finalRot = props.rotation === undefined ? (tween.target.rotation || 0) : props.rotation;
  				var deltaRot = (finalRot - guideData.endData.rotation) - guideData.startOffsetRot;
  				var modRot = deltaRot % 360;
  
  				guideData.endAbsRot = finalRot;
  
  				switch(guideData.orient) {
  					case "auto":
  						guideData.deltaRotation = deltaRot;
  						break;
  					case "cw":
  						guideData.deltaRotation = ((modRot + 360) % 360) + (360 * Math.abs((deltaRot/360) |0));
  						break;
  					case "ccw":
  						guideData.deltaRotation = ((modRot - 360) % 360) + (-360 * Math.abs((deltaRot/360) |0));
  						break;
  				}
  			}
  
  			tween._injectProp("rotation", guideData.endAbsRot);
  		}
  	};
  
  	/**
  	 * Called before a property is updated by the tween.
  	 * See {{#crossLink "SamplePlugin/change"}}{{/crossLink}} for more info.
  	 * @method change
  	 * @param {Tween} tween
  	 * @param {TweenStep} step
  	 * @param {String} prop
  	 * @param {any} value
  	 * @param {Number} ratio
  	 * @param {Boolean} end
  	 * @return {any}
  	 * @static
  	 */
  	s.change = function(tween, step, prop, value, ratio, end) {
  		var guideData = step.props.guide;
  
  		if(
  				!guideData ||							// Missing data
  				(step.props === step.prev.props) || 	// In a wait()
  				(guideData === step.prev.props.guide) 	// Guide hasn't changed
  		) {
  			return; // have no business making decisions
  		}
  		if(
  				(prop === "guide" && !guideData.valid) ||		// this data is broken
  				(prop == "x" || prop == "y") ||					// these always get over-written
  				(prop === "rotation" && guideData.orient)		// currently over-written
  		){
  			return createjs.Tween.IGNORE;
  		}
  
  		s._ratioToPositionData(ratio, guideData, tween.target);
  	};
  
  // public methods
  	/**
  	 * Provide potentially useful debugging information, like running the error detection system, and rendering the path
  	 * defined in the guide data.
  	 *
  	 * NOTE: you will need to transform your context 2D to the local space of the guide if you wish to line it up.
  	 * @param {Object} guideData All the information describing the guide to be followed.
  	 * @param {DrawingContext2D} [ctx=undefined] The context to draw the object into.
  	 * @param {Array} [higlight=undefined] Array of ratio positions to highlight
  	 * @returns {undefined|String}
  	 */
  	s.debug = function(guideData, ctx, higlight) {
  		guideData = guideData.guide || guideData;
  
  		// errors
  		var err = s._findPathProblems(guideData);
  		if(err) {
  			console.error("MotionGuidePlugin Error found: \n" + err);
  		}
  
  		// drawing
  		if(!ctx){ return err; }
  
  		var i;
  		var path = guideData.path;
  		var pathLength = path.length;
  		var width = 3;
  		var length = 9;
  
  		ctx.save();
  		//ctx.resetTransform();
  
  		ctx.lineCap = "round";
  		ctx.lineJoin = "miter";
  		ctx.beginPath();
  
  		// curve
  		ctx.moveTo(path[0], path[1]);
  		for(i=2; i < pathLength; i+=4) {
  			ctx.quadraticCurveTo(
  				path[i], path[i+1],
  				path[i+2], path[i+3]
  			);
  		}
  
  		ctx.strokeStyle = "black";
  		ctx.lineWidth = width*1.5;
  		ctx.stroke();
  		ctx.strokeStyle = "white";
  		ctx.lineWidth = width;
  		ctx.stroke();
  		ctx.closePath();
  
  		// highlights
  		var hiCount = higlight.length;
  		if(higlight && hiCount) {
  			var tempStore = {};
  			var tempLook = {};
  			s._solveGuideData(guideData, tempStore);
  
  			for(var i=0; i<hiCount; i++){
  				tempStore.orient = "fixed";
  				s._ratioToPositionData(higlight[i], tempStore, tempLook);
  
  				ctx.beginPath();
  
  				ctx.moveTo(tempLook.x, tempLook.y);
  				ctx.lineTo(
  					tempLook.x + Math.cos(tempLook.rotation * 0.0174533) * length,
  					tempLook.y + Math.sin(tempLook.rotation * 0.0174533) * length
  				);
  
  				ctx.strokeStyle = "black";
  				ctx.lineWidth = width*1.5;
  				ctx.stroke();
  				ctx.strokeStyle = "red";
  				ctx.lineWidth = width;
  				ctx.stroke();
  				ctx.closePath();
  			}
  		}
  
  		// end draw
  		ctx.restore();
  
  		return err;
  	};
  
  // private methods
  	/**
  	 * Calculate and store optimization data about the desired path to improve performance and accuracy of positions.
  	 * @param {Object} source The guide data provided to the tween call
  	 * @param {Object} storage the guide data used by the step calls and plugin to do the job, will be overwritten
  	 * @returns {undefined|String} Can return an error if unable to generate the data.
  	 * @private
  	 */
  	s._solveGuideData = function(source, storage) {
  		var err = undefined;
  		if(err = s.debug(source)) { return err; }
  
  		var path = storage.path = source.path;
  		var orient = storage.orient = source.orient;
  		storage.subLines = [];
  		storage.totalLength = 0;
  		storage.startOffsetRot = 0;
  		storage.deltaRotation = 0;
  		storage.startData = {ratio: 0};
  		storage.endData = {ratio: 1};
  		storage.animSpan = 1;
  
  		var pathLength = path.length;
  
  		var precision = 10;
  		var sx,sy, cx,cy, ex,ey, i,j, len, temp = {};
  
  		sx = path[0];		sy = path[1];
  
  		// get the data for each curve
  		for(i=2; i < pathLength; i+=4) {
  			cx = path[i];			cy = path[i+1];
  			ex = path[i+2];			ey = path[i+3];
  
  			var subLine = {
  				weightings: [],
  				estLength: 0,
  				portion: 0
  			};
  
  			var subX = sx, subY = sy;
  			// get the distance data for each point
  			for(j=1; j <= precision;j++) {	// we need to evaluate t = 1 not t = 0
  				s._getParamsForCurve(sx,sy, cx,cy, ex,ey, j/precision, false, temp);
  
  				var dx = temp.x - subX, dy = temp.y - subY;
  				len = Math.sqrt(dx*dx + dy*dy);
  				subLine.weightings.push(len);
  				subLine.estLength += len;
  
  				subX = temp.x;
  				subY = temp.y;
  			}
  
  			// figure out full lengths
  			storage.totalLength += subLine.estLength;
  
  			// use length to figure out proportional weightings
  			for(j=0; j < precision; j++) {
  				len = subLine.estLength;
  				subLine.weightings[j] = subLine.weightings[j] / len;
  			}
  
  			storage.subLines.push(subLine);
  			sx = ex;
  			sy = ey;
  		}
  
  		// use length to figure out proportional weightings
  		len = storage.totalLength;
  		var l = storage.subLines.length;
  		for(i=0; i<l; i++) {
  			storage.subLines[i].portion = storage.subLines[i].estLength / len;
  		}
  
  		// determine start and end data
  		var startRatio = isNaN(source.start) ? 0 : source.start;
  		var endRatio = isNaN(source.end) ? 1 : source.end;
  		s._ratioToPositionData(startRatio, storage, storage.startData);
  		s._ratioToPositionData(endRatio, storage, storage.endData);
  
  		// this has to be done last else the prev ratios will be out of place
  		storage.startData.ratio = startRatio;
  		storage.endData.ratio = endRatio;
  		storage.animSpan = storage.endData.ratio - storage.startData.ratio;
  	};
  
  	/**
  	 * Convert a percentage along the line into, a local line (start, control, end) t-value for calculation.
  	 * @param {Number} ratio The (euclidean distance) percentage into the whole curve.
  	 * @param {Object} guideData All the information describing the guide to be followed.
  	 * @param {Object} output Object to save output properties of x,y, and rotation onto.
  	 * @returns {Object} The output object, useful for isolated calls.
  	 * @private
  	 */
  	s._ratioToPositionData = function(ratio, guideData, output) {
  		var lineSegments = guideData.subLines;
  
  		var i,l, t, test, target;
  
  		var look = 0;
  		var precision = 10;
  		var effRatio = (ratio * guideData.animSpan) + guideData.startData.ratio;
  
  		// find subline
  		l = lineSegments.length;
  		for(i=0; i<l; i++) {
  			test = lineSegments[i].portion;
  			if(look + test >= effRatio){ target = i; break; }
  			look += test;
  		}
  		if(target === undefined) { target = l-1;  look -= test; }
  
  		// find midline weighting
  		var subLines = lineSegments[target].weightings;
  		var portion = test;
  		l = subLines.length;
  		for(i=0; i<l; i++) {
  			test = subLines[i] * portion;
  			if(look + test >= effRatio){ break; }
  			look += test;
  		}
  
  		// translate the subline index into a position in the path data
  		target = (target*4) + 2;
  		// take the distance we've covered in our ratio, and scale it to distance into the weightings
  		t = (i/precision) + (((effRatio-look) / test) * (1/precision));
  
  		// position
  		var pathData = guideData.path;
  		s._getParamsForCurve(
  			pathData[target-2],			pathData[target-1],
  			pathData[target],			pathData[target+1],
  			pathData[target+2],			pathData[target+3],
  			t,
  			guideData.orient,
  			output
  		);
  
  		if(guideData.orient) {
  			if(ratio >= 0.99999 && ratio <= 1.00001 && guideData.endAbsRot !== undefined) {
  				output.rotation = guideData.endAbsRot;
  			} else {
  				output.rotation += guideData.startOffsetRot + (ratio * guideData.deltaRotation);
  			}
  		}
  
  		return output;
  	};
  
  	/**
  	 * For a given quadratic bezier t-value, what is the position and rotation. Save it onto the output object.
  	 * @param {Number} sx Start x.
  	 * @param {Number} sy Start y.
  	 * @param {Number} cx Control x.
  	 * @param {Number} cy Control y.
  	 * @param {Number} ex End x.
  	 * @param {Number} ey End y.
  	 * @param {Number} t T value (parametric distance into curve).
  	 * @param {Boolean} orient Save rotation data.
  	 * @param {Object} output Object to save output properties of x,y, and rotation onto.
  	 * @private
  	 */
  	s._getParamsForCurve = function(sx,sy, cx,cy, ex,ey, t, orient, output) {
  		var inv = 1 - t;
  
  		// finding a point on a bezier curve
  		output.x =	inv*inv * sx + 2 * inv * t * cx + t*t * ex;
  		output.y =	inv*inv * sy + 2 * inv * t * cy + t*t * ey;
  
  		// finding an angle on a bezier curve
  		if(orient) {
  			// convert from radians back to degrees
  			output.rotation = 57.2957795 * Math.atan2(
  				(cy - sy)*inv + (ey - cy)*t,
  				(cx - sx)*inv + (ex - cx)*t
  			);
  		}
  	};
  
  	/**
  	 * Perform a check to validate path information so plugin can avoid later error checking.
  	 * @param {Object} guideData All the information describing the guide to be followed.
  	 * @returns {undefined|String} The problem found, or undefined if no problems.
  	 * @private
  	 */
  	s._findPathProblems = function(guideData) {
  		var path = guideData.path;
  		var valueCount = (path && path.length) || 0;	// ensure this is a number to simplify later logic
  		if(valueCount < 6 || (valueCount-2) % 4) {
  			var message =	"\tCannot parse 'path' array due to invalid number of entries in path. ";
  			message +=		"There should be an odd number of points, at least 3 points, and 2 entries per point (x & y). ";
  			message +=		"See 'CanvasRenderingContext2D.quadraticCurveTo' for details as 'path' models a quadratic bezier.\n\n";
  			message +=		"Only [ "+ valueCount +" ] values found. Expected: "+ Math.max(Math.ceil((valueCount-2)/4)*4+2, 6); //6, 10, 14,...
  			return message;
  		}
  
  		for(var i=0; i<valueCount; i++) {
  			if(isNaN(path[i])){
  				return "All data in path array must be numeric";
  			}
  		}
  
  		var start = guideData.start;
  		if(isNaN(start) && !(start === undefined)/* || start < 0 || start > 1*/) {	// outside 0-1 is unpredictable, but not breaking
  			return "'start' out of bounds. Expected 0 to 1, got: "+ start;
  		}
  		var end = guideData.end;
  		if(isNaN(end) && (end !== undefined)/* || end < 0 || end > 1*/) {	// outside 0-1 is unpredictable, but not breaking
  			return "'end' out of bounds. Expected 0 to 1, got: "+ end;
  		}
  
  		var orient = guideData.orient;
  		if(orient) { // mirror the check used elsewhere
  			if(orient != "fixed" && orient != "auto" && orient != "cw" && orient != "ccw") {
  				return 'Invalid orientation value. Expected ["fixed", "auto", "cw", "ccw", undefined], got: '+ orient;
  			}
  		}
  
  		return undefined;
  	};
  
  	createjs.MotionGuidePlugin = MotionGuidePlugin;
  
  }());
  