Source: math/geo2d/WKtVector2D.js

/*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 }
             });
});
W. Kowarschick © 2013 (CC BY-NC-SA 3.0: http://creativecommons.org/licenses/by-nc-sa/3.0/)
Documentation generated by JSDoc 3.3.0-dev on Sat Oct 05 2013 15:14:39 GMT+0200 (MESZ) using the DocStrap template.