GpsGate Server JavaScript API

Util  1.0.0

GpsGate Server JavaScript API > Util > Vec2.js (source view)
Search:
 
Filters
/**
 * 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

Copyright © 2009 Franson Technology AB, Sweden. All rights reserved.