/**
 * VERSION: 1.01
 * DATE: 10/18/2009
 * ACTIONSCRIPT VERSION: 3.0 
 * UPDATES AND DOCUMENTATION AT: http://www.TweenMax.com
 **/
package com.greensock.plugins {
    import com.greensock.TweenLite;
    import com.greensock.core.SimpleTimeline;
    import flash.display.DisplayObject;
/**
 * Provides simple physics functionality for tweening a DisplayObject's x and y coordinates based on a
 * combination of velocity, angle, gravity, acceleration, accelerationAngle, and/or friction. It is not intended
 * to replace a full-blown physics engine and does not offer collision detection, but serves 
 * as a way to easily create interesting physics-based effects with the GreenSock tweening platform. Parameters
 * are not intended to be dynamically updateable, but one unique convenience is that everything is reverseable. 
 * So if you spawn a bunch of particle tweens, for example, and throw them into a TimelineLite, you could
 * simply call reverse() on the timeline to watch the particles retrace their steps right back to the beginning. 
 * Keep in mind that any easing equation you define for your tween will be completely ignored for these properties.
 *     <ul>
 *         <li><b>velocity : Number</b> - the initial velocity of the object measured in pixels per time 
 *                                 unit (usually seconds, but for tweens where useFrames is true, it would 
 *                                 be measured in frames). The default is zero.</li>
 *         <li><b>angle : Number</b> - the initial angle (in degrees) at which the object is traveling. Only
 *                                 pertinent when a velocity is defined. For example, if the object should 
 *                                 start out traveling at -60 degrees (towards the upper right), the angle
 *                                 would be -60. The default is zero.</li>
 *         <li><b>gravity : Number</b> - the amount of downwards acceleration applied to the object, measured
 *                                 in pixels per time unit (usually seconds, but for tweens where useFrames 
 *                                 is true, it would be measured in frames). You can <b>either</b> use <code>gravity</code>
 *                                 <b>or</b> <code>acceleration</code>, not both because gravity is the same thing
 *                                 as acceleration applied at an <code>accelerationAngle</code> of 90. Think of <code>gravity</code>
 *                                 as a convenience property that automatically sets the <code>accelerationAngle</code> 
 *                                 for you.</li>
 *         <li><b>acceleration : Number</b> - the amount of acceleration applied to the object, measured
 *                                 in pixels per time unit (usually seconds, but for tweens where useFrames 
 *                                 is true, it would be measured in frames). To apply the acceleration in a specific
 *                                 direction that is different than the <code>angle</code>, use the <code>accelerationAngle</code>
 *                                 property. You can <b>either</b> use <code>gravity</code>
 *                                 <b>or</b> <code>acceleration</code>, not both because gravity is the same thing
 *                                 as acceleration applied at an <code>accelerationAngle</code> of 90.</li>
 *         <li><b>accelerationAngle : Number</b> - the angle at which acceleration is applied (if any), measured in degrees. 
 *                                 So if, for example, you want the object to accelerate towards the left side of the
 *                                 screen, you'd use an <code>accelerationAngle</code> of 180. If you define a
 *                                 <code>gravity</code> value, it will automatically set the <code>accelerationAngle</code>
 *                                 to 90 (downwards).</li>
 *         <li><b>friction : Number</b> - a value between 0 and 1 where 0 is no friction, 0.08 is a small amount of
 *                                 friction, and 1 will completely prevent any movement. This is not meant to be precise or 
 *                                 scientific in any way, but rather serves as an easy way to apply a friction-like
 *                                 physics effect to your tween. Generally it is best to experiment with this number a bit.
 *                                 Also note that friction requires more processing than physics tweens without any friction.</li>
 *     </ul><br />
 * 
 * 
 * <b>USAGE:</b><br /><br />
 * <code>
 *         import com.greensock.TweenLite; <br />
 *         import com.greensock.plugins.TweenPlugin; <br />
 *         import com.greensock.plugins.Physics2DPlugin; <br />
 *         TweenPlugin.activate([Physics2DPlugin]); //activation is permanent in the SWF, so this line only needs to be run once.<br /><br />
 * 
 *         TweenLite.to(mc, 2, {physics2D:{velocity:300, angle:-60, gravity:400}}); <br /><br />
 *         
 *         //--OR--<br /><br />
 * 
 *         TweenLite.to(mc, 2, {physics2D:{velocity:300, angle:-60, friction:0.1}}); <br /><br />
 *         
 *         //--OR--<br /><br />
 * 
 *         TweenLite.to(mc, 2, {physics2D:{velocity:300, angle:-60, acceleration:50, accelerationAngle:180}}); <br /><br />
 * </code>
 * 
 * Physics2DPlugin is a Club GreenSock membership benefit. You must have a valid membership to use this class
 * without violating the terms of use. Visit http://blog.greensock.com/club/ to sign up or get more details.<br /><br />
 * 
 * <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 Physics2DPlugin extends TweenPlugin {
        /** @private **/
        public static const API:Number = 1.0; //If the API/Framework for plugins changes in the future, this number helps determine compatibility
        /** @private precomputed for speed **/
        private static const _DEG2RAD:Number = Math.PI / 180;
        
        /** @private **/
        protected var _tween:TweenLite;
        /** @private **/
        protected var _target:DisplayObject;
        /** @private **/
        protected var _x:Physics2DProp;
        /** @private **/
        protected var _y:Physics2DProp;
        /** @private **/
        protected var _skipX:Boolean;
        /** @private **/
        protected var _skipY:Boolean;
        /** @private **/
        protected var _friction:Number = 1;
        /** @private **/
        protected var _step:uint; 
        /** @private for tweens with friction, we need to iterate through steps. frames-based tweens will iterate once per frame, and seconds-based tweens will iterate 30 times per second. **/
        protected var _stepsPerTimeUnit:uint = 30;
        
        
        public function Physics2DPlugin() {
            super();
            this.propName = "physics2D"; //name of the special property that the plugin should intercept/manage
            this.overwriteProps = ["x", "y"];
        }
        
        /** @private **/
        override public function onInitTween(target:Object, value:*, tween:TweenLite):Boolean {
            if (!(target is DisplayObject)) {
                trace("Tween Error: physics2D requires that the target be a DisplayObject.");
                return false;
            }
            _target = DisplayObject(target);
            _tween = tween;
            _step = 0;
            var tl:SimpleTimeline = _tween.timeline;
            while (tl.timeline) {
                tl = tl.timeline;
            }
            if (tl == TweenLite.rootFramesTimeline) { //indicates the tween uses frames instead of seconds.
                _stepsPerTimeUnit = 1;
            }
            
            var angle:Number = Number(value.angle) || 0;
            var velocity:Number = Number(value.velocity) || 0;
            var acceleration:Number = Number(value.acceleration) || 0;
            var aAngle:Number = (value.accelerationAngle || value.accelerationAngle == 0) ? Number(value.accelerationAngle) : angle;
            if (value.gravity) {
                acceleration = Number(value.gravity);
                aAngle = 90;
            }
            angle *= _DEG2RAD;
            aAngle *= _DEG2RAD;
            
            if (value.friction) {
                _friction = 1 - Number(value.friction);
            }
            _x = new Physics2DProp(_target.x, Math.cos(angle) * velocity, Math.cos(aAngle) * acceleration, _stepsPerTimeUnit);
            _y = new Physics2DProp(_target.y, Math.sin(angle) * velocity, Math.sin(aAngle) * acceleration, _stepsPerTimeUnit);
            return true;
        }
        
        /** @private **/
        override public function killProps(lookup:Object):void {
            if ("x" in lookup) {
                _skipX = true;
            }
            if ("y" in lookup) {
                _skipY = true;
            }
            super.killProps(lookup);
        }
        
        /** @private **/
        override public function set changeFactor(n:Number):void {
            var time:Number = _tween.cachedTime, x:Number, y:Number;
            if (_friction == 1) {
                var tt:Number = time * time * 0.5;
                x = _x.start + ((_x.velocity * time) + (_x.acceleration * tt));
                y = _y.start + ((_y.velocity * time) + (_y.acceleration * tt));
            } else {
                var steps:int = int(time * _stepsPerTimeUnit) - _step;
                var remainder:Number = ((time * _stepsPerTimeUnit) % 1);
                var j:int;
                if (steps >= 0) {     //going forward
                    j = steps;
                    while (j--) {
                        _x.v += _x.a;
                        _y.v += _y.a;
                        _x.v *= _friction;
                        _y.v *= _friction;
                        _x.value += _x.v;
                        _y.value += _y.v;
                    }    
                    
                } else {             //going backwards
                    j = -steps;
                    while (j--) {
                        _x.value -= _x.v;
                        _y.value -= _y.v;
                        _x.v /= _friction;
                        _y.v /= _friction;
                        _x.v -= _x.a;
                        _y.v -= _y.a;
                    }
                }
                x = _x.value + (_x.v * remainder);
                y = _y.value + (_y.v * remainder);    
                _step += steps;
                
            }
            if (this.round) {
                x = (x > 0) ? int(x + 0.5) : int(x - 0.5); //4 times as fast as Math.round()
                y = (y > 0) ? int(y + 0.5) : int(y - 0.5); 
            }
            if (!_skipX) {
                _target.x = x;
            }
            if (!_skipY) {
                _target.y = y;
            }
            
        }
        
    }
}

internal class Physics2DProp {
    public var start:Number;
    public var velocity:Number;
    public var v:Number;
    public var a:Number;
    public var value:Number;
    public var acceleration:Number;
    
    public function Physics2DProp(start:Number, velocity:Number, acceleration:Number, stepsPerTimeUnit:uint) {
        this.start = this.value = start;
        this.velocity = velocity;
        this.v = this.velocity / stepsPerTimeUnit;
        if (acceleration || acceleration == 0) {
            this.acceleration = acceleration;
            this.a = this.acceleration / (stepsPerTimeUnit * stepsPerTimeUnit);
        } else {
            this.acceleration = this.a = 0;
        }
    }    

}