package com.robertpenner.geom
{
	import com.robertpenner.utils.Degree;
	
	import flash.geom.Point;
	
	/**
	 * The Vector class is designed to represent vectors and points
	 * in two-dimensional space. Vectors can be added together,
	 * scaled, rotated, and otherwise manipulated with these methods.
	 * 
	 * @author Robert Penner ( AS 1.0 )
	 *  2002 Robert Penner
	 * http://www.robertpenner.com
	 * 
	 * @author Mark Walters ( AS 2.0 and AS 3.0 )
	 *  2007 DigitalFlipbook
	 * http://www.digitalflipbook.com
	 * 
	 * @langversion	ActionScript 3.0
	 * @playerversion Flash 9
	 */
	public class Vector
	{
	    //--------------------------------------------------------------------------
	    //
	    //  Class methods
	    //
	    //--------------------------------------------------------------------------
	    
		/**
		 * Adds the coordinates of one vector to the coordinates of another vector to create a new vector.
		 * 
		 * @param	v1	The first vector.
		 * @param	v2	The second vector that is added to the first vector.
		 * 
		 * @return	The new vector.
		 * 
		 * @example
		 * <listing>
		 * var position:Vector = new Vector( 4, 1 );
		 * var velocity:Vector = new Vector( -2, 0 );
		 * var newPosition:Vector = Vector.add( position, velocity );
		 * trace( newPosition );
		 * </listing>
		 */
		public static function add( v1:Vector, v2:Vector ):Vector
		{
			return new Vector( v1.x + v2.x, v1.y + v2.y );
		}
		
		/**
		 * Subtracts the coordinates of one vector from the coordinates of another vector to create a new vector.
		 * 
		 * @param	v1	The first vector.
		 * @param	v2	The second vector that is subtracted from the first vector.
		 * 
		 * @return	The new vector.
		 * 
		 * @example
		 * <listing>
		 * var pointA:Vector = new Vector( 0, 1 );
		 * var pointB:Vector = new Vector( -2, 0 );
		 * var displacement:Vector = Vector.subtract( pointB, pointA );
		 * trace( displacement );
		 * </listing>
		 */
		public static function subtract( v1:Vector, v2:Vector ):Vector
		{
			return new Vector( v1.x - v2.x, v1.y - v2.y );
		}
		
		/**
		 * Reverses the direction of a vector and returns the result as a new vector.
		 * 
		 * @param	v	The vector to reverse.
		 * 
		 * @return	The reversed vector.
		 * 
		 * @example
		 * <listing>
		 * var forward:Vector = new Vector( 99, 0 );
		 * var backward:Vector = Vector.reverse( forward );
		 * trace( backward );
		 * </listing>
		 */
		public static function reverse( v:Vector ):Vector
		{
			return new Vector( -v.x, -v.y );
		}
		
		/**
		 * Scales the length of a vector by a scale factor and returns the result of the scale as a new vector.
		 * 
		 * @param	v	The vector to scale.
		 * @param	s	The scale factor to multiply the vector by.
		 * 
		 * @return	The scaled vector.
		 * 
		 * @example
		 * <listing>
		 * var windForce:Vector = new Vector( -2, 1 );
		 * var galeForce:Vector = Vector.scale( windForce, 2 ); //strong wind
		 * trace( galeForce );
		 * </listing>
		 */
		public static function scale( v:Vector, s:Number ):Vector
		{
			return new Vector( v.x * s, v.y * s );
		}
		
		/**
		 * Rotates the angle of a vector by a certain amount of degrees and returns the result of the rotation as a new vector.
		 * 
		 * @param	v	The vector to rotate.
		 * @param	ang		The amount of degrees that the vector will be rotated by.
		 * 
		 * @return	The rotated vector.
		 * 
		 * @example
		 * <listing>
		 * var direction:Vector = new Vector( 5, 5 );
		 * var newDirection:Vector = Vector.rotate( direction, 10 );
		 * trace( newDirection.angle );
		 * </listing>
		 */
		public static function rotate( v:Vector, ang:Number ):Vector
		{
			var rv:Vector = new Vector( v.x, v.y );
			rv.rotate( ang );
			return rv;
		}
		
		/**
		 * Returns the distance between two vectors.
		 * 
		 * @param	v1	The first vector.
		 * @param	v2	The second vector.
		 * 
		 * @return	The distance between the first vector and the second vector.
		 * 
		 * @example
		 * <listing>
		 * var v1:Vector = new Vector( 10, 10 );
		 * var v2:Vector = new Vector( 20, 20 );
		 * var distance:Number = Vector.distance( v1, v2 );
		 * trace( distance );
		 * </listing>
		 */
		public static function distance( v1:Vector, v2:Vector ):Number
		{
			var dx:Number = v2.x - v1.x;
			var dy:Number = v2.y - v1.y;
			return Math.sqrt( dx*dx + dy*dy );
		}
		
		/**
		 * Returns the angle between two vectors.
		 * 
		 * @param	v1	The first vector.
		 * @param	v2	The second vector.
		 * 
		 * @return	The angle between the first vector and the second vector.
		 * 
		 * @example
		 * <listing>
		 * var pullForce:Vector = new Vector( 4, 0 );
		 * var frictionForce:Vector = new Vector( -1, 0 );
		 * var theta:Number = Vector.angleBetween( pullForce, frictionForce );
		 * trace( theta );
		 * </listing>
		 */
		public static function angleBetween( v1:Vector, v2:Vector ):Number
		{
			var dp:Number = v1.dot( v2 );
			var cosAngle:Number = dp / ( v1.length * v2.length );
			return Degree.acosD( cosAngle );
		}
		
		/**
		 * Converts a point object to a vector.
		 * 
		 * @param	p	The point to convert.
		 * 
		 * @return	The converted point as a vector.
		 * 
		 * @example
		 * <listing>
		 * var p:Point = new Point( 4, 2 );
		 * var v:Vector = Vector.pointToVector( p );
		 * trace( v );
		 * </listing>
		 */
		public static function pointToVector( p:Point ):Vector
		{
			return new Vector( p.x, p.y );
		}
		
		/**
		 * Converts a vector object to a point.
		 * 
		 * @param	v	The vector to convert.
		 * 
		 * @return	The converted vector as a point.
		 * 
		 * @example
		 * <listing>
		 * var v:Vector = new Vector( 4, 2 );
		 * var p:Point = Vector.vectorToPoint( v );
		 * trace( p );
		 * </listing>
		 */
		public static function vectorToPoint( v:Vector ):Point
		{
			return new Point( v.x, v.y );
		}
		
	    //--------------------------------------------------------------------------
	    //
	    //  Constructor
	    //
	    //--------------------------------------------------------------------------
		
		/**
		 * Constructor.
		 * 
		 * @param	x	The x value of the vector.
		 * @param	y	The y value of the vector.
		 * 
		 * @example
		 * <listing>
		 * var v:Vector = new Vector( -9, 4 );
		 * trace( v.x );
		 * trace( v.y );
		 * </listing>
		 */
		public function Vector( x:Number=0, y:Number=0 )
		{
			this.x = x;
			this.y = y;
		}
		
		//--------------------------------------------------------------------------
		//
		//  Properties
		//
		//--------------------------------------------------------------------------
		
		//----------------------------------
		//  x
		//----------------------------------
		
		/**
		 *  @private
		 *  Storage for the x property.
		 */
		private var _x:Number;
		
		/**
		 * The x property of the vector object.
		 * 
		 * @example
		 * <listing>
		 * v.x = 9;
		 * </listing>
		 */
		public function get x():Number
		{
			return _x;
		}
		
		/**
		 * @private
		 */
		public function set x( x:Number ):void
		{
			_x = x;
		}
		
		//----------------------------------
		//  y
		//----------------------------------
		
		/**
		 *  @private
		 *  Storage for the y property.
		 */
		private var _y:Number;
		
		/**
		 * The y property of the vector object.
		 * 
		 * @example
		 * <listing>
		 * v.y = -4;
		 * </listing>
		 */
		public function get y():Number
		{
			return _y;
		}
		
		/**
		 * @private
		 */
		public function set y( y:Number ):void
		{
			_y = y;
		}
		
		//----------------------------------
		//  length
		//----------------------------------
		
		/**
		 * The length (or magnitude) of the vector object.
		 * 
		 * @example
		 * <listing>
		 * var velocity:Vector = new Vector( 3, 4 );
		 * var newSpeed:Number = 10;
		 * velocity.length = newSpeed;
		 * trace( velocity );
		 * trace( velocity.length );
		 * </listing>
		 */
		public function get length():Number
		{
			return Math.sqrt( x*x + y*y );
		}
		
		/**
		 * @private
		 */
		public function set length( len:Number ):void
		{
			var r:Number = length;
			r ? scale( len/r ) : x = len;
		}
		
		//----------------------------------
		//  angle
		//----------------------------------
		
		/**
		 * The angle of the vector object.
		 * 
		 * @example
		 * <listing>
		 * var destination:Vector = new Vector( 5, 5 );
		 * var compassBearing:Number = destination.angle;
		 * trace( compassBearing );
		 * </listing>
		 */
		public function get angle():Number
		{
			return Degree.atan2D( y, x );
		}
		
		/**
		 * @private
		 */
		public function set angle( ang:Number ):void
		{
			var r:Number = length;
			x = r * Degree.cosD( ang );
			y = r * Degree.sinD( ang );
		}
		
		//----------------------------------
		//  normal
		//----------------------------------
		
		/**
		 * Finds a normal for the current vector.
		 * 
		 * @return	Returns the vector that is the normal to the current vector.
		 * 
		 * @example
		 * <listing>
		 * var wallDirection:Vector = new Vector( 3, 5 );
		 * var forceDirection:Vector = wallDirection.normal;
		 * trace( forceDirection );
		 * </listing>
		 */
		public function get normal():Vector
		{
			return new Vector( -y, x );
		}
		
	    //--------------------------------------------------------------------------
	    //
	    //  Methods
	    //
	    //--------------------------------------------------------------------------
		
		/**
		 * Reinitializes the vector object.
		 * 
		 * @param	x	The x value of the vector.
		 * @param	y	The y value of the vector.
		 * 
		 * @example
		 * <listing>
		 * var velocity:Vector = new Vector( 5, 1 );
		 * velocity.reset( -9, 7 );
		 * trace( velocity );
		 * </listing>
		 */
		public function reset( x:Number=0, y:Number=0 ):void
		{
			this.x = x;
			this.y = y;
		}
		
		/**
		 * Returns a new vector object containing the x and y values of the current vector.
		 * 
		 * @return	A copy of the current vector object.
		 * 
		 * @example
		 * <listing>
		 * var forceA:Vector = new Vector( 2, 4 );
		 * var forceB:Vector = forceA.clone();
		 * trace( forceA );
		 * trace( forceB );
		 * trace( forceA.equals( forceB ) );
		 * </listing>
		 */
		public function clone():Vector
		{
			return new Vector( x, y );
		}
		
		/**
		 * Determines whether two vectors are equal.
		 * 
		 * @param	v	The vector to be compared.
		 * 
		 * @return	A value of <code>true</code> if the object is equal to this Vector object; <code>false</code> if it is not equal.
		 * 
		 * @example
		 * <listing>
		 * var forceA:Vector = new Vector( 2, 4 );
		 * var forceB:Vector = forceA.clone();
		 * trace( forceB.equals( forceA ) );
		 * forceA.reset( 0, 0 );
		 * trace( forceB.equals( forceA ) );
		 * </listing>
		 */
		public function equals( v:Vector ):Boolean
		{
			return ( x == v.x && y == v.y );
		}
		
		/**
		 * Adds the coordinates of another vector to the coordinates of this vector.
		 * 
		 * @param	v	The vector to be added.
		 * 
		 * @example
		 * <listing>
		 * var position:Vector = new Vector( 1, 3 );
		 * var velocity:Vector = new Vector( 3, 0 );
		 * position.add( velocity );
		 * trace( position );
		 * </listing>
		 */
		public function add( v:Vector ):void
		{
			x += v.x;
			y += v.y;
		}
		
		/**
		 * Subtracts the coordinates of another vector from the coordinates of this vector.
		 * 
		 * @param	v	The vector to be subtracted.
		 * 
		 * @example
		 * <listing>
		 * var forceA:Vector = new Vector( 1, 1 );
		 * var forceB:Vector = new Vector( -2, -1 );
		 * forceA.subtract( forceB );
		 * trace( forceA );
		 * </listing>
		 */
		public function subtract( v:Vector ):void
		{
			x -= v.x;
			y -= v.y;
		}
		
		/**
		 * Reverses the direction of the current vector.
		 * 
		 * @example
		 * <listing>
		 * var direction:Vector = new Vector( 2, 3 );
		 * direction.reverse();
		 * trace( direction );
		 * </listing>
		 */
		public function reverse():void
		{
			x = -x;
			y = -y;
		}
		
		/**
		 * Scales the length of the current vector object by a scale factor.
		 * 
		 * @param	s	The scale factor to multiply the current vector by.
		 * 
		 * @example
		 * <listing>
		 * var windForce:Vector = new Vector( 2, 3 );
		 * windForce.scale( 2 );
		 * trace( windForce );
		 * </listing>
		 */
		public function scale( s:Number ):void
		{
			x *= s;
			y *= s;
		}
		
		/**
		 * Rotates the angle of the current vector object by a certain amount of degrees.
		 * 
		 * @param	ang		The amount of degrees that the current vector object will be rotated by.
		 * 
		 * @example
		 * <listing>
		 * var direction:Vector = new Vector( 5, 5 );
		 * trace( direction.angle );
		 * direction.rotate( -90 );
		 * trace( direction );
		 * trace( direction.angle );
		 * </listing>
		 */
		public function rotate( ang:Number ):void
		{
			var ca:Number = Degree.cosD( ang );
			var sa:Number = Degree.sinD( ang );
	
			var rx:Number = x * ca - y * sa;
			var ry:Number = x * sa + y * ca;
			x = rx;
			y = ry;
		}
		
		/**
		 * Calculates the dot product of the current vector with another vector.
		 * 
		 * @param	v	The vector to multiply the current vector by.
		 * 
		 * @return	The dot product.
		 * 
		 * @example
		 * <listing>
		 * var v1:Vector = new Vector( 2, 3 );
		 * var v2:Vector = new Vector( 4, 5 );
		 * trace( v1.dot( v2 ) );
		 * trace( v2.dot( v1 ) );
		 * </listing>
		 */
		public function dot( v:Vector ):Number
		{
			return x * v.x + y * v.y;
		}
		
		/**
		 * Determines whether the current vector is the normal of (or perpendicular to) another vector.
		 * 
		 * @param	v	The vector object to check perpendicularity against.
		 * @return	A value of <code>true</code> if the dot product is zero; <code>false</code> if it is not zero.
		 * 
		 * @example
		 * <listing>
		 * var goingLeft:Vector = new Vector( -3, 0 );
		 * var goingRight:Vector = new Vector( 5, 0 );
		 * trace( goingLeft.isNormalTo( goingRight ) );
		 * var goingUp:Vector = new Vector( 0, 8 );
		 * trace( goingUp.isNormalTo( goingLeft ) );
		 * </listing>
		 */
		public function isNormalTo( v:Vector ):Boolean
		{
			return( dot( v ) == 0 );
		}
		
		/**
		 * Normalizes the current vector to a length (or magnitude) of 1.
		 * Normalized vectors are sometimes referred to as unit vectors.
		 * 
		 * @example
		 * <listing>
		 * var v:Vector = new Vector( 2, 3 );
		 * v.normalize();
		 * trace( v.length );
		 * </listing>
		 */
		public function normalize():void
		{
			var len:Number = length;
			
			if( len != 0 && len != 1)
			{
				x /= len;
				y /= len;
			}
		}
		
		/**
		 * Returns a string representation of the vector object.
		 * 
		 * @return	The string representation of the vector object.
		 * 
		 * @example
		 * <listing>
		 * var position:Vector = new Vector( 5, 8 );
		 * trace( position.toString() );
		 * trace( position );
		 * </listing>
		 */
		public function toString():String
		{
			var rx:Number = Math.round( x * 1000 ) / 1000;
			var ry:Number = Math.round( y * 1000 ) / 1000;
			return "(x=" + rx + ", y=" + ry + ")";
		}
		
	}
	
}