/**
* Copyright Franson Technology AB, Sweden, 2009
* http://gpsgate.com, http://franson.com
*
* author Fredrik Blomqvist
*
* <p>
* 2D vector algebra.
* Should be able to work with any object with a .x and .y number property (integral or fp. type). See <a href="Franson.Vec2.html#method_isVec2Like">isVec2Like</a>.
* see also <a href="module_Math.html">Franson.Math</a>
* </p>
*
* @module Vec2
*
*/
/*
* todo: could be split in more modules
* todo: matrix transforms (matrix.js)
* todo: hmm, should decide on more consistent naming conventions for mutable/immutable operations.
* particularly normalize, negate etc. (Normalize is currently the only mutable operation)
* (or simply make everything immutable?)
* todo: put all of this in a Franson.Math namespace instead? (name collision?)
*
*/
var Franson = Franson || {};
/**
* namespace
* @class Franson.Vec2
* @static
*/
Franson.Vec2 = (function()
{
/**
* for comparisons. Set to your likings.
* see setEpsilon/getEpsilon
* see <a href="#method_equals">equals</a>
* @type float
* @private
*/
var Epsilon = 0.000001; // ok default?
/**
* Epsilon used for Vec2 comparison.
* see <a href="#method_equals">equals</a>
* @method setEpsilon
* @param {number} epsilon
*/
function setEpsilon(epsilon)
{
Epsilon = epsilon;
}
/**
* Epsilon used for Vec2 comparison.
* see <a href="#method_equals">equals</a>
* @method getEpsilon
* @return {number}
*/
function getEpsilon()
{
return Epsilon;
}
/**
* You should assume this is the precondition
* (PC, assertion) for all vector operations.
*
* todo: support multiple parameters to match Mochi-conventions?
* todo: alias as isPointLike?
* @method isVec2Like
* @param {Object} v
* @return {boolean}
*/
function isVec2Like(v)
{
// return (typeof(v) == 'object' && v !== null && typeof(v.x) == 'number' && typeof(v.y) == 'number');
return (typeof(v) == 'object' && v !== null && Franson.Math.isNumber(v.x) && Franson.Math.isNumber(v.y));
}
/**
* note: supports multiple arguments
* @method add
* @param {Vec2} a
* @param {Vec2} b
* @return {Vec2} a + b
*/
function add(a, b)
{
/* return MochiKit.Iter.reduce(
function(a, b)
{
return new Vec2(a.x + b.x, a.y + b.y);
},
arguments
);
*/
var ret = new Vec2(arguments[0].x, arguments[0].y);
for (var i = 1; i < arguments.length; ++i)
{
ret.x += arguments[i].x;
ret.y += arguments[i].y;
}
return ret;
}
/**
* note: supports multiple arguments
* @method sub
* @param {Vec2} a
* @param {Vec2} b
* @return {Vec2} a - b
*/
function sub(a, b)
{
// perhaps not as common to call sub with more than two arguments but still useful
/* return MochiKit.Iter.reduce(
function(a, b)
{
return new Vec2(a.x - b.x, a.y - b.y);
},
arguments
);
*/
var ret = new Vec2(arguments[0].x, arguments[0].y);
for (var i = 1; i < arguments.length; ++i)
{
ret.x -= arguments[i].x;
ret.y -= arguments[i].y;
}
return ret;
}
/**
* Dot product (inner-product, scalar-product, ( = <tt>|u||v|*cos(theta)</tt> )
* @method dot
* @param {Vec2} u
* @param {Vec2} v
* @return {number} a.b
*/
function dot(u, v)
{
return u.x * v.x + u.y * v.y;
}
/**
* Magnitude(length) of vector (add as alias?)
* @method norm
* @param {Vec2} v
* @return {number} Euclidian length of vector
*/
function norm(v)
{
return Math.sqrt(dot(v, v));
}
/**
* alias for <a href="#method_norm">norm</a>
* @method length
* @param {Vec2} v
* @alias norm
* @return {number} Euclidian length of vector
*/
function length(v)
{
return norm(v);
}
/**
* normalizes v (<tt>|v|=1</tt>) and returns the old norm
* note: this is the only mutable operation!
* @method normalize
* @param {Vec2} v
* @return {number} previous norm (before normalization)
*/
function normalize(v)
{
var norm = length(v);
v.x /= norm;
v.y /= norm;
return norm;
}
/**
* s*v (or v*s if scalar-vector arguments are reversed) or
* element-vise multiplication(scale) if both arguments are Vec2s.
* @method scale
* @param {Number|Vec2} s
* @param {Vec2|Number} v
* @return {Vec2} s*v
*/
function scale(s, v) // mul?
{
if (Franson.Math.isNumber(s) && isVec2Like(v))
{
return new Vec2(s * v.x, s * v.y);
}
else
if (isVec2Like(s) && Franson.Math.isNumber(v))
{
return scale(v, s);
}
else
if (isVec2Like(s) && isVec2Like(v))
{
return new Vec2(s.x * v.x, s.y * v.y);
}
return null; // just to avoid warning, cannot be reached
}
/**
* definition: <tt>add(v, negate(v))) == (0,0)</tt>
* @method negate
* @param {Vec2} v
* @return {Vec2} -v
*/
function negate(v)
{
return new Vec2(-v.x, -v.y); // == scale(-1, v)
}
/**
* @method equals
* @param {Vec2} a
* @param {Vec2} b
* @param {Number} [epsilon=Franson.Vec2.getEpsilon()]
* @return {boolean} a == b? (within epsilon tolerance)
*/
function equals(a, b, epsilon) // ok name? clash?
{
epsilon = typeof(epsilon) != 'undefined' ? epsilon : Epsilon;
var dv = sub(a, b); // or use 'norm(sub(a, b)) <= epsilon'? (to get a circular error diff distribution instead of square)
return Math.abs(dv.x) <= epsilon && Math.abs(dv.y) <= epsilon;
}
/**
* definition: <tt>dot(v, ortho(v)) == 0</tt>
* todo: provide a param (or two versions?) controlling which direction the flip should take? (CW, CCW)
* @method ortho
* @param {Vec2} v
* @return {Vec2} vector v rotated 90 degrees
*/
function ortho(v)
{
return new Vec2(v.y, -v.x);
}
/**
* Equiv to the magnitude of the z-comp of the cross product of a & b in the x-y plane. <tt>sin(t)|a||b|=det(a,b)</tt> = 2*area of the spanned triangle
* @method determinant
* @param {Vec2} a
* @param {Vec2} b
* @return {number}
*/
function determinant(a, b) // or just det()? (alias?)
{
return (a.x * b.y) - (a.y * b.x);
}
/**
* <b>L</b>in<b>E</b>ar inte<b>RP</b>olation
* @method lerp
* @param {float} t (0..1) t=0 => p0, t=1 => p1
* @param {Vec2} p0
* @param {Vec2} p1
* @return {Vec2}
*/
function lerp(t, p0, p1)
{
return add(p0, scale(t, sub(p1, p0))); // = p0 + t*(p1 - p0)
}
/**
* angle between two vectors in radians. (-Pi, Pi).
* @method angle
* @param {Vec2} u
* @param {Vec2} [v=x_axis] Note: in this case the angle is within [0, 2*Math.Pi)
* @return {number} (-Pi, Pi)
*/
function angle(u, v)
{
if (arguments.length == 1)
{
v = arguments[0];
u = new Vec2(1, 0);
var angle = Math.atan2(determinant(u, v), dot(u, v));
if (angle < 0) // push angle to 0..2*pi
angle += 2*Math.PI;
return angle;
}
else
{
return Math.atan2(determinant(u, v), dot(u, v));
}
}
/*---
* Matrix transformation (Vec2 treated as a _column_ vector (post-multiplication))<br />
* todo - move to separate package and write/specify a matrix.js or affine2d.js? (decide on matrix naming conventions etc)
* @param {Matrix} m (.tx/.ty = translation) (need an is2dTransformLike?)
* @param {Vec2} v
* @return {Vec2}
*/
/* function transform(m, v) // mul?
{
return new Vec2(
m[0]*v.x + m[1]*v.y + m.tx,
m[2]*v.x + m[3]*v.y + m.ty
);
}
*/
/**
* mostly for documentation purposes.
* Note that this class doesn't have _any_ methods,
* we use the separate functions in Franson.Vec2 and duck-typing.
* @param {number} [x=0]
* @param {number} [y=0]
* @class Franson.Vec2.Vec2
* @extends Point
* @constructor
*/
function Vec2(x, y)
{
/**
* @type number
* @default 0
*/
this.x = x || 0;
/**
* @type number
* @default 0
*/
this.y = y || 0;
}
// public API
return {
Vec2: Vec2,
isVec2Like: isVec2Like,
setEpsilon: setEpsilon,
getEpsilon: getEpsilon,
add: add,
sub: sub,
negate: negate,
scale: scale,
length: length,
norm: norm,
normalize: normalize,
dot: dot,
determinant: determinant, // or just 'det'?
angle: angle,
equals: equals, // add a compare also? (definition? just lexic x,y?)
ortho: ortho,
lerp: lerp,
// 'transform': transform, //?
// constants
zero: new Vec2(0, 0),
origo: new Vec2(0, 0), // alias
x_axis: new Vec2(1, 0),
y_axis: new Vec2(0, 1)
};
})(); // Vec2 namespace