Source: xform.js

/**
 * xform.js Linear Algebra Library
 *
 * @author Christopher Grabowski - https://github.com/cgrabowski
 * @license MIT License
 * @version 0.0.1
 *
 * Copyright (c) 2015 Christopher Grabowski
 *
 * 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.
 *
 */

// create the xform namespace for running in a browser environment.

/**
 * All types and functions belong to the xform namespace.
 *
 * @namespace xform
 */
var xform = {};

(function(undefined) {
  'use strict';

  // Set the exports property if this is a CommonJS environment (e.g., node.js).
  if (typeof module !== 'undefined') {
    module.exports = xform;
  }

  /**
   * @property {constructor} Dimensional
   * @property {constructor} Dimensions
   * @property {constructor} Vector
   * @property {constructor} Matrix
   * @property {constructor} Quaternion
   * @property {constructor} Attitude
   * @property {constructor}  DimensionError
   */
  xform.Dimensional = Dimensional;
  xform.Dimensions = Dimensions;
  xform.Vector = Vector;
  xform.Matrix = Matrix;
  xform.Quaternion = Quaternion;
  xform.Attitude = Attitude;
  xform.DimensionError = DimensionError;

  /**
   * Adds the xform namespace to the specified context.
   *
   * This function is intended to allow all members of the xform namespace to
   * be added to the global object or any other object.
   *
   * NOTE: In node.js if you want to add the xform members to the global object you
   * must pass the object named GLOBAL as the argument.
   *
   * @param {Object} - The object that will gain xform's members.
   * @returns {void}
   */
  xform.usingNamespace = function(object) {
    for (var prop in xform) {
      Object.defineProperty(object, prop, {
        value: xform[prop],
        configurable: true,
        enumerable: true,
        writable: true
      });
    }
  };

  /**
   * Common supertype of all types with dimensions.
   *
   * @name Dimensional
   * @constructor
   * @extends Array
   * @param {Array | number} dimensionality - An array whose entries represent an index and
   * whose value is the dimensionality of that index -or- a number that
   * represents the dimensionality.
   * @example
   * // Creates a Dimensional with a 3-dimensional index and a 4-dimensional
   * // index, just like a 3x4 Matrix.
   * new Dimensional([3, 4]);
   */
  function Dimensional(dimensionality) {
    dimensionality = dimensionality || null;
    if (typeof dimensionality === 'number') {
      this.dim = new Dimensions([dimensionality]);
    } else if (dimensionality instanceof Array) {
      this.dim = new Dimensions(dimensionality);
    } else {
      var msg = "Dimensional constructor expected number or array but got ";
      var type = (dimensionality === null) ? 'null' : dimensionality.constructor;
      throw new TypeError(msg + type);
    }
  }

  Dimensional.prototype = Object.create(Array.prototype);
  Dimensional.prototype.constructor = Dimensional;

  /**
   * @name equals
   * @memberof Dimensional.prototype
   * @function
   * @param {Array} array - The array to compare this Dimensional to.
   * @returns {boolean} True if all entries are equal, false otherwise.
   */
  Dimensional.prototype.equals = function(array) {
    if (array instanceof Dimensional) {
      if (this.dim.equals(array.dim) && arrayIndexedEntriesEqual(this, array)) {
        return true;
      } else {
        return false;
      }
    } else if (array instanceof Array) {
      if (arrayIndexedEntriesEqual(this, array)) {
        return true;
      } else {
        return false;
      }
    } else {
      var type;
      var ctorName = this.constructor.name;
      if (typeof array === 'number') {
        type = 'number';
      } else {
        type = array.constructor.name;
      }
      throw new TypeError(ctorName + '.prototype.equals expected instanceof Array but got ' + type);
    }
  };

  /**
   * Represents the dimensionality of a type.
   *
   * @name Dimensions
   * @class
   * @extends Array
   * @param {Array | number} arrayOrNumber- Array whose entries represent the number
   * of dimensions of each index -or- for convenience, a number that represents the dimensions of
   * an object with one index
   */

  function Dimensions(arrayOrNumber) {
    arrayOrNumber = arrayOrNumber || null;
    if (typeof arrayOrNumber === 'number') {
      this.push(arrayOrNumber);
    } else if (arrayOrNumber instanceof Array) {
      for (var i = 0, len = arrayOrNumber.length; i < len; ++i) {
        this.push(arrayOrNumber[i]);
      }
    } else {
      var msg = 'Dimensions constructor expected number or array but got ';
      var type = (arrayOrNumber === null) ? 'null' : arrayOrNumber.constructor;
      throw new TypeError(msg + type);
    }
  }
  Dimensions.prototype = Object.create(Array.prototype);
  Dimensions.prototype.constructor = Dimensions;

  /**
   * @name equals
   * @memberof Dimensions.prototype
   * @function
   * @param {Dimensions} dimensions - The Dimensions to compare this Dimensions to.
   * @returns {boolean} True if the Dimensions are equal, false otherwise.
   *
   */
  Dimensions.prototype.equals = function(dimensions) {
    return arrayIndexedEntriesEqual(this, dimensions);
  };

  /**
   * @name set
   * @memberof Dimensions.prototype
   * @function
   * @param {Array} array - The array whose entries will be set in this Dimensional.
   * @returns {Dimensions} This Dimensions.
   */
  Dimensions.prototype.set = function(array) {
    this.length = array.length;
    for (var i = 0, len = array.length; i < len; ++i) {
      this[i] = array[i];
    }
    return this;
  };

  /**
   * General vector type.
   *
   * @name Vector
   * @class
   * @extends Dimensional
   * @param {Array | number} arrayOrNumber - An array whose entries will become the values of the
   * vector. The vector's dimension will be set to the array's length -or- a
   * number that will set the Vector's value. Each of the Vector's entries will
   * be set to zero.
   */
  function Vector(dimOrArray) {
    var dimension = (dimOrArray instanceof Array) ? dimOrArray.length : dimOrArray;
    Dimensional.call(this, dimension);
    if (typeof dimOrArray === 'number') {
      for (var i = 0; i < dimOrArray; ++i) {
        this.push(0);
      }
    } else if (dimOrArray instanceof Array) {
      var len = dimOrArray.length;
      for (var i = 0; i < len; ++i) {
        this.push(dimOrArray[i]);
      }
    } else {
      var msg = "Argument to Vector constructor must be an instance of Array or Number.";
      throw new TypeError(msg);
    }
  }
  Vector.prototype = Object.create(Dimensional.prototype);
  Vector.prototype.constructor = Vector;

  /**
   * @name copy
   * @memberof Vector
   * @function
   * @param {Vector} vector - The Vector to copy from.
   * @param {Vector} [out] - The Vector to copy to. If undefined, a
   * new Vector is created.
   * @returns {Vector} The out vector.
   */
  Vector.copy = function(vector, out) {
    if (out == null) {
      out = new Vector(vector);
    } else {
      var len = vector.length;
      out.length = len;
      for (var i = 0; i < len; ++i) {
        out[i] = vector[i];
      }
    }
    return out;
  };

  /**
   * @name flatten
   * @memberof Vector
   * @function
   * @param {Array} arrayOfVectors - An array of Vectors.
   * @param {constructor} [arrayType = Float32Array] - The type of Array to create. This method
   * can create typed arrays.
   * @returns {Array | TypedArray} a new Array or TypedArray who will be filled
   * with the values of the Vectors.
   */
  Vector.flatten = function(arrayOfVectors, arrayType) {
    arrayType = arrayType || Float32Array;

    var arr = arrayOfVectors.reduce(function(sum, ele) {
      return sum.concat(ele.toArray());
    }, []);

    if (arrayType === Array) {
      return arr;
    } else {
      return new arrayType(arr);
    }
  };

  /**
   * @name dot
   * @memberof Vector
   * @function
   * @throws {DimensionError} if the dimension of vec1 and vec2 are not equal.
   * @param {Vector} vec1 - The first Vector.
   * @param {Vector} vec2 - The second Vector.
   * @returns {number} The dot product of the two Vectors.
   */
  Vector.dot = function(vec1, vec2) {
    dimCheck(vec1, vec2);
    var out = 0;
    var len = vec1.length;
    for (var i = 0; i < len; ++i) {
      out += vec1[i] * vec2[i];
    }
    return out;
  };

  /**
   * @name cross
   * @memberof Vector
   * @function
   * @throws {DimensionError} if vec1 and vec2 are not 3-dimensional Vectors.
   * @param {Vector} vec1 - The first Vector.
   * @param {Vector} vec2 - The second Vector.
   * @param {Vector} [out] - The Vector that will contain the result
   * of the cross product of the two Vectors. If undefined, a new Vector is
   * created.
   * @returns {Vector} The out Vector.
   */
  Vector.cross = function(vec1, vec2, out) {
    if (vec1.length !== 3 || vec2.length !== 3) {
      throw new DimensionError("Cross product is only defined for three-dimensional vectors.");
    }
    if (typeof out === 'undefined') {
      out = new Vector(3);
    } else if (!(out instanceof Vector)) {
      throw new TypeError('out argument to Vector.cross must be a Vector or undefined.');
    }

    out.set(vec1[1] * vec2[2] - vec1[2] * vec2[1],
      vec1[2] * vec2[0] - vec1[0] * vec2[2],
      vec1[0] * vec2[1] - vec1[1] * vec2[0]);
    return out;
  };

  /**
   * @name quadrance
   * @memberof Vector
   * @function
   * @throws {DimensionError} if the dimension of vec1 and vec2 are not equal.
   * @param {Vector} vec1 - The first Vector.
   * @param {Vector} vec2 - The second Vector.
   * @returns {number} The quadrance of the two Vectors.
   */
  Vector.quadrance = function(vec1, vec2) {
    vec2 = vec2 || vec1;
    dimCheck(vec1, vec2);
    var out = 0;
    var len = vec1.length;
    for (var i = 0; i < len; ++i) {
      out += vec1[i] * vec2[i];
    }
    return out;
  };

  /**
   * Set the value of every entry of this Vector to 0.
   *
   * @name zero
   * @memberof Vector.prototype
   * @function
   * @returns {Vector} This Vector.
   */
  Vector.prototype.zero = function() {
    for (var i = 0, len = this.length; i < len; ++i) {
      this[i] = 0;
    }
  };

  /**
   * @name dot
   * @memberof Vector.prototype
   * @function
   * @param {Vector} vec - The Vector.
   * @returns {number} The dot product of this Vector and vec
   */
  Vector.prototype.dot = function(vec) {
    return Vector.dot(this, vec);
  }

  /**
   * @name quadrance
   * @memberof Vector.prototype
   * @function
   * @param {Vector} vec - The Vector.
   * @returns {number} The quadrance of this Vector and vector.
   */
  Vector.prototype.quadrance = function(vec) {
    return Vector.quadrance(this, vec);
  };

  /**
   * @name magnitude
   * @memberof Vector.prototype
   * @function
   * @returns {number} The magnitude (length) of this Vector.
   */
  Vector.prototype.magnitude = function() {
    return Math.sqrt(Vector.quadrance(this));
  };

  /**
   * Adds each entry of vec to the corresponding entries of this Vector.
   *
   * @name add
   * @memberof Vector.prototype
   * @function
   * @throws {DimensionError} if the dimension of this Vector and vec are not equal.
   * @param {Vector} vec - The Vector
   * @returns {Vector} This Vector.
   */
  Vector.prototype.add = function(vec) {
    dimCheck(this, vec);
    var len = this.length;
    for (var i = 0; i < len; ++i) {
      this[i] += vec[i];
    }
    return this;
  };

  /**
   * Scales each entry of this Vector by scalar.
   *
   * @name scale
   * @memberof Vector.prototype
   * @function
   * @throws {TypeError} if scalar is not of type number.
   * @param {number} scalar - The scalar.
   * @returns {Vector} This Vector.
   */
  Vector.prototype.scale = function(scalar) {
    if (typeof scalar !== 'number') {
      throw new TypeError("argument to Vector.scale must be of type number.");
    }
    var len = this.length;
    for (var i = 0; i < len; ++i) {
      this[i] *= scalar;
    }
    return this;
  };

  /**
   * @name set
   * @memberof Vector.prototype
   * @function
   * @throws {DimensionError} if the dimension of this Vector and array are not equal.
   * @param {array} array - The array.
   * @returns {Vector} this Vector
   */
  /**
   * @name set^2
   * @memberof Vector.prototype
   * @function
   * @throws {DimensionError} if the dimension of this Vector and the number of
   * varargs are not equal.
   * @param {...number} varargs - Numbers this Vectors entries will be set from.
   * @returns {Vector} This Vector.
   */
  Vector.prototype.set = function(arrayOrVarargs) {
    var vals = (arguments.length === 1) ? arguments[0] : arguments;
    if (vals.length == null) {
      vals = [vals];
    }
    dimCheck(this, vals);
    for (var i = 0, len = this.length; i < len; ++i) {
      this[i] = vals[i];
    }
    return this;
  };

  /**
   * Normalizes this Vector.
   *
   * @name normalize
   * @memberof Vector.prototype
   * @function
   * @returns {Vector} This Vector.
   */
  Vector.prototype.normalize = function() {
    var mag = this.magnitude();
    for (var i = 0, len = this.length; i < len; ++i) {
      this[i] /= mag;
    }
    return this;
  }

  /**
   * This handles some edge cases such as Array.concat in which a subtype of Array
   * is not considered an Array.
   *
   * @name toArray
   * @memberof Vector.prototype
   * @function
   * @returns {Array} An Array whose entries are set from this Vector.
   */
  Vector.prototype.toArray = function() {
    return this.map(function(ele) {
      return ele;
    });
  };

  /**
   * @name toString
   * @memberof Vector.prototype
   * @function
   * @returns {string} A string representation of this Vector.
   */
  Vector.prototype.toString = function() {
    return '( ' + this.reduce(function(sum, ele) {
      return sum + ', ' + ele;
    }) + ' )';
  };

  /**
   * General matrix type.
   *
   * This Matrix type stores values in row-major order, so a Matrix must be
   * transposed before sending its values to an OpenGL shader.
   *
   * @name Matrix
   * @class
   * @extends Dimensional
   * @param {number} [m = 4] The number of rows in the matrix.
   * @param {number} [n = 4] The number of columns in the matrix.
   */
  function Matrix(m, n) {
    m = m || 4;
    n = n || 4;
    this.dim = new Dimensions([m, n]);
    for (var i = 0; i < m; ++i)
      for (var j = 0; j < n; ++j) {
        if (i === j) {
          this.push(1);
        } else {
          this.push(0);
        }
      }
  }
  Matrix.prototype = Object.create(Dimensional.prototype);
  Matrix.prototype.constructor = Matrix;

  Matrix.cache1 = new Matrix(4, 4);
  Matrix.cache2 = new Matrix(4, 4);

  /**
   * @name mul
   * @memberof Matrix
   * @function
   * @throws {TypeError} if mat2 is not an instance of Matrix or Vector.
   * @throws {DimensionError} if mat1 and mat2 are not compatable for multiplication.
   * @param {Matrix} mat1 - The left Matrix.
   * @param {matrix | Vector} mat2 - The right Matrix.
   * @param {Matrix | undefined} out - The result of the matrix multiplication.
   * If undefined, a new Matrix is created.
   * @returns {Matrix} The out matrix.
   */
  Matrix.mul = function(mat1, mat2, out) {
    var m1, m2, n1, n2;

    m1 = mat1.dim[0];
    n1 = mat1.dim[1];

    if (mat2 instanceof Matrix) {
      m2 = mat2.dim[0];
      n2 = mat2.dim[1];

      if (out == null) {
        out = new Matrix(m1, n2);
        out.zero();
      } else {
        out.dim = [m1, n2];
        out.zero();
      }

    } else if (mat2 instanceof Vector) {
      m2 = mat2.length;
      n2 = 1;

      if (out == null) {
        out = new Vector(mat2.length);
      } else {
        out.zero();
      }

    } else {
      throw new TypeError('second argument to Matrix.mul must be instance of Matrix or Vector');
    }

    if (n1 !== m2) {
      throw new DimensionError('left matrix n = ' + n1 + ', right matrix m = ' + m2);
    }

    for (var i = 0; i < m1; ++i)
      for (var j = 0; j < n2; ++j)
        for (var e = 0; e < n1; ++e) {
          out[j + n2 * i] += mat1[e + n1 * i] * mat2[n2 * e + j];
        }
    return out;
  };

  /**
   * @name copy
   * @memberof Matrix
   * @function
   * @param {Matrix} matrix - The matrix that will be copied.
   * @param {Matrix} [out] - The Matrix to copy into. If undefined, a
   * new Matrix is created.
   * @returns {Matrix} The out Matrix.
   */
  Matrix.copy = function(matrix, out) {
    if (out == null) {
      out = new Matrix(matrix.dim[0], matrix.dim[1]);
    } else {
      out.dim = [matrix.dim[0], matrix.dim[1]];
    }

    for (var i = 0; i < matrix.length; ++i) {
      out[i] = matrix[i];
    }
    return out;
  };

  /**
   * @name det
   * @memberof Matrix
   * @function
   * @throws {DimensionError} if matrix is not square.
   * @param {Matrix} matrix - The Matrix.
   * @returns {number} The determinant of matrix.
   */
  Matrix.det = function(matrix) {
    if (matrix.dim[0] !== matrix.dim[1]) {
      var msg = 'determinant is only defined for square matrices';
      throw new DimensionError(msg);

    } else if (matrix.dim[0] === 2) {
      return matrix[0] * matrix[3] - matrix[1] * matrix[2];

    } else if (matrix.dim[0] === 3) {
      var m = matrix;
      var x = m[0] * (m[4] * m[8] - m[5] * m[7]);
      var y = m[1] * (m[3] * m[8] - m[5] * m[6]);
      var z = m[2] * (m[3] * m[7] - m[4] * m[6]);
      return x - y + z;

    } else if (matrix.dim[0] === 4) {
      var m = matrix;
      var xy = m[8] * m[13] - m[9] * m[12];
      var xz = m[8] * m[14] - m[10] * m[12];
      var xw = m[8] * m[15] - m[11] * m[12];
      var yz = m[9] * m[14] - m[10] * m[13];
      var yw = m[9] * m[15] - m[11] * m[13];
      var zw = m[10] * m[15] - m[11] * m[14];
      var x = m[0] * (m[5] * zw - m[6] * yw + m[7] * yz);
      var y = m[1] * (m[4] * zw - m[6] * xw + m[7] * xz);
      var z = m[2] * (m[4] * yw - m[5] * xw + m[7] * xy);
      var w = m[3] * (m[4] * yz - m[5] * xz + m[6] * xy);
      return x - y + z - w;
    }

    var det = 0;
    for (var j = 0, d = matrix.dim[0]; j < d; ++j) {
      var md = matrix[j] * Matrix.det(Matrix.minor(matrix, 0, j));
      det += (j % 2 === 0) ? md : -md;
    }

    return det;
  };

  /**
   * @name invert
   * @memberof Matrix
   * @function
   * @throws {ReferenceError} if matrix is undefined.
   * @throws {DimensionError} if matrix is not square.
   * @throws {RangeError} if matrix is singular.
   * @param {Matrix} matrix - The matrix to invert.
   * @param {Matrix} [out] - The Matrix whose entries will be set to the inverse
   * of matrix. If undefined, a new Matrix will be created.
   * @returns {Matrix} The out Matrix.
   */
  Matrix.invert = function(matrix, out) {
    if (typeof matrix === 'undefined') {
      var msg = 'First argument to Matrix.invert is undefined';
      throw new ReferenceError(msg);
    }
    if (matrix.dim[0] !== matrix.dim[1]) {
      var msg = 'Matrix.invert requires a square matrix.';
      throw new DimensionError(msg);
    }
    var det = Matrix.det(matrix);
    if (det === 0) {
      var msg = 'Cannot invert a singular matrix.';
      throw new RangeError(msg);
    }
    var mat = matrix;
    if (mat.dim[0] === 2) {
      return mat.set([mat[3] / det, -mat[1] / det, -mat[2] / det, mat[0] / det]);
    }

    var d = mat.dim[0];
    if (typeof out === 'undefined') {
      out = new Matrix(d, d);
    } else {
      out.dim[0] = mat.dim[0];
      out.dim[1] = mat.dim[1];
    }
    var minor;
    if (d < 6) {
      minor = Matrix.cache1;
      minor.dim[0] = d;
      minor.dim[1] = d;
    } else {
      minor = new Matrix(d - 1, d - 1);
    }

    for (var i = 0; i < d; ++i)
      for (var j = 0; j < d; ++j) {
        var ind = i * d + j;
        out[ind] = Matrix.det(Matrix.minor(mat, i, j, minor));
        if (i % 2 !== j % 2) {
          out[ind] *= -1;
        }
      }
    for (var i = 0, len = mat.length; i < len; ++i) {
      out[i] /= det;
    }
    return out.transpose();
  };

  /**
   * Finds a minor of a Matrix. The index arguments begin at 0, so to remove the
   * first row of matrix, pass 0 as the argument to di.
   *
   * @name minor
   * @memberof Matrix
   * @function
   * @param {Matrix} matrix - The matrix.
   * @param {number} di - The index of the row to be removed from matrix.
   * @param {number} dj - The index of the column to be removed from matrix.
   * @param {Matrix} [out] - The matrix whose Dimensions and entries will be set
   * to the specified minor of matrix. If undefined, a new Matrix is created.
   * @returns {Matrix} The out Matrix.
   */
  Matrix.minor = function(matrix, di, dj, out) {
    var m = matrix.dim[0];
    var n = matrix.dim[1];
    var arr = [];
    if (typeof out === 'undefined') {
      out = new Matrix(m - 1, n - 1);
    } else {
      out.dim[0] = m - 1;
      out.dim[1] = n - 1;
    }

    row: for (var i = 0; i < m; ++i) {
      col: for (var j = 0; j < n; ++j) {
        if (i === di) {
          continue row;
        }
        if (j === dj) {
          continue col;
        }

        arr.push(matrix[i * m + j]);
      }
    }
    return out.set(arr);
  };

  /**
   * @name set
   * @memberof Matrix.prototype
   * @function
   * @throws {RangeError} if array.length + offset > the number of entries in this Matrix.
   * @param {Array} array - The array whose values will be set in this Matrix.
   * @param {number} [offset] - The offset to begin filling. If
   * undefined, offset will be set to 0.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.set = function(array, offset) {
    offset = offset || 0;
    if (array.length + offset > this.length) {
      var msg = 'Matrix.prototype.set arguments array + offset greater than this matrix.length';
      throw new RangeError(msg);
    }

    for (var i = 0, len = array.length; i < len; ++i) {
      this[i + offset] = array[i];
    }
    return this;
  };

  /**
   * Sets the ij entry of this Matrix. The index arguments begin at 1, so to set
   * the entry in the second row and third column, pass 2 and 3 as the arguments
   * to i and j, respectively.
   *
   * @name setEntry
   * @memberof Matrix.prototype
   * @function
   * @param {number} m - The row index.
   * @param {number} n - The column index.
   * @param {number} val - The value.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.setEntry = function(m, n, val) {
    this[n - 1 + (m - 1) * this.dim[1]] = val;
    return this;
  };

  /**
   * Sets this Matrix to the identity matrix or its analogue of this Matrix os
   * not square. (i.e., acts like the Kronecker delta over the indicies)
   *
   * @name identity
   * @memberof Matrix.prototype
   * @function
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.identity = function() {
    var m = this.dim[0];
    var n = this.dim[1];

    for (var i = 0; i < m; ++i)
      for (var j = 0; j < n; ++j) {
        this[j + i * n] = (i === j) ? 1 : 0;
      }
    return this;
  };

  /**
   * Sets every entry of this Matrix to 0.
   *
   * @name zero
   * @memberof Matrix.prototype
   * @function
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.zero = function() {
    var len = this.dim[0] * this.dim[1];
    for (var i = 0; i < len; ++i) {
      this[i] = 0;
    }
    return this;
  }

  /**
   * Multiplies this Matrix by matrix.
   *
   * @name mul
   * @memberof Matrix.prototype
   * @function
   * @throws {ReferenceError} if matrix is undefined.
   * @throws {TypeError} if matrix is not an instance of Matrix.
   * @throws {DimensionError} if this Matrix and matrix are not compatable for
   * multiplication.
   * @param {Matrix} matrix - The right-side multiplicand.
   * @returns {Matrix} - This Matrix.
   */
  Matrix.prototype.mul = function(matrix) {
    var m1 = this.dim[0];
    var n1 = this.dim[1];
    var m2, n2;

    if (typeof matrix === 'undefined') {
      var msg = 'Argument to Matrix.prototype.mul must be defined.';
      throw new ReferenceError(msg);
    } else if (matrix instanceof Matrix) {
      m2 = matrix.dim[0];
      n2 = matrix.dim[1];
    } else {
      var msg = 'Argument to Matrix.prototype.mul must be instance of Matrix. ';
      msg += 'If you want to multiply a vector by this matrix then use Matrix.mul';
      throw new TypeError(msg);
    }

    var cache = Matrix.cache1;
    cache.dim.set(this.dim);

    if (n1 !== m2) {
      throw new DimensionError('this matrix n = ' + n1 + ', other matrix m = ' + m2);
    }

    for (var i = 0, len = this.length; i < len; ++i) {
      cache[i] = this[i];
      this[i] = 0;
    }

    this.dim.set(matrix.dim);

    for (var i = 0; i < m1; ++i)
      for (var j = 0; j < n2; ++j)
        for (var e = 0; e < n1; ++e) {
          this[j + n2 * i] += cache[e + n1 * i] * matrix[n2 * e + j];
        }
    return this;
  };

  /**
   * @name det
   * @memberof Matrix.prototype
   * @function
   * @returns {number} The determinant of this Matrix.
   */
  Matrix.prototype.det = function() {
    return Matrix.det(this);
  };

  /**
   * Inverts this Matrix.
   *
   * @name invert
   * @memberof Matrix.prototype
   * @function
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.invert = function() {
    var cache = Matrix.cache2;
    cache.dim[0] = this.dim[0];
    cache.dim[1] = this.dim[1];
    cache.set(this);
    return Matrix.invert(cache, this);
  };

  /**
   * Transposes this Matrix.
   *
   * @name transpose
   * @memberof Matrix.prototype
   * @function
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.transpose = function() {
    var len = this.length;
    var m = this.dim[0];
    var n = this.dim[1];
    var cache = Matrix.cache1;

    cache.dim.set([m, n]);

    for (var i = 0; i < len; ++i) {
      cache[i] = this[i];
    }

    this.dim.set([n, m]);
    for (var i = 0; i < m; ++i)
      for (var j = 0; j < n; ++j) {
        this[i + m * j] = cache[j + i * n];
      }
    return this;
  };

  /**
   * Sets this matrix as a view Matrix.
   *
   * @name asView
   * @memberof Matrix.prototype
   * @function
   * @throws {DimensionError} if this Matrix is not a 4x4 Matrix.
   * @throws {TypeError} if position is not and instance of Array.
   * @param {Array} position - An array containing the starting x, y, and z
   * coordinates of this view Matrix.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.asView = function(position) {
    dimCheck(this, {
      dim: [4, 4]
    });
    position = position || [0, 0, 0];
    if (!(position instanceof Array)) {
      var msg = 'Matrix.prototype.asView argument must be an instance of Array'
      throw new TypeError(msg);
    }
    this.dim.set([4, 4]);
    this.identity();
    this[3] = position[0];
    this[7] = position[1];
    this[11] = position[2];
    return this;
  }

  /**
   * @name asOrtographic
   * @memberof Matrix.prototype
   * @function
   * @throws {DimensionError} if this Matrix is not a 4x4 Matrix.
   * @throws {RangeError} if the arguments to right and left, top and bottom, or
   * far and near are equal.
   * @param {number} left - The x coordinate of the left frustum plane.
   * @param {number} right - The x coordinate of the right frustum plane.
   * @param {number} bottom - The y coordinate of the bottom frustum plane.
   * @param {number} top - The y coordinate of the top frustum plane.
   * @param {number} near - The z coordinate of the near frustum plane.
   * @param {number} far - The z coordinate of the far frustum plane.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.asOrthographic = function(left, right, bottom, top, near, far) {
    try {
      dimCheck(this, {
        dim: [4, 4]
      });
    } catch (e) {
      e.message = 'Matrix.prototype.asOrthographic requires this matrix to be a 4x4 matrix, ';
      e.message += 'but this matrix is a ' + this.dim[0] + 'x' + this.dim[1] + ' matrix.';
      throw e;
    }

    if (right - left === 0) {
      var msg = 'Matrix.prototype.asOrthographic: right and left cannot have the same value';
      throw new RangeError(msg);
    }
    if (top - bottom === 0) {
      var msg = 'Matrix.prototype.asOrthographic: top and bottom cannot have the same value';
      throw new RangeError(msg);
    }
    if (far - near === 0) {
      var msg = 'iMatrix.prototype.asOrthographic: far and near cannot have the same value';
      throw new RangeError(msg);
    }

    this.dim.set([4, 4]);
    this.identity();
    this[0] = 2 / (right - left);
    this[3] = -(right + left) / (right - left);
    this[5] = 2 / (top - bottom);
    this[6] = 0;
    this[7] = -(top + bottom) / (top - bottom);
    this[10] = -2 / (far - near);
    this[11] = -(far + near) / (far - near);

    return this;
  };

  /**
   * @name asPerspective
   * @memberof Matrix.prototype
   * @function
   * @throws {DimensionError} if this Matrix is not a 4x4 Matrix.
   * @throws {RangeError} if the arguments to near and far are equal or if
   * the argument to aspect is 0.
   * @param {number} near - The distance from the camera to the near clipping
   * plane.
   * @param {number} far - The distance from the camera to the far clipping
   * plane.
   * @param {number} aspect - The aspect ratio.
   * @param {number} fov - The field of view angle.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.asPerspective = function(near, far, aspect, fov) {
    try {
      dimCheck(this, {
        dim: [4, 4]
      });
    } catch (e) {
      e.message = 'Matrix.prototype.asPerspective requires this matrix to be a 4x4 matrix, ';
      e.message += 'but this matrix is a ' + this.dim[0] + 'x' + this.dim[1] + ' matrix.';
      throw e;
    }

    if (far - near === 0) {
      var msg = 'Matrix.prototype.asPerspective: near and far cannot have the same value.';
      throw new RangeError(msg);
    }
    if (aspect === 0) {
      var msg = 'Matrix.prototype.asPerspective: aspect cannot equal zero.';
      throw new RangeError(msg);
    }
    near = (near === 0) ? Number.MIN_VALUE : near;
    far = (far === 0) ? Number.MIN_VALUE : far;
    fov = (fov === 0) ? Number.MIN_VALUE : fov;


    var top = near * Math.tan(fov);
    var right = top * aspect;

    this.dim.set([4, 4]);
    this.identity();
    this[0] = near / right;
    this[5] = near / top;
    this[10] = -(far + near) / (far - near);
    this[11] = -2 * far * near / (far - near);
    this[14] = -1;
    this[15] = 0;

    return this;
  };

  /**
   * If this Matrix is a 2x2 Matrix, sets this Matrix as a non-homogeneous
   * rotation Matrix. Else if this Matrix is a 3x3 Matrix, sets this Matrix as a
   * homogeneous rotation Matrix.
   *
   * @name asRotation
   * @memberof Matrix.prototype
   * @function
   * @throws {DimensionError} if this Matrix not square or if it is not a 2x2 or
   * 3x3 Matrix.
   * @param {number} angle - The angle of rotation.
   * @returns {Matrix} This Matrix.
   */
  /**
   * If this Matrix is a 3x3 Matrix, sets this Matrix as a non-homogeneous
   * rotation Matrix. Else if this Matrix is a 4x4 Matrix, sets this Matrix as a
   * homogeneous rotation Matrix.
   *
   * @name asRotation^2
   * @memberof Matrix.prototype
   * @function
   * @throws {DimensionError} if this Matrix is not square or if it is not a 3x3 or 4x4 Matrix.
   * @param {Array} axis - The axis of rotation.
   * @param {number} angle - The angle of rotation.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.asRotation = function() {

    if (this.dim[0] > 4 || this.dim[1] > 4) {
      var msg = 'Matrix.prototype.asRotation does not support matrices of dim > 4, ';
      msg += 'but this matrix is a ' + this.dim[0] + 'x' + this.dim[1] + ' matrix';
      throw new DimensionError(msg);
    } else if (this.dim[0] !== this.dim[1]) {
      var msg = 'Matrix.prototype.asRotation requires this matrix to be square, ';
      msg += 'but this matrix is a ' + this.dim[0] + 'x' + this.dim[1] + ' matrix';
      throw new DimensionErrror(msg);
    }

    if (typeof arguments[0] === 'number') {
      var angle = arguments[0];
      var c = Math.cos(angle);
      var s = Math.sin(angle);

      // non-homogeneous 2x2 matrix
      if (this.dim[0] === 2 && this.dim[1] === 2) {
        this[0] = c;
        this[1] = -s;
        this[2] = s;
        this[3] = c;

        // homogeneous 3x3 matrix
      } else if (this.dim[0] === 3 && this.dim[1] === 3) {
        this[0] = c;
        this[1] = -s;
        this[2] = 0;
        this[3] = s;
        this[4] = c;
        this[5] = 0;
        this[6] = 0;
        this[7] = 0;
        this[8] = 0;

      } else {
        var msg = 'Matrix.prototype.asRotation with first argument of type number requires this ';
        msg += 'matrix to be a non-homogeneous 2x2 matrix or a homogeneous 3x3 matrix, but ';
        msg += 'this matrix dim is ' + this.dim[0] + 'x' + this.dim[1] + '.';
        throw new DimensionError(msg);
      }

    } else if (arguments[0] instanceof Array) {
      var axis = arguments[0];
      var x = axis[0];
      var y = axis[1];
      var z = axis[2];

      if (x === 0 && y === 0 && z === 0) {
        throw new RangeError('Matrix.prototype.asRotation: axis cannot be the zero vector');
      }

      var mag = Math.sqrt(x * x + y * y + z * z);
      x /= mag;
      y /= mag;
      z /= mag;

      var c = Math.cos(arguments[1]);
      var s = Math.sin(arguments[1]);
      var t = 1 - c;

      // non-homogeneous 3x3 matrix
      if (this.dim[0] === 3 && this.dim[1] === 3) {
        this[0] = x * x * t + c;
        this[1] = x * y * t - z * s;
        this[2] = x * z * t + y * s;
        this[3] = x * y * t + z * s;
        this[4] = y * y * t + c;
        this[5] = y * z * t - x * s;
        this[6] = x * z * t - y * s;
        this[7] = y * z * t + x * s;
        this[8] = z * z * t + c;

        // homogeneous 4x4 matrix
      } else if (this.dim[0] === 4 && this.dim[1] === 4) {
        this[0] = x * x * t + c;
        this[1] = x * y * t - z * s;
        this[2] = x * z * t + y * s;
        this[3] = 0;
        this[4] = x * y * t + z * s;
        this[5] = y * y * t + c;
        this[6] = y * z * t - x * s;
        this[7] = 0;
        this[8] = x * z * t - y * s;
        this[9] = y * z * t + x * s;
        this[10] = z * z * t + c;
        this[11] = 0;
        this[12] = 0;
        this[13] = 0;
        this[14] = 0;
        this[15] = 1;
      } else {
        var msg = 'Matrix.prototype.asRotation with first argument instnaceof Array requires ';
        msg += 'this matrix to be a non-homogeneous 3x3 matrix or a homogeneous 4x4 matrix, but ';
        msg += 'this matrix dim is ' + this.dim[0] + 'x' + this.dim[1] + '.';
        throw new DimensionError(msg);
      }

    } else {
      var msg = 'Matrix.prototype.asRotation first argument must be of type number or ';
      msg += 'instanceof Array.';
    }

    return this;
  }

  /**
   * @name asTranslation
   * @memberof Matrix.prototype
   * @function
   * @throws {DimensionError} if this Matrix is not square, if this Matrix is a
   * 2x2 Matrix, or if the array argument's length is not one less than the
   * number of columns in this Matrix.
   * @param {Array} array - An array containing the x, y, [...] translation coordinates.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.asTranslation = function(vector) {
    var m = this.dim[0];
    var n = this.dim[1];
    var len = vector.length;

    if (this.dim[0] !== this.dim[1]) {
      var msg = 'Matrix.prototype.asTranslation requires this matrix to be square, ';
      msg += 'but this matrix is a ' + this.dim[0] + 'x' + this.dim[1] + ' matrix';
      throw new DimensionError(msg);
    }
    if (this.dim[0] < 3) {
      var msg = 'Matrix.prototype.asTranslation requries this matrix to have dim >= 3, ';
      msg += 'but this matrix is a ' + this.dim[0] + 'x' + this.dim[1] + ' matrix';
      throw new DimensionError(msg);
    }
    if (m - 1 !== len) {
      var msg = 'Matrix.prototype.asTranslation: argument length must be one less than the number ';
      msg += 'of rows in this matrix';
      throw new DimensionError(msg);
    }

    this.identity();
    for (var i = 0; i < len; ++i) {
      this[(i + 1) * n - 1] = vector[i];
    }
    return this;
  };

  /**
   * Sets the scale values for each dimension or this matrix to the values of
   * array.
   *
   * @name asScale
   * @memberof Matrix.prototype
   * @function
   * @throws {RangeError} if the array argument's length is not one less than
   * the number of columns in this Matrix.
   * @param {Array} array - An array containing the x, y, [...] scale values.
   * @returns {Matrix} This Matrix.
   */
  /**
   * Sets the scale values for each dimension of this matrix to the value of the
   * number argument.
   *
   * @name asScale^2
   * @memberof Matrix.prototype
   * @function
   * @param {number} number - The number to set as the scale value for each
   * dimension of this Matrix.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.asScale = function(arrayOrScalar) {
    var m = this.dim[0];
    var n = this.dim[1];

    this.identity();

    if (typeof arrayOrScalar === 'number') {
      for (var i = 0; i < m - 1; ++i) {
        this[i + i * n] = arrayOrScalar;
      }
    } else {
      if (arrayOrScalar.length !== m - 1) {
        var msg = 'Matrix.prototype.asScale array argument length must be one minus the number ';
        msg += 'of rows in this matrix because asScale only supports homogeneous coordinates.';
        throw new RangeError(msg);
      }
      for (var i = 0; i < m - 1; ++i) {
        this[i + i * n] = arrayOrScalar[i];
      }
    }

    return this;
  };

  /**
   * Rotates this Matrix by angle.
   *
   * @name rotate
   * @memberof Matrix.prototype
   * @function
   * @throws {DimensionError} if this Matrix not square or if it is not a 2x2 or
   * 3x3 Matrix.
   * @param {number} angle - The angle of rotation.
   * @returns {Matrix} This Matrix.
   */
  /**
   * Rotates this Matrix around axis by angle.
   *
   * @name rotate
   * @memberof Matrix.prototype
   * @function
   * @throws {DimensionError} if this Matrix is not square or if it is not a 3x3 or 4x4 Matrix.
   * @param {Array} axis - The axis of rotation.
   * @param {number} angle - The angle of rotation.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.rotate = function(axis, angle) {
    Matrix.cache2.dim.set(this.dim);
    try {
      this.mul(Matrix.cache2.asRotation(axis, angle));
    } catch (e) {
      e.message = e.message.replace('asRotation', 'rotate');
      throw e;
    }

    return this;
  }

  /**
   * @name translate
   * @memberof Matrix.prototype
   * @function
   * @throws {DimensionError} if this Matrix is not square, if this Matrix is a
   * 2x2 Matrix, or if the array argument's length is not one less than the
   * number of columns in this Matrix.
   * @param {Vector} vector - A vector whose entries represent the x, y, [...]
   * translation values.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.translate = function(vector) {
    Matrix.cache2.dim.set(this.dim);
    try {
      this.mul(Matrix.cache2.asTranslation(vector));
    } catch (e) {
      e.message = e.message.replace('asTranslation', 'translate');
      throw e;
    }

    return this;
  }

  /**
   * Scales this Matrix by the values of array.
   *
   * @name scale
   * @memberof Matrix.prototype
   * @function
   * @throws {RangeError} if the array argument's length is not one less than
   * the number of columns in this Matrix.
   * @param {Array} array - An array containing the x, y, [...] scale values.
   * @returns {Matrix} This Matrix.
   */
  /**
   * Scales each dimension of this matrix by the value of the number argument.
   *
   * @name scale^2
   * @memberof Matrix.prototype
   * @function
   * @param {number} number - The number to set as the scale value for each
   * dimension of this Matrix.
   * @returns {Matrix} This Matrix.
   */
  Matrix.prototype.scale = function(arrayOrScalar) {
    Matrix.cache2.dim.set(this.dim);
    try {
      this.mul(Matrix.cache2.asScale(arrayOrScalar));
    } catch (e) {
      e.message = e.message.replace('asScale', 'scale');
      throw e;
    }

    return this;
  }

  /**
   * @name toString
   * @memberof Matrix.prototype
   * @function
   * @returns {string} A string representation of this Matrix.
   */
  Matrix.prototype.toString = function() {
    var str = '';
    var m = this.dim[0];
    var n = this.dim[1];

    for (var i = 0; i < m; ++i) {
      if (i !== 0) {
        str += ' ';
      }
      for (var j = 0; j < n; ++j) {
        str += this[j + i * n];
        if (j !== n - 1) {
          str += ' ';
        }
      }
      if (i !== m - 1) {
        str += '\n';
      }
    }
    return str;
  };

  /**
   * Quaternion type.
   *
   * @name Quaternion
   * @class
   * @extends Dimensional
   */
  function Quaternion() {
    this.t = 1;
    this.v = new Vector(3);
    this.dim = new Dimensions(4);
  }
  Quaternion.prototype = Object.create(Dimensional.prototype);
  Quaternion.prototype.constructor = Quaternion;
  Quaternion.prototype.equals = function(quaternion) {
    return (this.t === quaternion.t && this.v.equals(quaternion.v));
  };

  Quaternion.cache1 = new Quaternion(4);
  Quaternion.cache2 = new Quaternion(4);

  /**
   * @name mul
   * @memberof Quaternion
   * @function
   * @param {Quaternion} q1 - The left multiplicand.
   * @param {Quaternion} q2 - The right multiplicand.
   * @param {Quaternion} [out] - The Quaternion to store the result of
   * the multiplication. If undefined, a new Quaternion is created.
   * @returns {Quaternion} The out Quaternion.
   */
  Quaternion.mul = function(q1, q2, out) {
    out = out || new Quaternion();
    var q1V = q1.v;
    var q2V = q2.v;
    var outV = out.v;
    var w = q1.t;
    var x = q1V[0];
    var y = q1V[1];
    var z = q1V[2];
    var ow = q2.t;
    var ox = q2V[0];
    var oy = q2V[1];
    var oz = q2V[2];

    out.t = w * ow - x * ox - y * oy - z * oz;
    outV[0] = w * ox + x * ow + y * oz - z * oy;
    outV[1] = w * oy - x * oz + y * ow + z * ox;
    outV[2] = w * oz + x * oy - y * ox + z * ow;

    return out;
  };

  /**
   * @name invert
   * @memberof Quaternion
   * @function
   * @param {Quaternion} quaternion - The Quaternion to invert.
   * @param {Quaternion} [out] - The Quaternion to store the result of the inversion.
   * If undefined, a new Quaternion is created.
   * @returns {Quaternion} The out Quaternion.
   */
  Quaternion.invert = function(quaternion, out) {
    out = out || new Quaternion();

    var vin = quaterinon.v;
    var vout = out.v;
    var quad = quaternion.quadrance();
    out.t = quaternion.t * invQuad;
    vout[0] = -vin[0] / quad;
    vout[1] = -vin[1] / quad;
    vout[2] = -vin[2] / quad;

    return out;
  };

  /**
   * @name conjugate
   * @memberof Quaternion
   * @function
   * @param {Quaternion} quaternion - The Quaterion.
   * @param {Quaternion} [out] - The Quaternion to store the conjugate
   * in. If undefined, a new Quaternion is created.
   * @returns {Quaternion} The out Quaternion.
   */
  Quaternion.conjugate = function(quaternion, out) {
    out = out || new Quaternion();
    var vin = quaternion.v;
    var vout = out.v;

    out.t = quaternion.t;
    vout[0] = -vin[0];
    vout[1] = -vin[1];
    vout[2] = -vin[2];

    return out;
  };

  /**
   * @name quadrance
   * @memberof Quaternion.prototype
   * @function
   * @returns {number} The quadrance of this Quaternion.
   */
  Quaternion.prototype.quadrance = function() {
    var v = this.v;
    return this.t * this.t + v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
  };

  /**
   * @name length
   * @memberof Quaternion.prototype
   * @function
   * @returns {number} The length (magnitude) of this Quaternion.
   */
  Quaternion.prototype.length = function() {
    return Math.sqrt(this.quadrance());
  };

  /**
   * Inverts this Quaternion.
   *
   * @name invert
   * @memberof Quaternion.prototype
   * @function
   * @returns {Quaternion} This Quaternion.
   */
  Quaternion.prototype.invert = function() {
    var v = this.v;
    var quad = this.quadrance();
    this.t /= quad;
    v[0] /= -quad;
    v[1] /= -quad;
    v[2] /= -quad;
    return this;
  };

  /**
   * Conjugates this Quaternion.
   *
   * @name conjugate
   * @memberof Quaternion.prototype
   * @function
   * @returns {Quaternion} This Quaternion.
   */
  Quaternion.prototype.conjugate = function() {
    this.v.scale(-1);
    return this;
  };

  /**
   * Normalizes this Quaternion.
   *
   * @name normalize
   * @memberof Quaternion.prototype
   * @function
   * @returns {Quaternion} This Quaternion
   */
  Quaternion.prototype.normalize = function() {
    var len = this.length();

    this.t /= len;
    this.v.scale(1 / len);

    return this;
  };

  /**
   * Rotates a Vector by this Quaternion.
   *
   * @name rotate
   * @memberof Quaternion.prototype
   * @function
   * @param {Vector} vector - The Vector to rotate by this Quaternion.
   * @returns {Vector} The rotated Vector.
   */
  Quaternion.prototype.rotate = function(vector) {
    var v = this.v;
    var t = this.t;
    var cx = Vector.cross(v, [vector[0], vector[1], vector[2]]).scale(2);
    var cx2 = Vector.cross(v, cx);
    vector[0] += t * cx[0] + cx2[0];
    vector[1] += t * cx[1] + cx2[1];
    vector[2] += t * cx[2] + cx2[2];
    return vector;
  };

  /**
   * Rotates this Quaternion by quaternion.
   *
   * @name mul
   * @memberof Quaternion.prototype
   * @function
   * @param {Quaternion} quaternion - The right-side multiplicand.
   * @returns {Quaternion} This Quaternion.
   */
  Quaternion.prototype.mul = function(quaternion) {
    var thisV = this.v;
    var otherV = quaternion.v;
    var w = this.t;
    var x = thisV[0];
    var y = thisV[1];
    var z = thisV[2];
    var ow = quaternion.t;
    var ox = otherV[0];
    var oy = otherV[1];
    var oz = otherV[2];

    this.t = w * ow - x * ox - y * oy - z * oz;
    thisV[0] = w * ox + x * ow + y * oz - z * oy;
    thisV[1] = w * oy - x * oz + y * ow + z * ox;
    thisV[2] = w * oz + x * oy - y * ox + z * ow;

    return this;
  };

  /**
   * Sets the orientation of this Quaternion to a rotation of angle around axis.
   *
   * @name setAxisAngle
   * @memberof Quaternion.prototype
   * @function
   * @throws {TypeError} if axis is not an instance of Array or if angle is not
   * of type number.
   * @param {Array} axis - The axis of rotation.
   * @param {number} angle - The angle of rotation.
   * @returns {Quaternion} This Quaternion.
   */
  Quaternion.prototype.setAxisAngle = function(axis, angle) {
    if (!(axis instanceof Array)) {
      var msg = 'Quaternion.prototype.setAxisAngle axis argument must be an instance of Array';
      throw new TypeError(msg);
    }
    if (typeof angle !== 'number') {
      var msg = 'Quaternion.prototype.SetAxisAngle angle argument must be of type Number';
      throw new TypeError(msg);
    }
    var len = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
    this.t = Math.cos(angle / 2);
    this.v.set(axis).scale(Math.sin(angle / 2) / len);
    return this;
  };

  /**
   * @name toMatrix
   * @memberof Quaternion.prototype
   * @function
   * @throws {DimensionError} if matrix is not a 3x3 matrix, 4x4 matrix, or undefined.
   * @param {Matrix} [matrix] - The matrix to be set as a
   * rotation-matrix representation of this Quaternion. If undefined, a new
   * Matrix is created.
   * @returns {Matrix} The Matrix.
   */
  Quaternion.prototype.toMatrix = function(matrix) {
    if (typeof matrix === 'undefined') {
      matrix = new Matrix();
    }

    var v = this.v;
    var w = this.t;
    var x = v[0];
    var y = v[1];
    var z = v[2];
    var s = 2.0 / this.quadrance();
    var xs = x * s;
    var ys = y * s;
    var zs = z * s;
    var wx = w * xs;
    var wy = w * ys;
    var wz = w * zs;
    var xx = x * xs;
    var xy = x * ys;
    var xz = x * zs;
    var yy = y * ys;
    var yz = y * zs;
    var zz = z * zs;

    if (matrix.dim[0] === 4 && matrix.dim[1] === 4) {

      matrix[0] = 1 - (yy + zz);
      matrix[1] = xy - wz;
      matrix[2] = xz + wy;
      matrix[3] = 0;
      matrix[4] = xy + wz;
      matrix[5] = 1 - (xx + zz);
      matrix[6] = yz - wx;
      matrix[7] = 0;
      matrix[8] = xz - wy;
      matrix[9] = yz + wx;
      matrix[10] = 1 - (xx + yy);
      matrix[11] = 0;
      matrix[12] = 0;
      matrix[13] = 0;
      matrix[14] = 0;
      matrix[15] = 1;

    } else if (matrix.dim[0] === 3 && matrix.dim[1] === 3) {
      matrix[0] = 1 - (yy + zz);
      matrix[1] = xy + wz;
      matrix[2] = xz - wy;
      matrix[3] = xy - wz;
      matrix[4] = 1 - (xx + zz);
      matrix[5] = yz + wx;
      matrix[6] = xz + wy;
      matrix[7] = yz - wx;
      matrix[8] = 1 - (xx + yy);

    } else {
      var msg = 'Quaternion.prototype.toMatrix argument must be undefined, a 3x3 matrix, ';
      msg += 'or a 4x4 matrix.';
      throw new DImensionError(msg);
    }

    return matrix;
  };

  /**
   * @name toTVArray
   * @memberof Quaternion.prototype
   * @function
   * @returns {Array} An array representation of this Quaternion with
   * coordinates in the folling order: [w, x, y, z].
   */
  Quaternion.prototype.toTVArray = function() {
    return [this.t, this.v[0], this.v[1], this.v[2]];
  };

  /**
   * @name toVTArray
   * @memberof Quaternion.prototype
   * @function
   * @returns {Array} An array representation of this Quaternion with
   * coordinates in the following order: [x, y, z, w].
   */
  Quaternion.prototype.toVTArray = function() {
    return [this.v[0], this.v[1], this.v[2], this.t];
  };

  /**
   * @name toString
   * @memberof Quaternion.prototype
   * @function
   * @returns {string} A string representation of this Quaternion.
   */
  Quaternion.prototype.toString = function() {
    return 'r: ' + this.t + ', i: [ ' + this.v[0] + ', ' + this.v[1] + ', ' + this.v[2] + ' ]';
  };

  /**
   * Attitude type.
   * Provides yaw, pitch, and roll attitude rotations.
   * Uses a quaternion and an orthogonal 3x3 matrix under the hood.
   *
   * @name Attitude
   * @class
   * @extends Dimensional
   */
  function Attitude() {
    this.dim = new Dimensions([3]);
    // lateral axis basis
    this[0] = new Vector([1, 0, 0]);
    // normal axis basis
    this[1] = new Vector([0, 1, 0]);
    // longitudinal axis basis
    this[2] = new Vector([0, 0, 1]);

    /**
     * A quaternion representing the orientation of this Attitude.
     *
     * @name orientation
     * @memberof Attitude
     * @instance
     */
    this.orientation = new Quaternion();

    /**
     * The cross Vector.
     *
     * @name cross
     * @memberof Attitude
     * @instance
     */
    Object.defineProperty(this, 'cross', {
      get: function() {
        return this[0];
      },
      set: function(vec) {
        this[0] = vec;
      }
    });

    /**
     * The up Vector.
     *
     * @name up
     * @memberof Attitude
     * @instance
     */
    Object.defineProperty(this, 'up', {
      get: function() {
        return this[1];
      },
      set: function(vec) {
        this[1] = vec;
      }
    });

    /**
     * The look Vector
     *
     * @name look
     * @memberof Attitude
     * @instance
     */
    Object.defineProperty(this, 'look', {
      get: function() {
        return this[2];
      },
      set: function(vec) {
        this[2] = vec;
      }
    });
  }
  Attitude.prototype = Object.create(Dimensional.prototype);
  Attitude.prototype.constructor = Attitude;
  Attitude.rotator = new Quaternion();

  /**
   * @name toMatrix
   * @memberof Attitude
   * @function
   * @throws {DimensionError} if matrix argument is not a 3x3 Matrix, a 4x4
   * Matrix, or undefined.
   * @param {Attitude} attitude - The Attitude.
   * @param {Matrix} [matrix] - the Matrix to be set as a rotation-matrix
   * representation of attitude. If undefined, creates a new Matrix.
   * @returns {Matrix} - The matrix.
   */
  Attitude.toMatrix = function(attitude, matrix) {
    if (typeof matrix === 'undefined') {
      matrix = new Matrix();
    }
    matrix.identity();
    var c = attitude.cross;
    var u = attitude.up;
    var l = attitude.look;

    if (matrix.dim[0] === 3 && matrix.dim[1] === 3) {
      matrix[0] = c[0];
      matrix[1] = c[1];
      matrix[2] = c[2];
      matrix[3] = u[0];
      matrix[4] = u[1];
      matrix[5] = u[2];
      matrix[6] = l[0];
      matrix[7] = l[1];
      matrix[8] = l[2];
    } else if (matrix.dim[0] === 4 && matrix.dim[1] === 4) {
      matrix[0] = c[0];
      matrix[1] = c[1];
      matrix[2] = c[2];
      matrix[4] = u[0];
      matrix[5] = u[1];
      matrix[6] = u[2];
      matrix[8] = l[0];
      matrix[9] = l[1];
      matrix[10] = l[2];
    } else {
      var msg = 'Attitude.toMatrix matrix argument must be undefined or a 3x3 or 4x4 Matrix.';
      throw new DimensionsEror(msg);
    }

    return matrix;
  };

  /**
   * Rotates this Attitude around its cross axis by theta.
   *
   * @name pitch
   * @memberof Attitude.prototype
   * @function
   * @param {number} theta - The angle of rotation.
   * @returns {Attitude} This Attitude.
   */
  Attitude.prototype.pitch = function(theta) {
    var rotator = Attitude.rotator;
    rotator.setAxisAngle(this[0], -theta);
    rotator.rotate(this[1]);
    rotator.rotate(this[2]);
    rotator.setAxisAngle(this[0], theta);
    this.orientation.mul(rotator);
    return this;
  }

  /**
   * Rotates this Attitude around its up axis by theta.
   *
   * @name yaw
   * @memberof Attitude.prototype
   * @function
   * @param {number} theta - The angle or rotation.
   * @returns {Attitude} This Attitude.
   */
  Attitude.prototype.yaw = function(theta) {
    var rotator = Attitude.rotator;
    rotator.setAxisAngle(this[1], -theta);
    rotator.rotate(this[0]);
    rotator.rotate(this[2]);
    rotator.setAxisAngle(this[1], theta);
    this.orientation.mul(rotator);
    return this;
  }

  /**
   * Rotates this Attitude around its look axis by theta.
   *
   * @name roll
   * @memberof Attitude.prototype
   * @function
   * @param {number} theta - The angle of rotation.
   * @returns {Attitude} This Attitude.
   */
  Attitude.prototype.roll = function(theta) {
    var rotator = Attitude.rotator;
    rotator.setAxisAngle(this[2], -theta);
    rotator.rotate(this[0]);
    rotator.rotate(this[1]);
    rotator.setAxisAngle(this[2], theta);
    this.orientation.mul(rotator);
    return this;
  }

  /**
   * @name toMatrix
   * @memberof Attitude.prototype
   * @function
   * @throws {ReferenceError} if matrix argument is undefined.
   * @throws {DimensionError} if matrix argument is not a 3x3 Matrix or a 4x4
   * Matrix.
   * @param {Matrix} matrix - The Matrix to set as a rotation-matrix
   * representation of this Attitude.
   * @returns {Matrix} The Matrix.
   */
  Attitude.prototype.toMatrix = function(matrix) {
    if (typeof Matrix === 'undefined') {
      var msg = 'Attitude.prototype.toMatrix matrix argument must be defined. ';
      msg += 'to create a new Matrix from this attitude use Attitude.toMatrix instead.';
      throw new ReferenceError(msg);
    }
    return Attitude.toMatrix(this, matrix);
  };

  /**
   * @name rotate
   * @memberof Attitude.prototype
   * @function
   * @throws {ReferenceError} if matrix argument is undefined.
   * @param {Matrix} matrix - The Matrix to rotate by this Attitude
   * @returns {matrix} The matrix.
   */
  Attitude.prototype.rotate = function(matrix) {
    var cache = Matrix.cache2;
    cache.dim[0] = matrix.dim[0];
    cache.dim[1] = matrix.dim[1];
    this.toMatrix(cache);

    return matrix.mul(cache);
  };

  /**
   * @name toString
   * @memberof Attitude.prototype
   * @function
   * @returns {string} A string representation of this Attitude.
   */
  Attitude.prototype.toString = function() {
    return this.cross.toString() + "\n " + this.up.toString() + "\n " + this.look.toString();
  };

  /**
   * @name DimensionError
   * @class
   * @extends Error
   * @param {string} message - The message property of this DimensionError.
   */
  /**
   * @name DimensionError^2
   * @class
   * @extends Error
   * @param {Array | number} obj1 - The first object with mismatched dimensions.
   * @param {Array | number} obj2 - The second object with mismatched dimensions.
   */
  function DimensionError(obj1, obj2) {
    if (typeof obj1 === 'string') {
      this.message = obj1;
      this.dims = new Dimensions([null]);
    } else {
      var t1 = obj1.constructor.name;
      var t2 = obj2.constructor.name;
      var d1;

      var d2;
      if (obj1.dim != null) {
        d1 = obj1.dim;
      } else if (obj1 instanceof Array) {
        d1 = obj1.length;
      } else if (typeof obj1 === 'number') {
        d1 = 1;
      } else {
        d1 = [null];
      }

      if (obj2.dim != null) {
        d2 = obj2.dim;
      } else if (obj2 instanceof Array) {
        d2 = obj2.length;
      } else if (typeof obj2 === 'number') {
        d2 = 1;
      } else {
        d2 = [null];
      }

      this.dims = [new Dimensions(d1), new Dimensions(d2)];
      this.dims.equals = function(array) {
        return this[0].equals(array[0]) && this[1].equals(array[1]);
      };

      if (this.dims[0][0] === null) {
        d1 = 'null';
      }
      if (this.dims[1][0] === null) {
        d2 = 'null';
      }

      this.message = 'Dimension mismatch: dim(' + t1 + ') = ' + d1 + '; dim(' + t2 + ') = ' + d2;
    }
  }
  DimensionError.prototype = Object.create(Error.prototype);
  DimensionError.prototype.constructor = DimensionError;

  /**
   * Checks the dimensions of two objects and throws a DimensionError if they do
   * not match.
   *
   * @throws {DimensionError} if the dimensions of obj1 and obj2 do not match.
   * @param {Array | number} obj1 - The first object (or number) to compare.
   * @param {Array | number} obj2 - The second object (or number) to compare.
   * @returns {void}
   */
  xform.dimCheck = function(obj1, obj2) {
    if (obj1.dim != null) {
      var dim = obj2.dim || [obj2.length];
      if (!obj1.dim.equals(dim)) {
        throw new DimensionError(obj1, obj2);
      }
    } else if (obj2.dim != null) {
      var dim = obj1.dim || [obj1.length];
      if (!obj2.dim.equals(dim)) {
        throw new DimensionError(obj1, obj2);
      }
    } else if (obj1.length !== obj2.length) {
      throw new DimensionError(obj1, obj2);
    }
  }

  /**
   * Performs a deep comparison of the entries of two Array-like objects.
   *
   * @param {Array} arr1 - The first Array-like object to compare.
   * @param {Array} arr2 - The second Array-like object to compare.
   * @returns {boolean} True if all entries are equal, false otherwise.
   */
  xform.arrayIndexedEntriesEqual = function(arr1, arr2) {
    var toString = Object.prototype.toString;

    // determine if arr1 and arr2 are array-like
    if (!(arr1 instanceof Array) && !/Array/.test(toString.call(arr1))) {
      return false;
    } else if (!(arr2 instanceof Array) && !/Array/.test(toString.call(arr2))) {
      return false;
    }

    for (var i = 0, len = arr1.length; i < len; ++i) {
      if (arr1[i] instanceof Array && arr2[i] instanceof Array) {
        if (!arrayIndexedEntriesEqual(arr1[i], arr2[i])) {
          return false;
        }
      } else if (arr1[i] !== arr2[i]) {
        return false;
      }
    }
    return true;
  }
}());