/*jslint plusplus: true, white: true, indent: 2, maxlen: 90 */
/*global $wk$ */
/**
* @author Wolfgang Kowarschick
* @copyright 2012-2013, Wolfgang Kowarschick
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted under the terms of the
* Creative Commons License Attribution-NonCommercial-ShareAlike 3.0 Unported
* (CC BY-NC-SA 3.0: http://creativecommons.org/licenses/by-nc-sa/3.0/).
*/
////////////////////////////////////////////////////////////////////////////////
// Class "$wk$.math.geo2d.WKtVector"
////////////////////////////////////////////////////////////////////////////////
$wk$("$wk$.WKcClass", "$wk$.math.WKcMath",
function($)
{ "use strict";
var WKcMath = $.WKcMath;
//============================================================================
// CONSTANTS
//============================================================================
var M_GET_R = 0;
var M_GET_R_SQR = 1;
var M_GET_φ = 2;
var M_GET_UNIT = 3;
var M_GET_NORMAL = 4;
var M_GET_NORMAL_UNIT = 5;
var M_GET_NEGATED = 6;
var M_GET_IS_ZERO = 7;
var M_GET_IS_UNIT = 8;
//============================================================================
// Private members
//============================================================================
var v_private_methods =
[ // M_GET_R:
// Computes the length r ({Number}) of <code>this</code> vector.
function()
{ return Math.sqrt(this.rSqr); },
// M_GET_R_SQR:
// Computes square of the length r ({Number}) of <code>this</code> vector.
function()
{ return this.v_x*this.v_x + this.v_y*this.v_y; },
// M_GET_φ:
// Computes the angle φ ({Number}) of <code>this</code> vector.
function()
{ return ((this.v_y>=0)?1:-1) * Math.acos(this.v_x/this.r); },
// M_GET_UNIT:
// Computes the unit vector of <code>this</code> vector.
function()
{ if (this.isZero)
return this;
else
{ var l_result = this.scale(1/this.r);
l_result.v_cache.m_get_is_unit = true;
return l_result;
}
},
// M_GET_NORMAL:
// Computes the normal vector of <code>this</code> vector.
function()
{ return new WKtVector2D(-this.v_y, this.v_x); },
// M_GET_NORMAL_UNIT:
// Computes the unit normal vector of <code>this</code> vector.
function()
{ return this.normal.unit; },
// M_GET_NEGATED:
// Returns (-this.x, -this.y).
function()
{ return new WKtVector2D(-this.v_y, -this.v_x); },
// M_GET_IS_ZERO:
// Returns <code>true</code>, if <code>this</code> vector is nearly equal
// to the zero vector.
function()
{ return Math.abs(this.v_x) <= WKcMath.EPSILON
&& Math.abs(this.v_y) <= WKcMath.EPSILON;
},
// M_GET_IS_UNIT:
// Returns <code>true</code>, if the length of <code>this</code> vector
// (<code>this.r</code>) is nearly equal to <code>1</code>.
function()
{ return Math.abs(this.rSqr-1) <= WKcMath.EPSILON; },
];
// Returns a cached attribute value. If the value has not been
// cached yet, it is computed by calling the corresponding attribute method.
function m_get_cache_value(p_method)
{ // Fetch the requested value from the cache.
var l_cache_value = this.v_cache[p_method];
// If the requested value has not been found, compute and then return it.
if (l_cache_value == null)
return this.v_cache[p_method] = v_private_methods[p_method].call(this);
else // Return the cached result.
return l_cache_value;
};
/**
* <code>WKtVector2D</code> values represent two-dimensional, non-modifiable
* vectors.
*
* @class
* @name $wk$.math.geo2d.WKtVector2D
* @returns {Object} A new 2D vector value.
*/
var WKtVector2D =
new $.WKcClass
({fullName: "$wk$.math.geo2d.WKtVector2D",
methods:
{ //========================================================================
// Constructor
//========================================================================
/**
* @constructor
*
* Creates a new vector and initializes it.
*
* @param {Number} [p_x = 0] The x-coordinate of the new vector.
* @param {Number} [p_y = 0] The y-coordinate of the new vector.
*/
init:
function(p_x, p_y)
{ this.v_x = p_x || 0.0;
this.v_y = p_y || 0.0;
this.v_cache = [];
},
//--------------------------------------------------------------------------
// Cloning
//--------------------------------------------------------------------------
/**
* Clones <code>this</code> vector and returns a non-modifyable vector.
* As this vector is not modifyable, the clone and <code>this</code>
* are indentical.
*
* <p>The return types of <code>WKtVector2D.clone</code> and
* <code>WKtVector2D.clone</code> differ: The first method returns
* a <code>WKtVector2D<code> object, the second method returns a
* <code>WKcVector2D<code> object.
* </p>
*
* @returns {WKtVector2D} A <code>WKtVector2D</code>
* clone of <code>this</code> vector.
*/
clone:
function(){ return this; },
//--------------------------------------------------------------------------
// Attributes
//--------------------------------------------------------------------------
/** The x-coordinate ({Number}) of <code>this</code> vector. */
get x() { return this.v_x; },
/** The y-coordinate ({Number}) of <code>this</code> vector. */
get y() { return this.v_y; },
/** The length r ({Number}) of <code>this</code> vector. */
get r() { return m_get_cache_value.call(this, M_GET_R); },
get length() { return m_get_cache_value.call(this, M_GET_R); },
/**
* The square of the length ({Number}) , i.e. the sum of squares
* of <code>this</code> vector.
*/
get rSqr() { return m_get_cache_value.call(this, M_GET_R_SQR); },
get lengthSqr() { return m_get_cache_value.call(this, M_GET_R_SQR); },
/** The angle φ ({Number}) of <code>this</code> vector */
get φ() { return m_get_cache_value.call(this, M_GET_φ); },
/**
* Normalizes <code>this</code> vector and returns the resulting
* unit vector the length of which is nearly equal to 1.
*
* @returns {WKtVector2D} The result vector.
*/
get unit()
{ return m_get_cache_value.call(this, M_GET_UNIT); },
/**
* Returns the normal vector of <code>this</code> vector.
* The normal vector is rotated counterclockwise by 90 degrees.
* It is perpendicular to <code>this</code> vector and has the same length.
*
* @returns {WKtVector2D} The normal vector.
*/
get normal()
{ return m_get_cache_value.call(this, M_GET_NORMAL); },
/**
* Returns the unit normal vector of <code>this</code> vector.
* The unit normal vector is rotated counterclockwise by 90 degrees.
* It is perpendicular to <code>this</code> vector and has the length 1.
*
* @returns {WKtVector2D} The unit normal vector.
*/
get normalUnit()
{ return m_get_cache_value.call(this, M_GET_NORMAL_UNIT); },
/**
* Returns <code>this</code> vector negated,
* i.e. <code>this.scale(-1)</code>.
*
* @returns {WKtVector2D} The negated vector.
*/
get negated()
{ return m_get_cache_value.call(this, M_GET_NEGATED); },
//--------------------------------------------------------------------------
// Predicates
//--------------------------------------------------------------------------
/**
* Returns <code>true</code>, if <code>this</code> vector is nearly equal to
* the zero vector, i.e., if the absolute values of all its coordinates
* are less then or equal to <code>WKcMATH.EPSILON</code>.
*
* @returns {Boolean}
*/
get isZero()
{ return m_get_cache_value.call(this, M_GET_IS_ZERO); },
/**
* Returns <code>true</code>, if the length of <code>this</code> vector
* (<code>this.r</code>) is nearly equal to <code>1</code>.
*
* @returns {Boolean}
*/
get isUnit()
{ return m_get_cache_value.call(this, M_GET_IS_UNIT); },
/**
* Returns <code>true</code>, if <code>this</code> vector is nearly equal to
* another vector <code>p_v</code>. Two vectors are supposed to be equal,
* if the distances of their coordinates are all less then or equal to
* <code>MATH.epsilon</code>.
*
* @param {WKtVector2D} p_v The other vector.
*
* @returns {Boolean}
*/
isEqualTo:
function(p_v)
{ return Math.abs(this.v_x - p_v.x) <= WKcMath.EPSILON
&& Math.abs(this.v_y - p_v.y) <= WKcMath.EPSILON;
},
//--------------------------------------------------------------------------
// Query Methods
//--------------------------------------------------------------------------
/**
* Computes the dot product (scalar product) of <code>this</code> vector
* and a second vector.
*
* @param {WKtVector2D} p_v The second vector
*
* @returns {Number} The scalar product.
*
* @result this.x*this.x+this.y*this.y
*/
dot:
function(p_v)
{ return this.v_x*p_v.v_x + this.v_y*p_v.v_y; },
/**
* Computes the angle between <code>this</code> vector and a
* second vector.
*
* @param {WKtVector2D} p_v The second vector
*
* @returns {Number} The angle between <code>this</code>
* and <code>p_v</code>
* (in radians, <= <code>Math.PI</code>).
*
* @result Math.acos(this.dot(p_v)/(this.r*p_v.r))
*/
angle:
function(p_v)
{ return Math.acos(this.dot(p_v)/(this.r*p_v.r)); },
/**
* Computes the angle between <code>this</code> vector and a
* second vector in clockwise direction.
*
* @param {WKtVector2D} p_v The second vector
*
* @returns {Number} The angle between <code>this</code>
* and <code>p_v</code>
* (in radians, <= <code>2*Math.PI</code>).
*
* @result this.dot(p_v.normal)<0
? 2*Math.PI - this.angle(p_v)
: this.angle(p_v)
*/
angleClockwise:
function(p_v)
{ return (this.dot(p_v.normal)<0
? 2*Math.PI - this.angle(p_v)
: this.angle(p_v)
);
},
/**
* Returns the vector sum of <code>this</code> vector and a second
* vector multiplied by a scalar factor.
*
* @param {WKtVector2D} p_v The vector to be added.
* @param {Number} [p_s=1] The scalar factor.
*
* @returns {WKtVector2D} The result vector.
*/
add:
function(p_v, p_s)
{ if (p_s != null)
return new WKtVector2D(this.v_x+p_s*p_v.x, this.v_y+p_s*p_v.y);
else
return new WKtVector2D(this.v_x+p_v.x, this.v_y+p_v.y);
},
/**
* Returns the vector difference of <code>this</code> vector and a second
* vector multiplied by a scalar factor.
*
* @param {WKtVector2D} p_v The vector to be subtracted.
* @param {Number} [p_s=1] The scalar factor.
*
* @returns {WKtVector2D} The result vector.
*/
sub:
function(p_v, p_s)
{ if (p_s != null)
return new WKtVector2D(this.v_x-p_s*p_v.x, this.v_y-p_s*p_v.y);
else
return new WKtVector2D(this.v_x-p_v.x, this.v_y-p_v.y);
},
/**
* Returns a scalar product for <code>this</code> vector.
*
* @param {Number} p_s The scalar value.
*
* @returns {WKtVector2D} The result vector.
*/
scale:
function(p_s)
{ return new WKtVector2D(p_s*this.v_x, p_s*this.v_y); },
/**
* Projects <code>this</code> vector onto another vector <code>p_v</code>.
*
* @param {WKtVector2D} p_v The vector onto which <code>this</code> vector
* is projected.
* @precondition <code>!p_v.isZero()</code>
*
* @returns {WKtVector2D} A multiple of <code>p_v</code>.
*/
proj:
function(p_v)
{ return p_v.scale(this.dot(p_v)/p_v.rSqr); },
/**
* Projects <code>this</code> vector onto another vector <code>p_v</code>.
*
* @param {WKtVector2D} p_v The vector onto which <code>this</code> vector
* is projected.
* @precondition <code>!p_v.isZero()</code>
*
* @returns {Number} α, where </code>this.proj(p_v) = p_v.scale(α)</code>.
*/
projFactor:
function(p_v)
{ return this.dot(p_v)/p_v.rSqr; },
/**
* Computes the reflection vector that arises from <code>this</code> vector
* being reflected by a "mirror" vector <code>v_m</code>.
* The length of the reflection vector is the same as the length of
* <code>this</code> vector multiplied by a scalar factor.
*
* @param {WKtVector2D} p_m The mirror vector.
* @param {Number} [p_s=1] The scalar factor.
*
* @precondition <code>!p_m.isZero()</code>
*
* @returns {WKtVector2D} The reflection vector.
*/
reflect:
function(p_m, p_s)
{ p_s = p_s || 1;
var l_n = p_m.normal;
return this.sub(l_n.scale(2*p_s*this.dot(l_n)/l_n.rSqr));
},
//--------------------------------------------------------------------------
// Tracing
//--------------------------------------------------------------------------
/**
* Formats <code>this</code> vector.
*
* @override
* @returns {String}
*/
toString:
function()
{ return "(" + this.x + "," + this.y + ")"; }
//==========================================================================
// End of Member Definitions
//==========================================================================
}
});
/**
* @method
* @name $wk$.math.WKcMath.randomVector2D
*
* @param {Number|$wk$.math.geo2d.WKtVector2D|Array|Object} p_interval
* A single number or vector, an array containing several numbers
* or vectors, or an object with two properties <code>min</code>
* and <code>max</code> whose values are numbers or vectors.
* @returns {$wk$.math.geo2d.WKtVector2D}
* A random vector whithin the range of the closed interval
* defined by <code>p_interval</code>.
*/
WKcMath.randomWKtVector2D =
function(p_interval)
{ var l_a, l_b;
if (p_interval instanceof Array)
{ p_interval = p_interval[Math.floor(Math.random()*p_interval.length)]; }
if (p_interval == null)
{ return new WKtVector2D; }
if (p_interval instanceof WKtVector2D)
{ return p_interval; }
if (p_interval instanceof Number)
{ return new WKtVector2D(p_interval, p_interval); }
if (p_interval instanceof Object && p_interval.min && p_interval.max)
{ l_a = p_interval.min, l_b = p_interval.max;
if (l_a instanceof Number)
{ l_a = new WKtVector2D(l_a, l_a); }
if (l_b instanceof Number)
{ l_b = new WKtVector2D(l_b, l_b); }
return new WKtVector2D
(WKcMath.random({min: l_a.x, max: l_b.x}),
WKcMath.random({min: l_a.y, max: l_b.y})
);
}
throw new Error( p_interval + " is no interval");
};
//////////////////////////////////////////////////////////////////////////////
// End of Class Definition
//////////////////////////////////////////////////////////////////////////////
$.moduleAdd({ name: "$wk$.math.geo2d.WKtVector2D",
module: { WKtVector2D: WKtVector2D }
});
});