RectanglePath2D.as 8.79 KB
/**
 * VERSION: 0.2 (beta)
 * DATE: 2010-04-21
 * ACTIONSCRIPT VERSION: 3.0 
 * UPDATES AND DOCUMENTATION AT: http://www.GreenSock.com
 **/
package com.greensock.motionPaths {
	import flash.display.Graphics;
	import flash.geom.Matrix;
/**
 * A RectanglePath2D defines a rectangular path on which a PathFollower can be placed, making it simple to tween objects
 * along a rectangle's perimeter. A PathFollower's position along the path is described using its <code>progress</code> property, 
 * a value between 0 and 1 where 0 is at the beginning of the path (top left corner), and as the value increases, it
 * moves clockwise along the path so that 0.5 would be at the lower right corner, and 1 is all the way back at the 
 * upper left corner of the path. So to tween a PathFollower along the path, you can simply tween its
 * <code>progress</code> property. To tween ALL of the followers on the path at once, you can tween the 
 * RectanglePath2D's <code>progress</code> property. PathFollowers automatically wrap so that if the <code>progress</code> 
 * value exceeds 1 it continues at the beginning of the path.<br /><br />
 *  
 * Since RectanglePath2D extends the Shape class, you can add an instance to the display list to see a line representation
 * of the path drawn which can be helpful especially during the production phase. Use <code>lineStyle()</code> 
 * to adjust the color, thickness, and other attributes of the line that is drawn (or set the RectanglePath2D's 
 * <code>visible</code> property to false or don't add it to the display list if you don't want to see the line 
 * at all). You can also adjust all of its properties like <code>scaleX, scaleY, rotation, width, height, x,</code> 
 * and <code>y</code>. That means you can tween those values as well to achieve very dynamic, complex effects 
 * with ease.<br /><br />
 * 
 * @example Example AS3 code:<listing version="3.0">
import com.greensock.~~;
import com.greensock.motionPaths.~~;

//create a rectangular motion path at coordinates x:25, y:25 with a width of 150 and a height of 100
var rect:RectanglePath2D = new RectanglePath2D(25, 25, 150, 100, false);

//position the MovieClip "mc" at the beginning of the path (upper left corner), and reference the resulting PathFollower instance with a "follower" variable.
var follower:PathFollower = rect.addFollower(mc, 0);

//tween the follower clockwise along the path all the way to the end, one full revolution
TweenLite.to(follower, 2, {progress:1});

//tween the follower counter-clockwise by using a negative progress value
TweenLite.to(follower, 2, {progress:-1});
</listing>
 * 
 * <b>NOTES</b><br />
 * <ul>
 * 		<li>All followers' positions are automatically updated when you alter the MotionPath that they're following.</li>
 * 		<li>To tween all followers along the path at once, simply tween the MotionPath's <code>progress</code> 
 * 			property which will provide better performance than tweening each follower independently.</li>
 * </ul>
 * 
 * <b>Copyright 2010, GreenSock. All rights reserved.</b> This work is subject to the terms in <a href="http://www.greensock.com/terms_of_use.html">http://www.greensock.com/terms_of_use.html</a> or for corporate Club GreenSock members, the software agreement that was issued with the corporate membership.
 * 
 * @author Jack Doyle, jack@greensock.com
 */	
	public class RectanglePath2D extends MotionPath {		
		/** @private **/
		protected var _rawWidth:Number;
		/** @private **/
		protected var _rawHeight:Number;
		/** @private **/
		protected var _centerOrigin:Boolean;
		
		/**
		 * Constructor
		 * 
		 * @param x The x coordinate of the origin of the rectangle (typically its top left corner unless <code>centerOrigin</code> is <code>true</code>)
		 * @param y The y coordinate of the origin of the rectangle (typically its top left corner unless <code>centerOrigin</code> is <code>true</code>)
		 * @param rawWidth The width of the rectangle in its unrotated and unscaled state
		 * @param rawHeight The height of the rectangle in its unrotated and unscaled state
		 * @param centerOrigin To position the origin (registration point around which transformations occur) at the center of the rectangle instead of its upper left corner, set <code>centerOrigin</code> to <code>true</code> (it is false by default).
		 */
		public function RectanglePath2D(x:Number, y:Number, rawWidth:Number, rawHeight:Number, centerOrigin:Boolean=false) {
			super();
			_rawWidth = rawWidth;
			_rawHeight = rawHeight;
			_centerOrigin = centerOrigin;
			super.x = x;
			super.y = y;
		}
		
		/** @private **/
		override protected function renderAll():void {
			var xOffset:Number = _centerOrigin ? _rawWidth / -2 : 0;
			var yOffset:Number = _centerOrigin ? _rawHeight / -2 : 0;
			
			var length:Number, px:Number, py:Number, xFactor:Number, yFactor:Number;
			var m:Matrix = this.transform.matrix;
			var a:Number = m.a, b:Number = m.b, c:Number = m.c, d:Number = m.d, tx:Number = m.tx, ty:Number = m.ty;
			var f:PathFollower = _rootFollower;
			while (f) {
				px = xOffset;
				py = yOffset;
				if (f.cachedProgress < 0.5) {
					length = f.cachedProgress * (_rawWidth + _rawHeight) * 2;
					if (length > _rawWidth) { 	//top
						px += _rawWidth;
						py += length - _rawWidth;
						xFactor = 0;
						yFactor = _rawHeight;
					} else { 					//right
						px += length;
						xFactor = _rawWidth;
						yFactor = 0;
					}
				} else {
					length = (f.cachedProgress - 0.5) / 0.5 * (_rawWidth + _rawHeight);
					if (length <= _rawWidth) {	//bottom
						px += _rawWidth - length;
						py += _rawHeight;
						xFactor = -_rawWidth;
						yFactor = 0;
					} else {					//left
						py += _rawHeight - (length - _rawWidth);
						xFactor = 0;
						yFactor = -_rawHeight;
					}
				}
				
				f.target.x = px * a + py * c + tx;
				f.target.y = px * b + py * d + ty;
				
				if (f.autoRotate) {
					f.target.rotation = Math.atan2(xFactor * b + yFactor * d, xFactor * a + yFactor * c) * _RAD2DEG + f.rotationOffset;
				}
				
				f = f.cachedNext;
			}
			if (_redrawLine && this.visible && this.parent) {
				var g:Graphics = this.graphics;
				g.clear();
				g.lineStyle(_thickness, _color, _lineAlpha, _pixelHinting, _scaleMode, _caps, _joints, _miterLimit);
				g.drawRect(xOffset, yOffset, _rawWidth, _rawHeight);
				_redrawLine = false;
			}
		}
		
		/** @inheritDoc **/
		override public function renderObjectAt(target:Object, progress:Number, autoRotate:Boolean=false, rotationOffset:Number=0):void {
			if (progress > 1) {
				progress -= int(progress);
			} else if (progress < 0) {
				progress -= int(progress) - 1;
			}
			
			var px:Number = _centerOrigin ? _rawWidth / -2 : 0;
			var py:Number = _centerOrigin ? _rawHeight / -2 : 0;
			var length:Number, xFactor:Number, yFactor:Number;
			if (progress < 0.5) {
				length = progress * (_rawWidth + _rawHeight) * 2;
				if (length > _rawWidth) {
					px += _rawWidth;
					py += length - _rawWidth;
					xFactor = 0;
					yFactor = _rawHeight;
				} else {
					px += length;
					xFactor = _rawWidth;
					yFactor = 0;
				}
			} else {
				length = (progress - 0.5) / 0.5 * (_rawWidth + _rawHeight);
				if (length <= _rawWidth) {
					px += _rawWidth - length;
					py += _rawHeight;
					xFactor = -_rawWidth;
					yFactor = 0;
				} else {
					py += _rawHeight - (length - _rawWidth);
					xFactor = 0;
					yFactor = -_rawHeight;
				}
			}
			var m:Matrix = this.transform.matrix;
			target.x = px * m.a + py * m.c + m.tx;
			target.y = px * m.b + py * m.d + m.ty;
			
			if (autoRotate) {
				target.rotation = Math.atan2(xFactor * m.b + yFactor * m.d, xFactor * m.a + yFactor * m.c) * _RAD2DEG + rotationOffset;
			}
		}
		
		
//---- GETTERS / SETTERS ----------------------------------------------------------------------
		
		/** width of the rectangle in its unrotated, unscaled state (does not factor in any transformations like scaleX/scaleY/rotation) **/
		public function get rawWidth():Number {
			return _rawWidth;
		}
		public function set rawWidth(value:Number):void {
			_rawWidth = value;
			_redrawLine = true;
			renderAll();
		}
		
		/** height of the rectangle in its unrotated, unscaled state (does not factor in any transformations like scaleX/scaleY/rotation) **/
		public function get rawHeight():Number {
			return _rawHeight;
		}
		public function set rawHeight(value:Number):void {
			_rawHeight = value;
			_redrawLine = true;
			renderAll();
		}
		
		/** height of the rectangle in its unrotated, unscaled state (does not factor in any transformations like scaleX/scaleY/rotation) **/
		public function get centerOrigin():Boolean {
			return _centerOrigin;
		}
		public function set centerOrigin(value:Boolean):void {
			_centerOrigin;
			_redrawLine = true;
			renderAll();
		}
		
	}
}