cpVect.js

/* Copyright (c) 2007 Scott Lembcke
 * 
 * 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.
 */

// I'm using an array tuple here because (at time of writing) its about 3x faster
// than an object on firefox, and the same speed on chrome.

//var numVects = 0;

/**
 *
 * @class
 * @memberof	cp
 * @param	{number}	x
 * @param	{number}	y
 */
var Vect = cp.Vect = function(x, y)
{
	this.x = x;
	this.y = y;
	//numVects++;

//	var s = new Error().stack;
//	traces[s] = traces[s] ? traces[s]+1 : 1;
};

/**
 * @function
 * @memberof	cp
 * @param	{number}	x
 * @param	{number}	y
 * @return	{cp.Vect}
 */
cp.v = function (x,y) { return new Vect(x, y) };

/**
 * @constant	{cp.Vect}
 * @readonly
 */
var vzero = cp.vzero = new Vect(0,0);

// The functions below *could* be rewritten to be instance methods on Vect. I don't
// know how that would effect performance. For now, I'm keeping the JS similar to
// the original C code.

/// Vector dot product.
/**
 * Vector dot product.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @return	{number}
 */
var vdot = cp.v.dot = function(v1, v2)
{
	return v1.x*v2.x + v1.y*v2.y;
};

/**
 * @function
 * @param	{number}	x1
 * @param	{number}	y1
 * @param	{number}	x2
 * @param	{number}	y2
 * @return	{number}
 */
var vdot2 = function(x1, y1, x2, y2)
{
	return x1*x2 + y1*y2;
};

/// Returns the length of v.
/**
 * Returns the length of v.
 * 
 * @memberof	cp.v
 * @param	{cp.Vect}	v
 * @return	{number}	Returns the lenght of v.
 */
var vlength = cp.v.len = function(v)
{
	return Math.sqrt(vdot(v, v));
};

/**
 * @function
 * @memberof	cp.v
 * @param	{number}	x
 * @param	{number}	y
 * @return	{number}
 */
var vlength2 = cp.v.len2 = function(x, y)
{
	return Math.sqrt(x*x + y*y);
};

/// Check if two vectors are equal. (Be careful when comparing floating point numbers!)
/**
 * Check if two vectors are equal. (Be careful when comparing floating point numbers!)
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @return	{boolean}
 */
var veql = cp.v.eql = function(v1, v2)
{
	return (v1.x === v2.x && v1.y === v2.y);
};

/// Add two vectors
/**
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @return	{cp.Vect}
 */
var vadd = cp.v.add = function(v1, v2)
{
	return new Vect(v1.x + v2.x, v1.y + v2.y);
};

/**
 * @function
 * @param	{cp.Vect}	v2
 * @retrun	{cp.Vect}
 */
Vect.prototype.add = function(v2)
{
	this.x += v2.x;
	this.y += v2.y;
	return this;
};

/// Subtract two vectors.
/**
 * Subtract two vectors.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @return	{cp.Vect}
 */
var vsub = cp.v.sub = function(v1, v2)
{
	return new Vect(v1.x - v2.x, v1.y - v2.y);
};

/**
 * @function
 * @param	{cp.Vect}
 * @return	{cp.Vect}
 */
Vect.prototype.sub = function(v2)
{
	this.x -= v2.x;
	this.y -= v2.y;
	return this;
};

/// Negate a vector.
/**
 * Negate a vector.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v
 * @return	{cp.Vect}
 */
var vneg = cp.v.neg = function(v)
{
	return new Vect(-v.x, -v.y);
};

/**
 * @function
 * @return	{cp.Vect}
 */
Vect.prototype.neg = function()
{
	this.x = -this.x;
	this.y = -this.y;
	return this;
};

/// Scalar multiplication
/**
 * Scalar multiplication
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v
 * @param	{number}	s
 */
var vmult = cp.v.mult = function(v, s)
{
	return new Vect(v.x*s, v.y*s);
};

/**
 * @function
 * @param	{number}
 * @return	{cp.Vect}
 */
Vect.prototype.mult = function(s)
{
	this.x *= s;
	this.y *= s;
	return this;
};

/// 2D vector cross product analog.
/// The cross product of 2D vectors results in a 3D vector with only a z component.
/// This function returns the magnitude of the z value.
/**
 * 2D vector cross product analog.
 * The cross product of 2D vectors results in a 3D vector with only a z component.
 * This function returns the magnitude of the z value.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @return	{cp.Vect}
 */
var vcross = cp.v.cross = function(v1, v2)
{
	return v1.x*v2.y - v1.y*v2.x;
};

/**
 * @function
 * @param	{number}	x1
 * @param	{number}	y1
 * @param	{number}	x2
 * @param	{number}	y2
 * @return	{cp.Vect}
 */
var vcross2 = function(x1, y1, x2, y2)
{
	return x1*y2 - y1*x2;
};

/// Returns a perpendicular vector. (90 degree rotation)
/**
 * Returns a perpendicular vector. (90 degree rotation)
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v
 * @return	{cp.Vect}
 */
var vperp = cp.v.perp = function(v)
{
	return new Vect(-v.y, v.x);
};

/// Returns a perpendicular vector. (-90 degree rotation)
/**
 * Returns a perpendicular vector. (-90 degree rotation)
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v
 * @return	{cp.Vect}
 */
var vpvrperp = cp.v.pvrperp = function(v)
{
	return new Vect(v.y, -v.x);
};

/// Returns the vector projection of v1 onto v2.
/**
 * Returns the vector projection of v1 onto v2.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @return	{cp.Vect}
 */
var vproject = cp.v.project = function(v1, v2)
{
	return vmult(v2, vdot(v1, v2)/vlengthsq(v2));
};

/**
 * @function
 * @param	{cp.Vect}	v2
 * @return	{cp.Vect}
 */
Vect.prototype.project = function(v2)
{
	this.mult(vdot(this, v2) / vlengthsq(v2));
	return this;
};

/// Uses complex number multiplication to rotate v1 by v2. Scaling will occur if v1 is not a unit vector.
/**
 * Uses complex number multiplication to rotate v1 by v2. Scaling will occur if v1 is not a unit vector.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @return	{cp.Vect}
 */
var vrotate = cp.v.rotate = function(v1, v2)
{
	return new Vect(v1.x*v2.x - v1.y*v2.y, v1.x*v2.y + v1.y*v2.x);
};

/**
 * @function
 * @param	{cp.Vect}	v2
 * @return	{cp.Vect}
 */
Vect.prototype.rotate = function(v2)
{
	this.x = this.x * v2.x - this.y * v2.y;
	this.y = this.x * v2.y + this.y * v2.x;
	return this;
};

/// Inverse of vrotate().
/**
 * Inverse of vrotate().
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @return	{cp.Vect}
 */
var vunrotate = cp.v.unrotate = function(v1, v2)
{
	return new Vect(v1.x*v2.x + v1.y*v2.y, v1.y*v2.x - v1.x*v2.y);
};

/// Returns the squared length of v. Faster than vlength() when you only need to compare lengths.
/**
 * Returns the squared length of v. Faster than vlength() when you only need to compare lengths.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v
 * @return	{number}
 */
var lengthsq = cp.v.lengthsq = function(v)
{
	return vdot(v, v);
};

/**
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	x
 * @param	{cp.Vect}	y
 * @return	{number}
 */
var vlengthsq2 = cp.v.lengthsq2 = function(x, y)
{
	return x*x + y*y;
};

/// Linearly interpolate between v1 and v2.
/**
 * Linearly interpolate between v1 and v2.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @param	{number}	t
 * @return	{cp.Vect}
 */
var vlerp = cp.v.lerp = function(v1, v2, t)
{
	return vadd(vmult(v1, 1 - t), vmult(v2, t));
};

/// Returns a normalized copy of v.
/**
 * Returns a normalized copy of v.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v
 * @return	{cp.Vect}
 */
var vnormalize = cp.v.normalize = function(v)
{
	return vmult(v, 1/vlength(v));
};

/// Returns a normalized copy of v or vzero if v was already vzero. Protects against divide by zero errors.
/**
 * Returns a normalized copy of v or vzero if v was already vzero. Protects against divide by zero errors.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v
 * @return	{cp.Vect}
 */
var vnormalize_safe = cp.v.normalize_safe = function(v)
{
	return (v.x === 0 && v.y === 0 ? vzero : vnormalize(v));
};

/// Clamp v to length len.
/**
 * Clamp v to length len.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{number}	len
 * @return	{cp.Vect}
 */
var vclamp = cp.v.clamp = function(v, len)
{
	return (vdot(v,v) > len*len) ? vmult(vnormalize(v), len) : v;
};

/// Linearly interpolate between v1 towards v2 by distance d.
/**
 * Linearly interpolate between v1 towards v2 by distance d.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @param	{number}	d
 * @return	{cp.Vect}
 */
var vlerpconst = cp.v.lerpconst = function(v1, v2, d)
{
	return vadd(v1, vclamp(vsub(v2, v1), d));
};

/// Returns the distance between v1 and v2.
/**
 * Returns the distance between v1 and v2.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @return	{number}
 */
var vdist = cp.v.dist = function(v1, v2)
{
	return vlength(vsub(v1, v2));
};

/// Returns the squared distance between v1 and v2. Faster than vdist() when you only need to compare distances.
/**
 * Returns the squared distance between v1 and v2. Faster than vdist() when you only need to compare distances.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @return	{number}
 */
var vdistsq = cp.v.distsq = function(v1, v2)
{
	return vlengthsq(vsub(v1, v2));
};

/// Returns true if the distance between v1 and v2 is less than dist.
/**
 * Returns true if the distance between v1 and v2 is less than dist.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @param	{number}	dist
 * @return	{cp.Vect}
 */
var vnear = cp.v.near = function(v1, v2, dist)
{
	return vdistsq(v1, v2) < dist*dist;
};

/// Spherical linearly interpolate between v1 and v2.
/**
 * Spherical linearly interpolate between v1 and v2.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @param	{number}	t
 * @return	{cp.Vect}
 */
var vslerp = cp.v.slerp = function(v1, v2, t)
{
	var omega = Math.acos(vdot(v1, v2));
	
	if(omega) {
		var denom = 1/Math.sin(omega);
		return vadd(vmult(v1, Math.sin((1 - t)*omega)*denom), vmult(v2, Math.sin(t*omega)*denom));
	} else {
		return v1;
	}
};

/// Spherical linearly interpolate between v1 towards v2 by no more than angle a radians
/**
 * Spherical linearly interpolate between v1 towards v2 by no more than angle a radians
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @param	{cp.Vect}	v2
 * @param	{number}	a
 * @return	{cp.Vect}
 */
var vslerpconst = cp.v.slerpconst = function(v1, v2, a)
{
	var angle = Math.acos(vdot(v1, v2));
	return vslerp(v1, v2, min(a, angle)/angle);
};

/// Returns the unit length vector for the given angle (in radians).
/**
* Returns the unit length vector for the given angle (in radians).
 *
 * @function
 * @memberof	cp.v
 * @param	{number}	a
 * @return	{cp.Vect}
 */
var vforangle = cp.v.forangle = function(a)
{
	return new Vect(Math.cos(a), Math.sin(a));
};

/// Returns the angular direction v is pointing in (in radians).
/**
 * Returns the angular direction v is pointing in (in radians).
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v
 * @return	{number}
 */
var vtoangle = cp.v.toangle = function(v)
{
	return Math.atan2(v.y, v.x);
};

///	Returns a string representation of v. Intended mostly for debugging purposes and not production use.
/**
 * Returns a string representation of v. Intended mostly for debugging purposes and not production use.
 *
 * @function
 * @memberof	cp.v
 * @param	{cp.Vect}	v1
 * @return	{string}
 */
var vstr = cp.v.str = function(v)
{
	return "(" + v.x.toFixed(3) + ", " + v.y.toFixed(3) + ")";
};