/**
 * VERSION: 2.03
 * DATE: 2010-03-31
 * ACTIONSCRIPT VERSION: 3.0 
 * UPDATES AND DOCUMENTATION AT: http://www.TweenMax.com
 **/
package classes.greensock.plugins {
	import classes.com.greensock.*;
	import classes.com.greensock.core.*;
	
	import flash.display.*;
	import flash.filters.BlurFilter;
	import flash.geom.*;
	import flash.utils.getDefinitionByName;
/**
 * MotionBlurPlugin provides an easy way to apply a directional blur to a DisplayObject based on its velocity
 * and angle of movement in 2D (x/y). This creates a much more realistic effect than a standard BlurFilter for
 * several reasons:
 * <ol>
 * 		<li>A regular BlurFilter is limited to blurring horizontally and/or vertically whereas the motionBlur 
 * 		   gets applied at the angle at which the object is moving.</li>
 * 
 * 		<li>A BlurFilter tween has static start/end values whereas a motionBlur tween dynamically adjusts the
 * 			values on-the-fly during the tween based on the velocity of the object. So if you use a <code>Strong.easeInOut</code>
 * 			for example, the strength of the blur will start out low, then increase as the object moves faster, and 
 * 			reduce again towards the end of the tween.</li>
 * </ol>
 * 
 * motionBlur even works on bezier/bezierThrough tweens!<br /><br />
 * 
 * To accomplish the effect, MotionBlurPlugin creates a Bitmap that it places over the original object, changing 
 * alpha of the original to [almost] zero during the course of the tween. The original DisplayObject still follows the 
 * course of the tween, so MouseEvents are properly dispatched. You shouldn't notice any loss of interactivity. 
 * The DisplayObject can also have animated contents - MotionBlurPlugin automatically updates on every frame. 
 * Be aware, however, that as with most filter effects, MotionBlurPlugin is somewhat CPU-intensive, so it is not 
 * recommended that you tween large quantities of objects simultaneously. You can activate <code>fastMode</code>
 * to significantly speed up rendering if the object's contents and size/color doesn't need to change during the
 * course of the tween. <br /><br />
 * 
 * motionBlur recognizes the following properties:
 * <ul>
 * 		<li><b>strength : int</b> - Determines the strength of the blur. The default is 1. For a more powerful
 * 							blur, increase the number. Or reduce it to make the effect more subtle.</li>
 * 
 * 		<li><b>fastMode : Boolean</b> - Setting fastMode to <code>true</code> will significantly improve rendering
 * 						performance but it is only appropriate for situations when the target object's contents, 
 * 						size, color, filters, etc. do not need to change during the course of the tween. It works
 * 						by essentially taking a BitmapData snapshot of the target object at the beginning of the
 * 						tween and then reuses that throughout the tween, blurring it appropriately. The default
 * 						value for <code>fastMode</code> is <code>false</code>.</li>
 * 
 * 		<li><b>quality : int</b> - The lower the quality, the less CPU-intensive the effect will be. Options 
 * 							are 1, 2, or 3. The default is 2.</li>
 * 
 * 		<li><b>padding : int</b> - padding controls the amount of space around the edges of the target object that is included
 * 						in the BitmapData capture (the default is 10 pixels). If the target object has filters applied to 
 * 						it like a GlowFilter or DropShadowFilter that extend beyond the bounds of the object itself,
 * 						you might need to increase the padding to accommodate the filters. </li>
 * </ul>
 * 
 * You can optionally set motionBlur to the Boolean value of <code>true</code> in order to use the defaults. (see below for examples)<br /><br />
 * 
 * Also note that due to a bug in Flash, if you apply motionBlur to an object that was masked in the Flash IDE it won't work
 * properly - you must apply the mask via ActionScript instead (and set both the mask's and the masked object's cacheAsBitmap
 * property to true). And another bug in Flash prevents motionBlur from working on objects that have 3D properties applied, 
 * like <code>z, rotationY, rotationX,</code> or <code>rotationZ</code>.<br /><br />
 * 
 * <b>USAGE:</b><br /><br />
 * <code>
 * 		import com.greensock.~~; <br />
 * 		import com.greensock.plugins.~~; <br />
 * 		TweenPlugin.activate([MotionBlurPlugin]); //only do this once in your SWF to activate the plugin <br /><br />
 * 
 * 		TweenMax.to(mc, 2, {x:400, y:300, motionBlur:{strength:1.5, fastMode:true, padding:15}}); <br /><br />
 * 
 * 		//or to use the default values, you can simply pass in the Boolean "true" instead: <br />
 * 		TweenMax.to(mc, 2, {x:400, y:300, motionBlur:true}); <br /><br />
 * </code>
 * 
 * MotionBlurPlugin is a <a href="http://www.greensock.com/club/">Club GreenSock</a> membership benefit. 
 * You must have a valid membership to use this class without violating the terms of use. Visit 
 * <a href="http://www.greensock.com/club/">http://www.greensock.com/club/</a> 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 MotionBlurPlugin 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 **/
		private static const _DEG2RAD:Number = Math.PI / 180; //precomputation for speed
		/** @private **/
		private static const _RAD2DEG:Number = 180 / Math.PI; //precomputation for speed;
		/** @private **/
		private static const _point:Point = new Point(0, 0);
		/** @private **/
		private static const _ct:ColorTransform = new ColorTransform();
		/** @private **/
		private static var _classInitted:Boolean;
		/** @private **/
		private static var _isFlex:Boolean;
		
		/** @private **/
		protected var _target:DisplayObject;
		/** @private **/
		protected var _time:Number;
		/** @private **/
		protected var _xCurrent:Number;
		/** @private **/
		protected var _yCurrent:Number;
		/** @private **/
		protected var _bd:BitmapData;
		/** @private **/
		protected var _bitmap:Bitmap;
		/** @private **/
		protected var _strength:Number = 0.05;
		/** @private **/
		protected var _tween:TweenLite;
		/** @private **/
		protected var _blur:BlurFilter = new BlurFilter(0, 0, 2);
		/** @private **/
		protected var _matrix:Matrix = new Matrix();
		/** @private **/
		protected var _sprite:Sprite;
		/** @private **/
		protected var _rect:Rectangle;
		/** @private **/
		protected var _angle:Number;
		/** @private **/
		protected var _alpha:Number;
		/** @private **/
		protected var _xRef:Number; //we keep recording this value every time the _target moves at least 2 pixels in either direction in order to accurately determine the angle (small measurements don't produce accurate results).
		/** @private **/
		protected var _yRef:Number;
		/** @private **/
		protected var _mask:DisplayObject;
		/** @private **/
		protected var _padding:int;
		/** @private **/
		protected var _bdCache:BitmapData;
		/** @private **/
		protected var _rectCache:Rectangle;
		/** @private **/
		protected var _cos:Number;
		/** @private **/
		protected var _sin:Number;
		/** @private **/
		protected var _smoothing:Boolean;
		/** @private **/
		protected var _xOffset:Number;
		/** @private **/
		protected var _yOffset:Number;
		/** @private **/
		protected var _cached:Boolean;
		/** @private **/
		protected var _fastMode:Boolean;
		
		/** @private **/
		public function MotionBlurPlugin() {
			super();
			this.propName = "motionBlur"; //name of the special property that the plugin should intercept/manage
			this.overwriteProps = ["motionBlur"]; 
			this.onComplete = disable;
			this.onDisable = onTweenDisable;
			this.priority = -2; //so that the x/y/alpha tweens occur BEFORE the motion blur is applied (we need to determine the angle at which it moved first)
			this.activeDisable = true;
		}
		
		/** @private **/
		override public function onInitTween(target:Object, value:*, tween:TweenLite):Boolean {
			if (!(target is DisplayObject)) {
				throw new Error("motionBlur tweens only work for DisplayObjects.");
				return false;
			} else if (value == false) {
				_strength = 0;
			} else if (typeof(value) == "object") {
				_strength = (value.strength || 1) * 0.05;
				_blur.quality = int(value.quality) || 2;
				_fastMode = Boolean(value.fastMode == true);
			}
			if (!_classInitted) {
				try {
					_isFlex = Boolean(getDefinitionByName("mx.managers.SystemManager")); // SystemManager is the first display class created within a Flex application
				} catch (e:Error) {
					_isFlex = false;
				}
				_classInitted = true;
			}
			
			_target = target as DisplayObject;
			_tween = tween;
			_time = 0;
			_padding = ("padding" in value) ? int(value.padding) : 10;
			_smoothing = Boolean(_blur.quality > 1);
			
			_xCurrent = _xRef = _target.x;
			_yCurrent = _yRef = _target.y;
			_alpha = _target.alpha;
			
			if ("x" in _tween.propTweenLookup && "y" in _tween.propTweenLookup && !_tween.propTweenLookup.x.isPlugin && !_tween.propTweenLookup.y.isPlugin) { //if the tweens are plugins, like bezier or bezierThrough for example, we cannot assume the angle between the current _x/_y and the destination ones is what it should start at!
				_angle = Math.PI - Math.atan2(_tween.propTweenLookup.y.change, _tween.propTweenLookup.x.change);
			} else if (_tween.vars.x != null || _tween.vars.y != null) {
				var x:Number = _tween.vars.x || _target.x;
				var y:Number = _tween.vars.y || _target.y;
				_angle = Math.PI - Math.atan2((y - _target.y), (x - _target.x));
			} else {
				_angle = 0;
			}
			_cos = Math.cos(_angle);
			_sin = Math.sin(_angle);
			
			_bd = new BitmapData(_target.width + _padding * 2, _target.height + _padding * 2, true, 0x00FFFFFF);
			_bdCache = _bd.clone();
			_bitmap = new Bitmap(_bd);
			_bitmap.smoothing = _smoothing;
			_sprite = _isFlex ? new (getDefinitionByName("mx.core.UIComponent"))() : new Sprite();
			_sprite.mouseEnabled = _sprite.mouseChildren = false;			
			_sprite.addChild(_bitmap);
			_rectCache = new Rectangle(0, 0, _bd.width, _bd.height);
			_rect = _rectCache.clone();
			if (_target.mask) {
				_mask = _target.mask;
			}
			
			return true;
		}
		
		/** @private **/
		private function disable():void {
			if (_strength != 0) {
				_target.alpha = _alpha;
			}
			if (_sprite.parent) {
				_sprite.parent.removeChild(_sprite);
			}
			if (_mask) {
				_target.mask = _mask;
			}
		}
		
		/** @private **/
		private function onTweenDisable():void {
			if (_tween.cachedTime != _tween.cachedDuration && _tween.cachedTime != 0) { //if the tween is on a TimelineLite/Max that eventually completes, another tween might have affected the target's alpha in which case we don't want to mess with it - only disable() if it's mid-tween. Also remember that from() tweens will complete at a value of 0, not 1.
				disable();
			}
		}
		
		/** @private **/
		override public function set changeFactor(n:Number):void {
			var time:Number = (_tween.cachedTime - _time);
			if (time < 0) {
				time = -time; //faster than Math.abs(_tween.cachedTime - _time)
			}
			
			if (time < 0.0000001) {
				return; //number is too small - floating point errors will cause it to render incorrectly
			}
			
			var dx:Number = _target.x - _xCurrent;
			var dy:Number = _target.y - _yCurrent;
			var rx:Number = _target.x - _xRef;
			var ry:Number = _target.y - _yRef;
			_changeFactor = n;
			
			if (rx > 2 || ry > 2 || rx < -2 || ry < -2) { //setting a tolerance of 2 pixels helps eliminate floating point error funkiness.
				_angle = Math.PI - Math.atan2(ry, rx);
				_cos = Math.cos(_angle);
				_sin = Math.sin(_angle);
				_xRef = _target.x;
				_yRef = _target.y;
			}
					
			_blur.blurX = Math.sqrt(dx * dx + dy * dy) * _strength / time;
			
			_xCurrent = _target.x;
			_yCurrent = _target.y;
			_time = _tween.cachedTime;
			
			if (n == 0 || _target.parent == null) { 
				disable();
				return;
			}
			
			if (_sprite.parent != _target.parent && _target.parent) {
				_target.parent.addChildAt(_sprite, _target.parent.getChildIndex(_target));
				if (_mask) {
					_sprite.mask = _mask;
				}
			}
			
			if (!_fastMode || !_cached) {
				_target.x = _target.y = 20000; //get it away from everything else;
				var prevVisible:Boolean = _target.visible;
				_target.visible = true;
				var bounds:Rectangle = _target.getBounds(_target.parent);
				if (bounds.width + _blur.blurX * 2 > 2870) { //in case it's too big and would exceed the 2880 maximum in Flash
					_blur.blurX = (bounds.width >= 2870) ? 0 : (2870 - bounds.width) * 0.5;
				}
				
				_xOffset = 20000 - bounds.x + _padding;
				_yOffset = 20000 - bounds.y + _padding;
				bounds.width += _padding * 2;
				bounds.height += _padding * 2;
				
				if (bounds.height > _bdCache.height || bounds.width > _bdCache.width) {
					_bdCache = new BitmapData(bounds.width, bounds.height, true, 0x00FFFFFF);
					_rectCache = new Rectangle(0, 0, _bdCache.width, _bdCache.height);
				}
				
				_matrix.tx = _padding - bounds.x;
				_matrix.ty = _padding - bounds.y;
				_matrix.a = _matrix.d = 1;
				_matrix.b = _matrix.c = 0;
				
				bounds.x = bounds.y = 0;
				if (_target.alpha == 0.00390625) {
					_target.alpha = _alpha;
				} else { //means the tween is affecting alpha, so respect it.
					_alpha = _target.alpha;
				}
				
				_bdCache.fillRect(_rectCache, 0x00FFFFFF);
				_bdCache.draw(_target.parent, _matrix, _ct, "normal", bounds, _smoothing);
				
				_target.visible = prevVisible;
				_target.x = _xCurrent;
				_target.y = _yCurrent;
				
				_cached = true;
			} else if (_target.alpha != 0.00390625) {
				//means the tween is affecting alpha, so respect it.
				_alpha = _target.alpha;
			}
			_target.alpha = 0.00390625; //use 0.00390625 instead of 0 so that we can identify if it was changed outside of this plugin next time through. We were running into trouble with tweens of alpha to 0 not being able to make the final value because of the conditional logic in this plugin.
			
			_matrix.tx = _matrix.ty = 0;
			_matrix.a = _cos;
			_matrix.b = _sin;
			_matrix.c = -_sin;
			_matrix.d = _cos;
			
			var width:Number, height:Number, val:Number;
			if ((width = _matrix.a * _bdCache.width) < 0) {
				_matrix.tx = -width;
				width = -width;
			} 
			if ((val = _matrix.c * _bdCache.height) < 0) {
				_matrix.tx -= val;
				width -= val;
			} else {
				width += val;
			}
			if ((height = _matrix.d * _bdCache.height) < 0) {
				_matrix.ty = -height;
				height = -height;
			} 
			if ((val = _matrix.b * _bdCache.width) < 0) {
				_matrix.ty -= val;
				height -= val;
			} else {
				height += val;
			}
			
			width += _blur.blurX * 2;
			_matrix.tx += _blur.blurX;
			if (width > _bd.width || height > _bd.height) {
				_bd = _bitmap.bitmapData = new BitmapData(width, height, true, 0x00FFFFFF);
				_rect = new Rectangle(0, 0, _bd.width, _bd.height);
				_bitmap.smoothing = _smoothing;
			}
			
			_bd.fillRect(_rect, 0x00FFFFFF);
			_bd.draw(_bdCache, _matrix, _ct, "normal", _rect, _smoothing);
			_bd.applyFilter(_bd, _rect, _point, _blur);
			
			_bitmap.x = 0 - (_matrix.a * _xOffset + _matrix.c * _yOffset + _matrix.tx);
			_bitmap.y = 0 - (_matrix.d * _yOffset + _matrix.b * _xOffset + _matrix.ty);
			
			_matrix.b = -_sin;
			_matrix.c = _sin;
			_matrix.tx = _xCurrent;
			_matrix.ty = _yCurrent;
			
			_sprite.transform.matrix = _matrix;
			
		}
		
	}
}