// Meteor Generate
// Copyright(c) 2014 Adam Brodzinski <adambrodzinski@gmail.com>
// MIT Licensed

/*global templatePath */
var fs = require('fs-extra');

module.exports = {
  destRoot: 'both/',
  routeSrc: templatePath + 'router/routes.js',
  routeDest: 'both/routes.js',
  terminateRoute: '});//<end-routes>',

  // \}\);             - });
  // \/\/<end-routes>  - //<end-routes>
  beforeEndOfRouter: /\}\);\/\/<end-routes>/,

  // Public: Copy routes.js from templates folder into users `both/` folder.
  // If file already exists it will not be overwritten.
  //
  create: function(projPath) {
    // handle calling create outside of project and inside
    var rootPath = (projPath) ? projPath : './';

    // create both folder if it doesn't already exisit
    fs.mkdirsSync(rootPath);

    // bail if router already exists
    if (fs.existsSync(rootPath + 'both/routes.js')) return;

    // copy router.js from templates/ to project/both/controllers
    fs.copySync(this.routeSrc, rootPath + 'both/routes.js');
    console.log('    Created: both/routes.js');
  },

  // Public: Append the correct route into routes.js. The routes file is
  // examined for the `end-routes` marker and it is removed. The new route
  // is inserted onto the end of the file and the marker is re-appended.
  //
  // resName - The {String} name of the controller action, eg index, show
  //
  appendRoute: function(resName, action) {
    var route, newContent, oldFileStr, newFileStr, res;
    resName = resName || '';

    res = require('./parse_name')(resName)

    // set correct route for the required action
    // TODO Pascal case namespaces, camelcase routes
    switch (action) {
      case 'index':
        route = "  this.route('" + res.camelPlural + "',     { path: '/"+resName+"',          controller: "+res.pascalPlural+"Controller.Index });";
        break;
      case 'new':
        route = "  this.route('" + res.camelPlural + "New',  { path: '/"+resName+"/new',      controller: "+res.pascalPlural+"Controller.New });";
        break;
      case 'show':
        route = "  this.route('" + res.camelPlural + "Show', { path: '/"+resName+"/:id',      controller: "+res.pascalPlural+"Controller.Show });";
        break;
      case 'edit':
        route = "  this.route('" + res.camelPlural + "Edit', { path: '/"+resName+"/edit/:id', controller: "+res.pascalPlural+"Controller.Edit });";
        break;
      case 'comment_line':
        route = "  // " + resName + " routes";
        break;
      case 'blank_line':
        route = "";
        break;
      default:
        route = "  this.route('UNKNOWN', { path: '/', controller: UNKNOWN.index });";
        break;
    }

    // read routes.js from users folder and save to string
    // concat new route together
    oldFileStr = fs.readFileSync(this.routeDest, {encoding: 'utf-8'});
    newContent = route + "\n" + this.terminateRoute;

    // insert new content into current routes.js string
    // write new content to file `routes/controllers/resource_name.js`
    newFileStr = oldFileStr.replace(this.beforeEndOfRouter, newContent);
    fs.writeFileSync(this.routeDest, newFileStr);
  }
};


// Meteor Generate
// Copyright(c) 2014 Adam Brodzinski <adambrodzinski@gmail.com>
// MIT Licensed
//
// Creates a Meteor controller for Iron Router and custom CRUD methods.
// Saves controller actions to it's resource namespace. File is generated
// in `both/controllers` folder and the filename takes on the resource name.
// When file already exists, the new controllers are appended to the bottom.

/*global templatePath */
var fs = require('fs-extra');

module.exports = {
  controllerPath : 'both/controllers/',
  contrTemplates: templatePath + 'controller/',

  // Public: Creates a controller file & appends any type of controller needed.
  // If no options are passed in it will create all router and CRUD controllers
  //
  // resName - The {String} name of the resource passed in from command line
  // opts -    The options generated from a Commander command entry.
  //
  init: function(resName, opts) {

    // TODO ensure resName is always snake case
    this.contrFile = this.controllerPath + resName + '.js';
    this.resName = resName;
    this.opts = opts;

    // if no args passed in, created all routes
    if (!opts.index && !opts.new && !opts.show && !opts.edit &&
        !opts.create && !opts.update && !opts.destroy) {
      this.opts.all = true;
    }

    this._createBaseController();

    // add a comment line before adding new routes
    require('./router').appendRoute(resName, 'comment_line');

    // print 'created' if file doesn't exist yet (created on first append)
    if (!fs.existsSync(this.contrFile)) {
      console.log('    Created: ' + this.contrFile);
    }

    // Append Iron Router Controllers

    if (opts.index || opts.all) {
      this._appendController('index');
      require('./page').run(this.resName, {index: true});
    }

    if (opts.new || opts.all) {
      this._appendController('new');
      require('./page').run(resName, {'new': true});
    }

    if (opts.show || opts.all) {
      this._appendController('show');
      require('./page').run(resName, {show: true});
    }

    if (opts.edit || opts.all) {
      this._appendController('edit');
      require('./page').run(resName, {edit: true});
    }

    // Append data Controllers

    if (opts.create || opts.all) {
      this._appendController('create');
    }

    if (opts.update || opts.all) {
      this._appendController('update');
    }

    if (opts.destroy || opts.all) {
      this._appendController('destroy');
    }

    // add blank line to routes.js after controller routes
    require('./router').appendRoute(null, 'blank_line');

    // instantiate and add namespace for this resource if needed
    new (require('./namespace'))().add(resName);
  },

  // Private: Streams controller template from templates folder, renames any
  // template variables with `lib/rename.js`and appends the new processed
  // template into user's controller folder `both/controller/posts.js`. If this
  // file does not exist, it will be created. A route is appended to `routes.js`.
  //
  // action - The {String} name of the controller action, eg index, show
  //
  _appendController: function(action) {
    var templateStr = fs.readFileSync(this.contrTemplates + action + '.js',
                                 {encoding: 'utf-8'});

    // rename template variables and append to controller file
    templateStr = require('./rename')(this.resName, templateStr);
    fs.appendFileSync(this.contrFile, templateStr);

    // add a route for new controller
    if (action !== 'create' && action !== 'update' && action !== 'destroy') {
      require('./router').appendRoute(this.resName, action);
      console.log('    Added Route: ' + this.resName + " " + action);
    }
  },

  // Private: Copy base app controller from templates dir to user project
  // folder `both/controllers/app.js` Will not overwrite existing file.
  //
  _createBaseController: function() {
    if (fs.existsSync(this.controllerPath + '_app.js')) return;

    fs.copySync(this.contrTemplates+'_app.js', this.controllerPath+'_app.js');
    console.log('    Created: '+ this.controllerPath +'_app.js');
  }
};


(function() {

    'use strict';

    var EPSILON = require('./Epsilon');

    /**
     * Instantiates a Vec4 object.
     * @class Vec4
     * @classdesc A four component vector.
     */
    function Vec4() {
        switch ( arguments.length ) {
            case 1:
                // array or VecN argument
                var argument = arguments[0];
                this.x = argument.x || argument[0] || 0.0;
                this.y = argument.y || argument[1] || 0.0;
                this.z = argument.z || argument[2] || 0.0;
                this.w = argument.w || argument[3] || 0.0;
                break;
            case 4:
                // individual component arguments
                this.x = arguments[0];
                this.y = arguments[1];
                this.z = arguments[2];
                this.w = arguments[3] || 0.0;
                break;
            default:
                this.x = 0.0;
                this.y = 0.0;
                this.z = 0.0;
                this.w = 0.0;
                break;
        }
    }

    /**
     * Returns a new Vec4 with each component negated.
     * @memberof Vec4
     *
     * @returns {Vec4} The negated vector.
     */
    Vec4.prototype.negate = function() {
        return new Vec4( -this.x, -this.y, -this.z, -this.w );
    };

    /**
     * Adds the vector with the provided vector argument, returning a new Vec4
     * object representing the sum.
     * @memberof Vec4
     *
     * @param {Vec4|Array} that - The vector to add.
     *
     * @returns {Vec4} The sum of the two vectors.
     */
    Vec4.prototype.add = function( that ) {
        if ( that instanceof Array ) {
            return new Vec4(
                this.x + that[0],
                this.y + that[1],
                this.z + that[2],
                this.w + that[3] );
        }
        return new Vec4(
            this.x + that.x,
            this.y + that.y,
            this.z + that.z,
            this.w + that.w );
    };

    /**
     * Subtracts the provided vector argument from the vector, returning a new Vec4
     * object representing the difference.
     * @memberof Vec4
     *
     * @param {Vec4|Array} - The vector to subtract.
     *
     * @returns {Vec4} The difference of the two vectors.
     */
    Vec4.prototype.sub = function( that ) {
        if ( that instanceof Array ) {
            return new Vec4(
                this.x - that[0],
                this.y - that[1],
                this.z - that[2],
                this.w - that[3] );
        }
        return new Vec4(
            this.x - that.x,
            this.y - that.y,
            this.z - that.z,
            this.w - that.w );
    };

    /**
     * Multiplies the vector with the provided scalar argument, returning a new Vec4
     * object representing the scaled vector.
     * @memberof Vec4
     *
     * @param {number} - The scalar to multiply the vector by.
     *
     * @returns {Vec4} The scaled vector.
     */
    Vec4.prototype.multScalar = function( that ) {
        return new Vec4(
            this.x * that,
            this.y * that,
            this.z * that,
            this.w * that );
    };

    /**
     * Divides the vector with the provided scalar argument, returning a new Vec4
     * object representing the scaled vector.
     * @memberof Vec4
     *
     * @param {number} - The scalar to divide the vector by.
     *
     * @returns {Vec4} The scaled vector.
     */
    Vec4.prototype.divScalar = function( that ) {
        return new Vec4(
            this.x / that,
            this.y / that,
            this.z / that,
            this.w / that );
    };

    /**
     * Calculates and returns the dot product of the vector and the provided
     * vector argument.
     * @memberof Vec4
     *
     * @param {Vec4|Array} - The other vector argument.
     *
     * @returns {number} The dot product.
     */
    Vec4.prototype.dot = function( that ) {
        if ( that instanceof Array ) {
            return ( this.x * that[0] ) +
                ( this.y * that[1] ) +
                ( this.z * that[2] ) +
                ( this.w * that[3] );
        }
        return ( this.x * that.x ) +
            ( this.y * that.y ) +
            ( this.z * that.z ) +
            ( this.w * that.w );
    };

    /**
     * If no argument is provided, this function returns the scalar length of
     * the vector. If an argument is provided, this method will return a new
     * Vec4 scaled to the provided length.
     * @memberof Vec4
     *
     * @param {number} - The length to scale the vector to. Optional.
     *
     * @returns {number|Vec4} Either the length, or new scaled vector.
     */
    Vec4.prototype.length = function( length ) {
        if ( length === undefined ) {
            var len = this.dot( this );
            if ( Math.abs( len - 1.0 ) < EPSILON ) {
                return len;
            } else {
                return Math.sqrt( len );
            }
        }
        return this.normalize().multScalar( length );
    };

    /**
     * Returns the squared length of the vector.
     * @memberof Vec4
     *
     * @returns {number} The squared length of the vector.
     */
    Vec4.prototype.lengthSquared = function() {
        return this.dot( this );
    };

    /**
     * Returns true if the vector components match those of a provided vector.
     * An optional epsilon value may be provided.
     * @memberof Vec4
     *
     * @param {Vec4|Array} that - The vector to test equality with.
     * @param {number} epsilon - The epsilon value. Optional.
     *
     * @returns {boolean} Whether or not the vector components match.
     */
    Vec4.prototype.equals = function( that, epsilon ) {
        var x = that.x !== undefined ? that.x : that[0],
            y = that.y !== undefined ? that.y : that[1],
            z = that.z !== undefined ? that.z : that[2],
            w = that.w !== undefined ? that.w : that[3];
        epsilon = epsilon === undefined ? 0 : epsilon;
        return ( this.x === x || Math.abs( this.x - x ) <= epsilon ) &&
            ( this.y === y || Math.abs( this.y - y ) <= epsilon ) &&
            ( this.z === z || Math.abs( this.z - z ) <= epsilon ) &&
            ( this.w === w || Math.abs( this.w - w ) <= epsilon );
    };

    /**
     * Returns a new Vec4 of unit length.
     * @memberof Vec4
     *
     * @returns {Vec4} The vector of unit length.
     */
    Vec4.prototype.normalize = function() {
        var mag = this.length();
        if ( mag === 0 ) {
            throw 'Cannot normalize a vector of zero length';
        }
        return new Vec4(
            this.x / mag,
            this.y / mag,
            this.z / mag,
            this.w / mag );
    };

    /**
     * Returns a random Vec4 of unit length.
     * @memberof Vec4
     *
     * @returns {Vec4} A random vector of unit length.
     */
    Vec4.random = function() {
        return new Vec4(
            Math.random(),
            Math.random(),
            Math.random(),
            Math.random() ).normalize();
    };

    /**
     * Returns a string representation of the vector.
     * @memberof Vec4
     *
     * @returns {String} The string representation of the vector.
     */
    Vec4.prototype.toString = function() {
        return this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w;
    };

    /**
     * Returns an array representation of the vector.
     * @memberof Vec4
     *
     * @returns {Array} The vector as an array.
     */
    Vec4.prototype.toArray = function() {
        return [ this.x, this.y, this.z, this.w ];
    };

    module.exports = Vec4;

}());


'use strict'

var formatter = require('format')

var fault = create(Error)

module.exports = fault

fault.eval = create(EvalError)
fault.range = create(RangeError)
fault.reference = create(ReferenceError)
fault.syntax = create(SyntaxError)
fault.type = create(TypeError)
fault.uri = create(URIError)

fault.create = create

// Create a new `EConstructor`, with the formatted `format` as a first argument.
function create(EConstructor) {
  FormattedError.displayName = EConstructor.displayName || EConstructor.name

  return FormattedError

  function FormattedError(format) {
    if (format) {
      format = formatter.apply(null, arguments)
    }

    return new EConstructor(format)
  }
}


(function() {

    'use strict';

    var Vec3 = require('./Vec3');
    var Vec4 = require('./Vec4');
    var Mat33 = require('./Mat33');
    var EPSILON = require('./Epsilon');

    /**
     * Instantiates a Mat44 object.
     * @class Mat44
     * @classdesc A 4x4 column-major matrix.
     */
    function Mat44( that ) {
        that = that || [
            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        ];
        if ( that instanceof Array ) {
            this.data = that;
        } else {
            this.data = new Array( 16 );
            this.data[0] = that.data[0];
            this.data[1] = that.data[1];
            this.data[2] = that.data[2];
            this.data[3] = that.data[3];
            this.data[4] = that.data[4];
            this.data[5] = that.data[5];
            this.data[6] = that.data[6];
            this.data[7] = that.data[7];
            this.data[8] = that.data[8];
            this.data[9] = that.data[9];
            this.data[10] = that.data[10];
            this.data[11] = that.data[11];
            this.data[12] = that.data[12];
            this.data[13] = that.data[13];
            this.data[14] = that.data[14];
            this.data[15] = that.data[15];
        }
    }

    /**
     * Returns a row of the matrix as a Vec4 object.
     * @memberof Mat44
     *
     * @param {number} index - The 0-based row index.
     * @param {Vec3|Array} vec - The vector to replace the row. Optional.
     *
     * @returns {Vec4} The row vector.
     */
    Mat44.prototype.row = function( index, vec ) {
        if ( vec ) {
            this.data[0+index] = vec[0] || vec.x;
            this.data[4+index] = vec[1] || vec.y;
            this.data[8+index] = vec[2] || vec.z;
            this.data[12+index] = vec[3] || vec.w;
            return this;
        }
        return new Vec4(
            this.data[0+index],
            this.data[4+index],
            this.data[8+index],
            this.data[12+index] );
    };

    /**
     * Returns a column of the matrix as a Vec4 object.
     * @memberof Mat44
     *
     * @param {number} index - The 0-based col index.
     * @param {Vec3|Array} vec - The vector to replace the col. Optional.
     *
     * @returns {Vec4} The column vector.
     */
    Mat44.prototype.col = function( index, vec ) {
        if ( vec ) {
            this.data[0+index*4] = vec[0] || vec.x;
            this.data[1+index*4] = vec[1] || vec.y;
            this.data[2+index*4] = vec[2] || vec.z;
            this.data[3+index*4] = vec[3] || vec.w;
            return this;
        }
        return new Vec4(
            this.data[0+index*4],
            this.data[1+index*4],
            this.data[2+index*4],
            this.data[3+index*4] );
    };

    /**
     * Returns the identity matrix.
     * @memberof Mat44
     *
     * @returns {Mat44} The identiy matrix.
     */
    Mat44.identity = function() {
        return new Mat44();
    };

    /**
     * Returns a scale matrix.
     * @memberof Mat44
     *
     * @param {Vec3|Array|number} scale - The scalar or vector scaling factor.
     *
     * @returns {Mat44} The scale matrix.
     */
    Mat44.scale = function( scale ) {
        if ( typeof scale === 'number' ) {
            return new Mat44([
                scale, 0, 0, 0,
                0, scale, 0, 0,
                0, 0, scale, 0,
                0, 0, 0, 1
            ]);
        } else if ( scale instanceof Array ) {
            return new Mat44([
                scale[0], 0, 0, 0,
                0, scale[1], 0, 0,
                0, 0, scale[2], 0,
                0, 0, 0, 1
            ]);
        }
        return new Mat44([
            scale.x, 0, 0, 0,
            0, scale.y, 0, 0,
            0, 0, scale.z, 0,
            0, 0, 0, 1
        ]);
    };

    /**
     * Returns a translation matrix.
     * @memberof Mat44
     *
     * @param {Vec3|Array} translation - The translation vector.
     *
     * @returns {Mat44} The translation matrix.
     */
    Mat44.translation = function( translation ) {
        if ( translation instanceof Array ) {
            return new Mat44([
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                translation[0], translation[1], translation[2], 1
            ]);
        }
        return new Mat44([
            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            translation.x, translation.y, translation.z, 1
        ]);
    };

    /**
     * Returns a rotation matrix defined by an axis and an angle.
     * @memberof Mat44
     *
     * @param {number} angle - The angle of the rotation, in radians.
     * @param {Vec3} axis - The axis of the rotation.
     *
     * @returns {Mat44} The rotation matrix.
     */
    Mat44.rotation = function( angle, axis ) {
        if ( axis instanceof Array ) {
            axis = new Vec3( axis );
        }
        // zero vector, return identity
        if ( axis.lengthSquared() === 0 ) {
            throw 'Cannot create rotation matrix for a zero vector axis';
        }
        var normAxis = axis.normalize(),
            x = normAxis.x,
            y = normAxis.y,
            z = normAxis.z,
            modAngle = ( angle > 0 ) ? angle % (2*Math.PI) : angle % (-2*Math.PI),
            s = Math.sin( modAngle ),
            c = Math.cos( modAngle ),
            xx = x * x,
            yy = y * y,
            zz = z * z,
            xy = x * y,
            yz = y * z,
            zx = z * x,
            xs = x * s,
            ys = y * s,
            zs = z * s,
            one_c = 1.0 - c;
        return new Mat44([
            (one_c * xx) + c, (one_c * xy) + zs, (one_c * zx) - ys, 0,
            (one_c * xy) - zs, (one_c * yy) + c, (one_c * yz) + xs, 0,
            (one_c * zx) + ys, (one_c * yz) - xs, (one_c * zz) + c, 0,
            0, 0, 0, 1
        ]);
    };

    /**
     * Returns a rotation matrix to rotate a vector from one direction to
     * another.
     * @memberof Mat44
     *
     * @param {Vec3} from - The starting direction.
     * @param {Vec3} to - The ending direction.
     *
     * @returns {Mat44} The matrix representing the rotation.
     */
    Mat44.rotationFromTo = function( fromVec, toVec ) {
        /*
        This method is based on the code from:
            Tomas Mller, John Hughes
            Efficiently Building a Matrix to Rotate One Vector to Another
            Journal of Graphics Tools, 4(4):1-4, 1999
        */
        fromVec = new Vec3( fromVec ).normalize();
        toVec = new Vec3( toVec ).normalize();
        var e = fromVec.dot( toVec );
        var f = Math.abs( e );
        var x, u, v;
        var fx, fy, fz;
        var ux, uz;
        var c1, c2, c3;
        if ( f > 1.0 - EPSILON ) {
            // 'from' and 'to' almost parallel
            // nearly orthogonal
            fx = Math.abs( fromVec.x );
            fy = Math.abs( fromVec.y );
            fz = Math.abs( fromVec.z );
            if ( fx < fy ) {
                if ( fx < fz ) {
                    x = new Vec3( 1, 0, 0 );
                } else {
                    x = new Vec3( 0, 0, 1 );
                }
            } else {
                if ( fy < fz ) {
                    x = new Vec3( 0, 1, 0 );
                } else {
                    x = new Vec3( 0, 0, 1 );
                }
            }
            u = x.sub( fromVec );
            v = x.sub( toVec );
            c1 = 2.0 / u.dot( u );
            c2 = 2.0 / v.dot( v );
            c3 = c1*c2 * u.dot( v );
            // set matrix entries
            return new Mat44([
                -c1*u.x*u.x - c2*v.x*v.x + c3*v.x*u.x + 1.0,
                -c1*u.y*u.x - c2*v.y*v.x + c3*v.y*u.x,
                -c1*u.z*u.x - c2*v.z*v.x + c3*v.z*u.x,
                0.0,
                -c1*u.x*u.y - c2*v.x*v.y + c3*v.x*u.y,
                -c1*u.y*u.y - c2*v.y*v.y + c3*v.y*u.y + 1.0,
                -c1*u.z*u.y - c2*v.z*v.y + c3*v.z*u.y,
                1.0,
                -c1*u.x*u.z - c2*v.x*v.z + c3*v.x*u.z,
                -c1*u.y*u.z - c2*v.y*v.z + c3*v.y*u.z,
                -c1*u.z*u.z - c2*v.z*v.z + c3*v.z*u.z + 1.0,
                 0.0,
                 0.0,
                 0.0,
                 0.0,
                 1.0
            ]);
        }
        // the most common case, unless 'from'='to', or 'to'=-'from'
        v = fromVec.cross( toVec );
        u = 1.0 / ( 1.0 + e );    // optimization by Gottfried Chen
        ux = u * v.x;
        uz = u * v.z;
        c1 = ux * v.y;
        c2 = ux * v.z;
        c3 = uz * v.y;
        return new Mat44([
            e + ux * v.x,
            c1 + v.z,
            c2 - v.y,
            0.0,
            c1 - v.z,
            e + u * v.y * v.y,
            c3 + v.x,
            0.0,
            c2 + v.y,
            c3 - v.x,
            e + uz * v.z,
            0.0,
            0.0,
            0.0,
            0.0,
            1.0
        ]);
    };

    /**
     * Adds the matrix with the provided matrix argument, returning a new Ma33
     * object.
     * @memberof Mat44
     *
     * @param {Mat33|Array} that - The matrix to add.
     *
     * @returns {Mat44} The sum of the two matrices.
     */
    Mat44.prototype.addMat33 = function( that ) {
        that = ( that instanceof Array ) ? that : that.data;
        return new Mat44([
            this.data[0] + that[0],
            this.data[1] + that[1],
            this.data[2] + that[2],
            this.data[3],
            this.data[4] + that[3],
            this.data[5] + that[4],
            this.data[6] + that[5],
            this.data[7],
            this.data[8] + that[6],
            this.data[9] + that[7],
            this.data[10] + that[8],
            this.data[11],
            this.data[12],
            this.data[13],
            this.data[14],
            this.data[15]
        ]);
    };

    /**
     * Adds the matrix with the provided matrix argument, returning a new Ma33
     * object.
     * @memberof Mat44
     *
     * @param {Mat44|Array} that - The matrix to add.
     *
     * @returns {Mat44} The sum of the two matrices.
     */
    Mat44.prototype.addMat44 = function( that ) {
        that = ( that instanceof Array ) ? that : that.data;
        return new Mat44([
            this.data[0] + that[0],
            this.data[1] + that[1],
            this.data[2] + that[2],
            this.data[3] + that[3],
            this.data[4] + that[4],
            this.data[5] + that[5],
            this.data[6] + that[6],
            this.data[7] + that[7],
            this.data[8] + that[8],
            this.data[9] + that[9],
            this.data[10] + that[10],
            this.data[11] + that[11],
            this.data[12] + that[12],
            this.data[13] + that[13],
            this.data[14] + that[14],
            this.data[15] + that[15]
        ]);
    };

    /**
     * Subtracts the provided matrix argument from the matrix, returning a new
     * Mat44 object.
     * @memberof Mat44
     *
     * @param {Mat33|Array} that - The matrix to add.
     *
     * @returns {Mat44} The difference of the two matrices.
     */
    Mat44.prototype.subMat33 = function( that ) {
        that = ( that instanceof Array ) ? that : that.data;
        return new Mat44([
            this.data[0] - that[0],
            this.data[1] - that[1],
            this.data[2] - that[2],
            this.data[3],
            this.data[4] - that[3],
            this.data[5] - that[4],
            this.data[6] - that[5],
            this.data[7],
            this.data[8] - that[6],
            this.data[9] - that[7],
            this.data[10] - that[8],
            this.data[11],
            this.data[12],
            this.data[13],
            this.data[14],
            this.data[15]
        ]);
    };

    /**
     * Subtracts the provided matrix argument from the matrix, returning a new
     * Mat44 object.
     * @memberof Mat44
     *
     * @param {Mat44|Array} that - The matrix to add.
     *
     * @returns {Mat44} The difference of the two matrices.
     */
    Mat44.prototype.subMat44 = function( that ) {
        that = ( that instanceof Array ) ? that : that.data;
        return new Mat44([
            this.data[0] - that[0],
            this.data[1] - that[1],
            this.data[2] - that[2],
            this.data[3] - that[3],
            this.data[4] - that[4],
            this.data[5] - that[5],
            this.data[6] - that[6],
            this.data[7] - that[7],
            this.data[8] - that[8],
            this.data[9] - that[9],
            this.data[10] - that[10],
            this.data[11] - that[11],
            this.data[12] - that[12],
            this.data[13] - that[13],
            this.data[14] - that[14],
            this.data[15] - that[15]
        ]);
    };

    /**
     * Multiplies the provded vector argument by the matrix, returning a new
     * Vec3 object.
     * @memberof Mat44
     *
     * @param {Vec3|Vec4|Array} - The vector to be multiplied by the matrix.
     *
     * @returns {Vec3} The resulting vector.
     */
    Mat44.prototype.multVec3 = function( that ) {
        // ensure 'that' is a Vec3
        // it is safe to only cast if Array since Vec4 has own method
        if ( that instanceof Array ) {
            return new Vec3(
                this.data[0] * that[0] + this.data[4] * that[1] + this.data[8] * that[2] + this.data[12],
                this.data[1] * that[0] + this.data[5] * that[1] + this.data[9] * that[2] + this.data[13],
                this.data[2] * that[0] + this.data[6] * that[1] + this.data[10] * that[2] + this.data[14]
            );
        }
        return new Vec3(
            this.data[0] * that.x + this.data[4] * that.y + this.data[8] * that.z + this.data[12],
            this.data[1] * that.x + this.data[5] * that.y + this.data[9] * that.z + this.data[13],
            this.data[2] * that.x + this.data[6] * that.y + this.data[10] * that.z + this.data[14]
        );
    };

    /**
     * Multiplies the provded vector argument by the matrix, returning a new
     * Vec3 object.
     * @memberof Mat44
     *
     * @param {Vec3|Vec4|Array} - The vector to be multiplied by the matrix.
     *
     * @returns {Vec4} The resulting vector.
     */
    Mat44.prototype.multVec4 = function( that ) {
        // ensure 'that' is a Vec4
        // it is safe to only cast if Array since Vec3 has own method
        if ( that instanceof Array ) {
            return new Vec4(
                this.data[0] * that[0] + this.data[4] * that[1] + this.data[8] * that[2] + this.data[12] * that[3],
                this.data[1] * that[0] + this.data[5] * that[1] + this.data[9] * that[2] + this.data[13] * that[3],
                this.data[2] * that[0] + this.data[6] * that[1] + this.data[10] * that[2] + this.data[14] * that[3],
                this.data[3] * that[0] + this.data[7] * that[1] + this.data[11] * that[2] + this.data[15] * that[3]
            );
        }
        return new Vec4(
            this.data[0] * that.x + this.data[4] * that.y + this.data[8] * that.z + this.data[12] * that.w,
            this.data[1] * that.x + this.data[5] * that.y + this.data[9] * that.z + this.data[13] * that.w,
            this.data[2] * that.x + this.data[6] * that.y + this.data[10] * that.z + this.data[14] * that.w,
            this.data[3] * that.x + this.data[7] * that.y + this.data[11] * that.z + this.data[15] * that.w
        );
    };

    /**
     * Multiplies all components of the matrix by the provded scalar argument,
     * returning a new Mat44 object.
     * @memberof Mat44
     *
     * @param {number} - The scalar to multiply the matrix by.
     *
     * @returns {Mat44} The resulting matrix.
     */
    Mat44.prototype.multScalar = function( that ) {
        return new Mat44([
            this.data[0] * that,
            this.data[1] * that,
            this.data[2] * that,
            this.data[3] * that,
            this.data[4] * that,
            this.data[5] * that,
            this.data[6] * that,
            this.data[7] * that,
            this.data[8] * that,
            this.data[9] * that,
            this.data[10] * that,
            this.data[11] * that,
            this.data[12] * that,
            this.data[13] * that,
            this.data[14] * that,
            this.data[15] * that
        ]);
    };

    /**
     * Multiplies the provded matrix argument by the matrix, returning a new
     * Mat44 object.
     * @memberof Mat44
     *
     * @param {Mat33|Array} - The matrix to be multiplied by the matrix.
     *
     * @returns {Mat44} The resulting matrix.
     */
    Mat44.prototype.multMat33 = function( that ) {
        that = ( that instanceof Array ) ? that : that.data;
        return new Mat44([
            this.data[0] * that[0] + this.data[4] * that[1] + this.data[8] * that[2],
            this.data[1] * that[0] + this.data[5] * that[1] + this.data[9] * that[2],
            this.data[2] * that[0] + this.data[6] * that[1] + this.data[10] * that[2],
            this.data[3] * that[0] + this.data[7] * that[1] + this.data[11] * that[2],
            this.data[0] * that[3] + this.data[4] * that[4] + this.data[8] * that[5],
            this.data[1] * that[3] + this.data[5] * that[4] + this.data[9] * that[5],
            this.data[2] * that[3] + this.data[6] * that[4] + this.data[10] * that[5],
            this.data[3] * that[3] + this.data[7] * that[4] + this.data[11] * that[5],
            this.data[0] * that[6] + this.data[4] * that[7] + this.data[8] * that[8],
            this.data[1] * that[6] + this.data[5] * that[7] + this.data[9] * that[8],
            this.data[2] * that[6] + this.data[6] * that[7] + this.data[10] * that[8],
            this.data[3] * that[6] + this.data[7] * that[7] + this.data[11] * that[8],
            this.data[12],
            this.data[13],
            this.data[14],
            this.data[15]
        ]);
    };

    /**
     * Multiplies the provded matrix argument by the matrix, returning a new
     * Mat44 object.
     * @memberof Mat44
     *
     * @param {Mat44|Array} - The matrix to be multiplied by the matrix.
     *
     * @returns {Mat44} The resulting matrix.
     */
    Mat44.prototype.multMat44 = function( that ) {
        that = ( that instanceof Array ) ? that : that.data;
        return new Mat44([
            this.data[0] * that[0] + this.data[4] * that[1] + this.data[8] * that[2] + this.data[12] * that[3],
            this.data[1] * that[0] + this.data[5] * that[1] + this.data[9] * that[2] + this.data[13] * that[3],
            this.data[2] * that[0] + this.data[6] * that[1] + this.data[10] * that[2] + this.data[14] * that[3],
            this.data[3] * that[0] + this.data[7] * that[1] + this.data[11] * that[2] + this.data[15] * that[3],
            this.data[0] * that[4] + this.data[4] * that[5] + this.data[8] * that[6] + this.data[12] * that[7],
            this.data[1] * that[4] + this.data[5] * that[5] + this.data[9] * that[6] + this.data[13] * that[7],
            this.data[2] * that[4] + this.data[6] * that[5] + this.data[10] * that[6] + this.data[14] * that[7],
            this.data[3] * that[4] + this.data[7] * that[5] + this.data[11] * that[6] + this.data[15] * that[7],
            this.data[0] * that[8] + this.data[4] * that[9] + this.data[8] * that[10] + this.data[12] * that[11],
            this.data[1] * that[8] + this.data[5] * that[9] + this.data[9] * that[10] + this.data[13] * that[11],
            this.data[2] * that[8] + this.data[6] * that[9] + this.data[10] * that[10] + this.data[14] * that[11],
            this.data[3] * that[8] + this.data[7] * that[9] + this.data[11] * that[10] + this.data[15] * that[11],
            this.data[0] * that[12] + this.data[4] * that[13] + this.data[8] * that[14] + this.data[12] * that[15],
            this.data[1] * that[12] + this.data[5] * that[13] + this.data[9] * that[14] + this.data[13] * that[15],
            this.data[2] * that[12] + this.data[6] * that[13] + this.data[10] * that[14] + this.data[14] * that[15],
            this.data[3] * that[12] + this.data[7] * that[13] + this.data[11] * that[14] + this.data[15] * that[15]
        ]);
    };

    /**
     * Divides all components of the matrix by the provded scalar argument,
     * returning a new Mat44 object.
     * @memberof Mat44
     *
     * @param {number} - The scalar to divide the matrix by.
     *
     * @returns {Mat44} The resulting matrix.
     */
    Mat44.prototype.divScalar = function( that ) {
        return new Mat44([
            this.data[0] / that,
            this.data[1] / that,
            this.data[2] / that,
            this.data[3] / that,
            this.data[4] / that,
            this.data[5] / that,
            this.data[6] / that,
            this.data[7] / that,
            this.data[8] / that,
            this.data[9] / that,
            this.data[10] / that,
            this.data[11] / that,
            this.data[12] / that,
            this.data[13] / that,
            this.data[14] / that,
            this.data[15] / that
        ]);
    };

    /**
     * Returns true if the all components match those of a provided matrix.
     * An optional epsilon value may be provided.
     * @memberof Mat44
     *
     * @param {Mat44|Array} that - The matrix to test equality with.
     * @param {number} epsilon - The epsilon value. Optional.
     *
     * @returns {boolean} Whether or not the matrix components match.
     */
    Mat44.prototype.equals = function( that, epsilon ) {
        epsilon = epsilon === undefined ? 0 : epsilon;
        that = ( that instanceof Array ) ? that : that.data;
        return (( this.data[0] === that[0] ) || ( Math.abs( this.data[0] - that[0] ) <= epsilon ) ) &&
            (( this.data[1] === that[1] ) || ( Math.abs( this.data[1] - that[1] ) <= epsilon ) ) &&
            (( this.data[2] === that[2] ) || ( Math.abs( this.data[2] - that[2] ) <= epsilon ) ) &&
            (( this.data[3] === that[3] ) || ( Math.abs( this.data[3] - that[3] ) <= epsilon ) ) &&
            (( this.data[4] === that[4] ) || ( Math.abs( this.data[4] - that[4] ) <= epsilon ) ) &&
            (( this.data[5] === that[5] ) || ( Math.abs( this.data[5] - that[5] ) <= epsilon ) ) &&
            (( this.data[6] === that[6] ) || ( Math.abs( this.data[6] - that[6] ) <= epsilon ) ) &&
            (( this.data[7] === that[7] ) || ( Math.abs( this.data[7] - that[7] ) <= epsilon ) ) &&
            (( this.data[8] === that[8] ) || ( Math.abs( this.data[8] - that[8] ) <= epsilon ) ) &&
            (( this.data[9] === that[9] ) || ( Math.abs( this.data[9] - that[9] ) <= epsilon ) ) &&
            (( this.data[10] === that[10] ) || ( Math.abs( this.data[10] - that[10] ) <= epsilon ) ) &&
            (( this.data[11] === that[11] ) || ( Math.abs( this.data[11] - that[11] ) <= epsilon ) ) &&
            (( this.data[12] === that[12] ) || ( Math.abs( this.data[12] - that[12] ) <= epsilon ) ) &&
            (( this.data[13] === that[13] ) || ( Math.abs( this.data[13] - that[13] ) <= epsilon ) ) &&
            (( this.data[14] === that[14] ) || ( Math.abs( this.data[14] - that[14] ) <= epsilon ) ) &&
            (( this.data[15] === that[15] ) || ( Math.abs( this.data[15] - that[15] ) <= epsilon ) );
    };

    /**
     * Returns an orthographic projection matrix.
     *
     * @param {number} left - The minimum x extent of the projection.
     * @param {number} right - The maximum x extent of the projection.
     * @param {number} bottom - The minimum y extent of the projection.
     * @param {number} top - The maximum y extent of the projection.
     * @param {number} near - The minimum z extent of the projection.
     * @param {number} far - The maximum z extent of the projection.
     *
     * @returns {Mat44} The orthographic projection matrix.
     */
    Mat44.ortho = function( left, right, bottom, top, near, far ) {
        var mat = Mat44.identity();
        mat.data[0] = 2 / ( right - left );
        mat.data[5] = 2 / ( top - bottom );
        mat.data[10] = -2 / ( far - near );
        mat.data[12] = -( ( right + left ) / ( right - left ) );
        mat.data[13] = -( ( top + bottom ) / ( top - bottom ) );
        mat.data[14] = -( ( far + near ) / ( far - near ) );
        return mat;
    };

    /**
     * Returns a perspective projection matrix.
     *
     * @param {number} fovy - The vertical field of view, in radians.
     * @param {number} aspect - The aspect ratio.
     * @param {number} near - The near clipping plane of the frustum.
     * @param {number} far - The far clipping plane of the frustum.
     *
     * @returns {Mat44} The perspective projection matrix.
     */
    Mat44.perspective = function( fovy, aspect, near, far ) {
        var f = 1.0 / Math.tan( fovy / 2.0 );
        var nf = 1.0 / ( near - far );
        var mat = Mat44.identity();
        mat.data[0] = f / aspect;
        mat.data[5] = f;
        mat.data[10] = ( far + near ) * nf;
        mat.data[11] = -1;
        mat.data[14] = ( 2.0 * far * near ) * nf;
        mat.data[15] = 0;
        return mat;
    };

    /**
     * Returns the a view matrix for the affine-transformation of the current matrix.
     *
     * @returns {Mat44} The view matrix.
     */
    Mat44.prototype.view = function() {
        var x = new Vec3( this.data[0], this.data[1], this.data[2] ).normalize();
        var y = new Vec3( this.data[4], this.data[5], this.data[6] ).normalize();
        var z = new Vec3( this.data[8], this.data[9], this.data[10] ).normalize();
        var t = new Vec3( -this.data[12], -this.data[13], -this.data[14] );
        return new Mat44([
            x.x, y.x, z.x, 0,
            x.y, y.y, z.y, 0,
            x.z, y.z, z.z, 0,
            t.dot( x ), t.dot( y ), t.dot( z ), 1
        ]);
    };

    /**
     * Returns the transpose of the matrix.
     * @memberof Mat44
     *
     * @returns {Mat44} The transposed matrix.
     */
    Mat44.prototype.transpose = function() {
        return new Mat44([
            this.data[0],
            this.data[4],
            this.data[8],
            this.data[12],
            this.data[1],
            this.data[5],
            this.data[9],
            this.data[13],
            this.data[2],
            this.data[6],
            this.data[10],
            this.data[14],
            this.data[3],
            this.data[7],
            this.data[11],
            this.data[15]
        ]);
    };

    /**
     * Returns the inverse of the matrix.
     * @memberof Mat44
     *
     * @returns {Mat44} The inverted matrix.
     */
    Mat44.prototype.inverse = function() {
        var inv = new Mat44([
            // col 0
            this.data[5]*this.data[10]*this.data[15] -
                this.data[5]*this.data[11]*this.data[14] -
                this.data[9]*this.data[6]*this.data[15] +
                this.data[9]*this.data[7]*this.data[14] +
                this.data[13]*this.data[6]*this.data[11] -
                this.data[13]*this.data[7]*this.data[10],
            -this.data[1]*this.data[10]*this.data[15] +
                this.data[1]*this.data[11]*this.data[14] +
                this.data[9]*this.data[2]*this.data[15] -
                this.data[9]*this.data[3]*this.data[14] -
                this.data[13]*this.data[2]*this.data[11] +
                this.data[13]*this.data[3]*this.data[10],
            this.data[1]*this.data[6]*this.data[15] -
                this.data[1]*this.data[7]*this.data[14] -
                this.data[5]*this.data[2]*this.data[15] +
                this.data[5]*this.data[3]*this.data[14] +
                this.data[13]*this.data[2]*this.data[7] -
                this.data[13]*this.data[3]*this.data[6],
            -this.data[1]*this.data[6]*this.data[11] +
                this.data[1]*this.data[7]*this.data[10] +
                this.data[5]*this.data[2]*this.data[11] -
                this.data[5]*this.data[3]*this.data[10] -
                this.data[9]*this.data[2]*this.data[7] +
                this.data[9]*this.data[3]*this.data[6],
            // col 1
            -this.data[4]*this.data[10]*this.data[15] +
                this.data[4]*this.data[11]*this.data[14] +
                this.data[8]*this.data[6]*this.data[15] -
                this.data[8]*this.data[7]*this.data[14] -
                this.data[12]*this.data[6]*this.data[11] +
                this.data[12]*this.data[7]*this.data[10],
            this.data[0]*this.data[10]*this.data[15] -
                this.data[0]*this.data[11]*this.data[14] -
                this.data[8]*this.data[2]*this.data[15] +
                this.data[8]*this.data[3]*this.data[14] +
                this.data[12]*this.data[2]*this.data[11] -
                this.data[12]*this.data[3]*this.data[10],
            -this.data[0]*this.data[6]*this.data[15] +
                this.data[0]*this.data[7]*this.data[14] +
                this.data[4]*this.data[2]*this.data[15] -
                this.data[4]*this.data[3]*this.data[14] -
                this.data[12]*this.data[2]*this.data[7] +
                this.data[12]*this.data[3]*this.data[6],
            this.data[0]*this.data[6]*this.data[11] -
                this.data[0]*this.data[7]*this.data[10] -
                this.data[4]*this.data[2]*this.data[11] +
                this.data[4]*this.data[3]*this.data[10] +
                this.data[8]*this.data[2]*this.data[7] -
                this.data[8]*this.data[3]*this.data[6],
            // col 2
            this.data[4]*this.data[9]*this.data[15] -
                this.data[4]*this.data[11]*this.data[13] -
                this.data[8]*this.data[5]*this.data[15] +
                this.data[8]*this.data[7]*this.data[13] +
                this.data[12]*this.data[5]*this.data[11] -
                this.data[12]*this.data[7]*this.data[9],
            -this.data[0]*this.data[9]*this.data[15] +
                this.data[0]*this.data[11]*this.data[13] +
                this.data[8]*this.data[1]*this.data[15] -
                this.data[8]*this.data[3]*this.data[13] -
                this.data[12]*this.data[1]*this.data[11] +
                this.data[12]*this.data[3]*this.data[9],
            this.data[0]*this.data[5]*this.data[15] -
                this.data[0]*this.data[7]*this.data[13] -
                this.data[4]*this.data[1]*this.data[15] +
                this.data[4]*this.data[3]*this.data[13] +
                this.data[12]*this.data[1]*this.data[7] -
                this.data[12]*this.data[3]*this.data[5],
            -this.data[0]*this.data[5]*this.data[11] +
                this.data[0]*this.data[7]*this.data[9] +
                this.data[4]*this.data[1]*this.data[11] -
                this.data[4]*this.data[3]*this.data[9] -
                this.data[8]*this.data[1]*this.data[7] +
                this.data[8]*this.data[3]*this.data[5],
            // col 3
            -this.data[4]*this.data[9]*this.data[14] +
                this.data[4]*this.data[10]*this.data[13] +
                this.data[8]*this.data[5]*this.data[14] -
                this.data[8]*this.data[6]*this.data[13] -
                this.data[12]*this.data[5]*this.data[10] +
                this.data[12]*this.data[6]*this.data[9],
            this.data[0]*this.data[9]*this.data[14] -
                this.data[0]*this.data[10]*this.data[13] -
                this.data[8]*this.data[1]*this.data[14] +
                this.data[8]*this.data[2]*this.data[13] +
                this.data[12]*this.data[1]*this.data[10] -
                this.data[12]*this.data[2]*this.data[9],
            -this.data[0]*this.data[5]*this.data[14] +
                this.data[0]*this.data[6]*this.data[13] +
                this.data[4]*this.data[1]*this.data[14] -
                this.data[4]*this.data[2]*this.data[13] -
                this.data[12]*this.data[1]*this.data[6] +
                this.data[12]*this.data[2]*this.data[5],
            this.data[0]*this.data[5]*this.data[10] -
                this.data[0]*this.data[6]*this.data[9] -
                this.data[4]*this.data[1]*this.data[10] +
                this.data[4]*this.data[2]*this.data[9] +
                this.data[8]*this.data[1]*this.data[6] -
                this.data[8]*this.data[2]*this.data[5]
        ]);
        // calculate determinant
        var det = this.data[0]*inv.data[0] +
            this.data[1]*inv.data[4] +
            this.data[2]*inv.data[8] +
            this.data[3]*inv.data[12];
        return inv.multScalar( 1 / det );
    };

    /**
     * Returns the rotation matrix from the affine-transformation.
     * @memberof Mat44
     *
     * @returns {Mat44} The rotation matrix.
     */
    Mat44.prototype.rotation = function() {
        var x = new Vec3( this.data[0], this.data[1], this.data[2] ).normalize();
        var y = new Vec3( this.data[4], this.data[5], this.data[6] ).normalize();
        var z = new Vec3( this.data[8], this.data[9], this.data[10] ).normalize();
        return new Mat44([
            x.x, x.y, x.z, 0,
            y.x, y.y, y.z, 0,
            z.x, z.y, z.z, 0,
            0, 0, 0, 1
        ]);
    };

    /**
     * Returns the translation matrix from the affine-transformation.
     * @memberof Mat44
     *
     * @returns {Mat44} The translation matrix.
     */
    Mat44.prototype.translation = function() {
        return Mat44.translation([ this.data[12], this.data[13], this.data[14] ]);
    };

    /**
     * Returns the scale matrix from the affine-transformation.
     * @memberof Mat44
     *
     * @returns {Mat44} The scale matrix.
     */
    Mat44.prototype.scale = function() {
        var x = new Vec3( this.data[0], this.data[1], this.data[2] );
        var y = new Vec3( this.data[4], this.data[5], this.data[6] );
        var z = new Vec3( this.data[8], this.data[9], this.data[10] );
        return Mat44.scale([ x.length(), y.length(), z.length() ]);
    };

    /**
     * Returns the inverse of the transform's rotation matrix.
     * @memberof Mat44
     *
     * @returns {Mat44} The inverse rotation matrix.
     */
    Mat44.prototype.inverseRotation = function() {
        var x = new Vec3( this.data[0], this.data[1], this.data[2] ).normalize();
        var y = new Vec3( this.data[4], this.data[5], this.data[6] ).normalize();
        var z = new Vec3( this.data[8], this.data[9], this.data[10] ).normalize();
        return new Mat44([
            x.x, y.x, z.x, 0,
            x.y, y.y, z.y, 0,
            x.z, y.z, z.z, 0,
            0, 0, 0, 1
        ]);
    };

    /**
     * Returns the inverse of the transform's translation matrix.
     * @memberof Mat44
     *
     * @returns {Mat44} The inverse translation matrix.
     */
    Mat44.prototype.inverseTranslation = function() {
        return Mat44.translation([ -this.data[12], -this.data[13], -this.data[14] ]);
    };

    /**
     * Returns the inverse of the transform's scale matrix.
     * @memberof Mat44
     *
     * @returns {Mat44} The inverse scale matrix.
     */
    Mat44.prototype.inverseScale = function() {
        var x = new Vec3( this.data[0], this.data[1], this.data[2] );
        var y = new Vec3( this.data[4], this.data[5], this.data[6] );
        var z = new Vec3( this.data[8], this.data[9], this.data[10] );
        var scale = new Vec3( x.length(), y.length(), z.length() );
        return Mat44.scale([
            1 / scale.x,
            1 / scale.y,
            1 / scale.z
        ]);
    };

    /**
     * Returns a random transform matrix composed of a rotation and scale.
     * @memberof Mat44
     *
     * @returns {Mat44} A random transform matrix.
     */
    Mat44.random = function() {
        var r = Mat44.rotation( Math.random() * 360, Vec3.random() );
        var s = Mat44.scale( Math.random() * 10 );
        var t = Mat44.translation( Vec3.random() );
        return t.multMat44( r.multMat44( s ) );
    };

    /**
     * Returns a string representation of the matrix.
     * @memberof Mat44
     *
     * @returns {String} The string representation of the matrix.
     */
    Mat44.prototype.toString = function() {
        return this.data[0] +', '+ this.data[4] +', '+ this.data[8] +', '+ this.data[12] +',\n' +
            this.data[1] +', '+ this.data[5] +', '+ this.data[9] +', '+ this.data[13] +',\n' +
            this.data[2] +', '+ this.data[6] +', '+ this.data[10] +', '+ this.data[14] +',\n' +
            this.data[3] +', '+ this.data[7] +', '+ this.data[11] +', '+ this.data[15];
    };

    /**
     * Returns an array representation of the matrix.
     * @memberof Mat44
     *
     * @returns {Array} The matrix as an array.
     */
    Mat44.prototype.toArray = function() {
        return [
            this.data[0],
            this.data[1],
            this.data[2],
            this.data[3],
            this.data[4],
            this.data[5],
            this.data[6],
            this.data[7],
            this.data[8],
            this.data[9],
            this.data[10],
            this.data[11],
            this.data[12],
            this.data[13],
            this.data[14],
            this.data[15]
        ];
    };

    /**
     * Returns an the matrix representation as a 3x3 Mat33 object.
     * @memberof Mat44
     *
     * @returns {Mat33} The matrix as an array.
     */
    Mat44.prototype.toMat33 = function() {
        return new Mat33([
            this.data[0],
            this.data[1],
            this.data[2],
            this.data[4],
            this.data[5],
            this.data[6],
            this.data[8],
            this.data[9],
            this.data[10]
        ]);
    };

    /**
     * Returns an array representation of the matrix.
     * @memberof Mat33
     *
     * @returns {Array} The matrix as an array.
     */
    Mat33.prototype.toMat44 = function() {
        return new Mat44([
            this.data[0],
            this.data[1],
            this.data[2],
            0.0,
            this.data[3],
            this.data[4],
            this.data[5],
            0.0,
            this.data[6],
            this.data[7],
            this.data[8],
            0.0,
            0.0,
            0.0,
            0.0,
            1.0
        ]);
    };

    module.exports = Mat44;

}());


/**
 * @fileOverview
 * A simple UDP multicast discovery. The Discovery object is acquired with
 * getDiscovery. Announcements are sent out at intervals that may be unique to
 * each service. If an announcement is not seen in 2x the advertised
 * announcement interval, the service is marked as unavailable.
 *
 * The Discovery object emits a single event, 'available' that is sent when the
 * service is first seen, an available service times out with no announcements
 * or the service changes its availability status.
 */
'use strict';
var dgram = require('dgram');
var util = require('util');
var inspect = util.inspect;
var events = require('events');
var debug = require('debug')('udp-discovery');
var is = require('is2');
var objToJson = require('obj-to-json');

// Constants
var MULTICAST_ADDRESS = '224.0.0.234';
var DEFAULT_UDP_PORT = 44201;
var DEFAULT_TIMEOUT = 1000;
var DEFAULT_INTERVAL = 3000;
var DEFAULT_DGRAM_TYPE = 'udp4'; // could also be 'udp6'
var GLOBAL_EVENT_NAME = 'MessageBus';
var DEFAULT_REUSE_ADDR = true;

// We use events and must inherit from events.EventEmitter
util.inherits(Discovery, events.EventEmitter);

// export the object for users of the module.
exports.Discovery = Discovery;

/**
 * Creates a Discovery object. The options object is optional. Supported
 * options are:
 *   - port - Set the port the service listens on for announcements default:
 *     44201
 *   - bindAddr - bind to an address
 *   - dgramType - Either 'udp4' or 'udp6', default: 'udp4'.
 *   - timeOutInt - duration of time between timeout checks in ms. Default 1000.
 * @param {Object} [options] An optional configuration object.
 * @constructor
 */
function Discovery(options) {
    var self = this;

    if (options && !is.obj(options))
        debug('Dicovery constructor bad options argument: '+inspect(options));

    // Create a dgram socket and bind it
    self.dgramType = (options && options.dgramType) ?
                      options.dgramType.toLowerCase() : DEFAULT_DGRAM_TYPE;
    self.reuseaddr = (options && options.reuseaddr) ? options.reuseaddr : DEFAULT_REUSE_ADDR;
    self.socket = dgram.createSocket({type: self.dgramType, reuseAddr: self.reuseaddr});
    self.port = (options && options.port) ? options.port : DEFAULT_UDP_PORT;
    self.bindAddr = (options && options.bindAddr) ? options.bindAddr :
                     undefined;
    self.socket.bind(self.port, self.bindAddr);

    // create an interval task to check for announcements that have timed out
    self.timeOutInt = (options && options.timeOutInt) ? options.timeOutInt :
                                        DEFAULT_TIMEOUT;
    self.timeOutId = setInterval(function() { self.handleTimeOut(); },
                                                             self.timeOutInt);

    // listen and listen for multicast packets
    self.socket.on('listening', function() {
        self.socket.addMembership(MULTICAST_ADDRESS);
    });

    // handle any announcements, here we just do the formatting
    self.socket.on('message', function(message, rinfo) {
        if (message) {
            var obj = objToJson.jsonParse(message.toString());
            if (!obj) {
                debug('bad announcement: '+message.toString());
                return;
            }

            // the received message was either an event or an announcement
            if (obj.eventName)
                self.emit(GLOBAL_EVENT_NAME, obj.eventName, obj.data);
            else
                self.handleAnnouncement(obj, rinfo);
        }
    });
}

/**
 * Sets up announcements for a service.
 * @param {String} name The name of the service to announce. Required.
 * @param {Object} userData Any data the user desires, must be serializable to
 *      JSON.
 * @param {Number} [interval] The duration between announcements. If not
 *      specified, the default is 3000 ms.
 * @param {Boolean} [available] OPtional parameter setting the state of the
 *      service. If not included, the default is true meaning available.
 * @return {Boolean} true, if successful false otherwise.
 */
Discovery.prototype.announce = function(name, userData, interval, available) {
    if (!is.nonEmptyStr(name)) {
        debug('accounce error: bad name: '+inspect(name));
        return false;
    }

    if (!userData) {
        debug('announce error: no userData: what is being announced?');
        return false;
    }

    if (!is.positiveInt(interval))    interval = DEFAULT_INTERVAL;
    if (!available)    available = true;

    // make a copy of the userData object
    var userDataCopy = objToJson.copyObj(userData);
    if (!userDataCopy)    return false;
    debug('userDataCopy:'+inspect(userDataCopy));

    // attempt to add the announcement return result to user
    var announce = true;
    return this.addNew(name, userDataCopy, interval, available, announce);
};

/**
 * Pause announcements for a service.
 * @param {String} name The name of the service to resume announcements.
 * @return {Boolean} true, if successful false otherwise.
 */
Discovery.prototype.pause = function(name) {
    // we have to have a name that is string and not empty
    if (!is.nonEmptyStr(name)) {
        debug('stopAnouncement: bad name param: '+inspect(name));
        return false;
    }

    if (!is.nonEmptyObj(this.services)) {
        debug('stopAnnounce: There are no services to stop');
        return false;
    }

    // the service has to be already known to stop announcing
    if (!this.services[name]) {
        debug('Discovery.stopAnnounce error: no entry for \''+name+'\'');
        return false;
    }

    // if there is no task to do the announcing, quit
    if (!this.services[name].intervalId) {
        debug('Discovery.stopAnnounce error: not announcing \''+name+'\'');
        return false;
    }

    // clear the interval and remove the intervalId property
    clearInterval(this.services[name].intervalId);
    delete this.services[name].intervalId;
    return true;
};

/**
 * Resumes announcements for a service.
 * @param {String} name The name of the service to resume announcements.
 * @param {Number} [interval] The duration in ms between announcements.
 *      Optional.
 * @return {Boolean} true, if successful false otherwise.
 */
Discovery.prototype.resume = function(name, interval) {
    var self = this;
    // we need a name that is a string which is not empty
    if (!is.nonEmptyStr(name)) {
        debug('Discovery.resumeAnnounce error: invalid name: '+inspect(name));
        return false;
    }

    if (!is.positiveint(interval))  interval = DEFAULT_INTERVAL;

    // the service has to be known to resume
    if (!self.services || !self.services[name]) {
        debug('resumeAnnounce error: no entry for \''+name+'\'');
        return false;
    }

    // there can't be an interval task doing announcing to resume
    if (self.services[name].intervalId) {
        debug('resumeAnnounce error: already announcing \''+name+'\'');
        return false;
    }

    if (interval)  self.services[name].annInterval = interval;

    // create a function to send the announcement using the closure to retain
    // the name and self
    var sendAnnouncement = function() {
        self.sendAnnounce(self.services[name]);
    };

    // create an interval task and store the id on the service entry
    self.services[name].intervalId = setInterval(sendAnnouncement,
      self.services[name].annInterval);

    return true;
};

/**
 * Allows for updating of service data.
 * @param {String} name The name of the service to update. Required.
 * @param {Object} userData Any data the user desires, must be serializable to
 *      JSON. Required.
 * @param {Number} [interval] The duration between announcements. Default 3000
 *      ms.
 * @param {Boolean} [available] OPtional parameter setting the state of the
 *      service. If not included, the default is true meaning available.
 * @return {Boolean} true, if successful false otherwise.
 */
Discovery.prototype.update = function(name, userData, interval, available) {
    if (!is.nonEmptyStr(name)) {
        debug('update error: missing name: '+inspect(name));
        return false;
    }

    if (!userData) {
        debug('update error: no userData: what is being announced?');
        return false;
    }

    if (!is.positiveInt(interval))    interval = DEFAULT_INTERVAL;
    if (!available)    available = true;

    // make a copy of the userData object
    var userDataCopy = objToJson.copyObj(userData);
    if (!userDataCopy)    return false;

    // attempt to add the announcement return result to user
    return this.updateExisting(name, userDataCopy, interval, available);
};

/**
 * Adds new announcements to the services object. Takes care of adding missing
 * values that have defaults, making the name property constant, and emitting
 * the correct events.
 * @param {String} name The name of the service to announce. Required.
 * @param {Object} userData Any data the user desires, must be serializable to
 *      JSON. Required.
 * @param {Number} [interval] The duration between announcements. Default 3000
 *      ms, if not specified.
 * @param {Boolean} [available] OPtional parameter setting the state of the
 *      service. If not included, the default is true meaning available.
 * @param {Boolean} [announce] Optional parameter do we send the net
 *      announcement. Default is treu.
 * @return {Boolean} true, if successful false otherwise.
 */
Discovery.prototype.addNew = function(name, userData, interval, available,
                                      announce, rinfo) {
    var self = this;
    debug('addNew');
    if (!is.nonEmptyStr(name)) {
        debug('addNew error: missing name: '+inspect(name));
        return false;
    }

    if (!userData) {
        debug('addNew error: no userData: what is being announced?');
        return false;
    }

    // add defaults, if needed
    if (!is.positiveNum(interval))    interval = DEFAULT_INTERVAL;
    if (!available)    available = true;

    // create the services storage if need be
    if (!self.services)    self.services = {};

    // The entry should not already exist
    if (self.services[name]) {
        debug('addNew for \''+name+'\', but it already exists.');
        return false;
    }

    self.services[name] = {};
    self.services[name].name = name;
    self.services[name].interval = interval;
    self.services[name].data = userData;
    self.services[name].available = available;
    //self.services[name].announce = announce;
    // if local is true, the service is local to this process.
    self.services[name].local = announce;

    // if there is an rinfo, copy it and place it on the service
    // we don't need the size parameter, though.
    if (is.obj(rinfo) && is.nonEmptyStr(rinfo.address))
        self.services[name].addr = rinfo.address;

    // set the name property to be read-only - it would be confusing if it
    // changed as it is also the key.
    Object.defineProperty(self.services[name], 'name', {
        value: name,
        writable: false,
        enumerable: true,
        configurable: true
    });

    // since it's new - send an event
    var evName = available ? 'available' : 'unavailable';
    self.emit(evName, name, self.services[name], 'new');

    // update the lanst announcement time to now
    self.services[name].lastAnnTm = Date.now();

    // create a function to send the announcement using the closure to retain
    // the name and self
    var sendAnnouncement = function() {
        self.sendAnnounce(self.services[name]);
    };

    // create an interval task to repeatedly send announcements
    if (announce) {
        self.services[name].intervalId = setInterval(sendAnnouncement,
                                                     interval);
    }

    return true;
};

/**
 * update an existing service entry. Only works on services created locally.
 * @param {String} name The name of the service to announce. Required.
 * @param {Object} userData Any data the user desires, must be serializable to
 *      JSON. Required.
 * @param {Number} interval The duration between announcements. Default 3000 ms.
 * @param {Boolean} [available] OPtional parameter setting the state of the
 *      service. If not included, the default is true meaning available.
 * @param {Object} [rinfo] Optional parameter for remote address
 * @return {Boolean} true if successful, false otherwise.
 */
Discovery.prototype.updateExisting = function(name, data, interval, available,
                                              rinfo) {
    // this is an existing entry
    var oldAvail = this.services[name].available;
    // update the lanst announcement time to now
    this.services[name].interval = interval;
    this.services[name].data = data;

    // if there is an rinfo, copy it and place it on the service
    // we don't need the size parameter, though.
    if (is.obj(rinfo) && is.str(rinfo.address) && !this.services[name].addr)
        this.services[name].addr = rinfo.address;

    // if the availability changed, send an event
    if (available !== oldAvail) {
        this.services[name].available = available;
        var evName = available ? 'available' : 'unavailable';
        this.emit(evName, name, this.services[name], 'availabilityChange');
    }

    return true;
};

/**
 * Receives and processes announcements for a service.
 * @param {Object} ann The object describing the service.
 * @param {Object} [rinfo] An object with the sender's address information.
 * @return {Boolean} true, if successful false otherwise.
 * @private
 */
Discovery.prototype.handleAnnouncement = function(ann, rinfo) {
    // ensure the ann is an object that is not empty
    if (!is.nonEmptyObj(ann)) {
        debug('handleAnnouncement bad ann: '+inspect(ann));
        return false;
    }

    // also, the ann obj needs a name
    if (!ann.name) {
        debug('handleAnnouncement error: no name.');
        return false;
    }

    // The entry exists, update it
    if (this.services && this.services[ann.name]) {
        this.services[ann.name].lastAnnTm = Date.now();
        return this.updateExisting(ann.name, ann.data, ann.interval,
                                   ann.available,
                                  rinfo);
    }

    // the entry is new, add it
    var announce = false;
    return this.addNew(ann.name, ann.data, ann.interval, ann.available,
                       announce, rinfo);
};

/**
 * Retrieves service information by name.
 * @param {String} name The name of the service for which you want data.
 * @return {Object|Boolean} The object describing the srevice if available, and
 *     false otherwise.
 */
Discovery.prototype.getData = function(name) {
    // handle conditions for which there is no answer
    if (!name || typeof name !== 'string' || !name.length)    return false;
    if (!this.services || !this.services[name])    return false;
    // Developers just want annoucement data, send that.
    return this.services[name].data;
};

/**
 * Setup to send announcements for a service over UDP multicast.
 * @param {Object} data The service to announce.
 * @return {Boolean} true, if successful false otherwise.
 * @private
 */
Discovery.prototype.sendAnnounce = function(data) {
    if (!is.nonEmptyObj(data)) {
        debug('sendAnnounce has a bad param for data: '+inspect(data));
        return false;
    }

    var copy = objToJson.copyObj(data);
    delete copy.lastAnnTm;
    delete copy.intervalId;

    var str = objToJson.jsonStringify(copy);
    if (!str) {
        debug('objToJson.jsonStringify failed on serice data: '+inspect(data));
        return;
    }

    // send the stringified buffer over multicast
    var buf = new Buffer(str);
    this.socket.send(buf, 0, buf.length, this.port, MULTICAST_ADDRESS);
};

/**
 * Handle timeouts on announcements. Deletes timed out entries from services.
 * @param {Object} discObj A Discovery object.
 * @private
 */
Discovery.prototype.handleTimeOut = function() {
    var self = this;

    // Also the object should have a services storage on it
    if (!self.services || !Object.keys(self.services).length) {
        //debug('handleTimeOut no services, exiting.');
        return;
    }

    var now = Date.now();                         // timestamp in ms
    var services = self.services;    // quick ref for storage

    // iterate over all the properties in hash object
    for (var name in services) {
        // every object should have a timestamp - what's up here?
        if (!services[name].lastAnnTm) {
            debug('handleTimeOut: service \''+name+'\' has no time stamp. '+
                  'Adding one.');
            continue;
        }

        // if the time since the last announce is greater than 2X the
        // announcement interval, we timed out.
        if ((now - services[name].lastAnnTm) > (2*services[name].interval)) {
            var data = services[name].data;
            data.available = false;
            delete services[name];
            self.emit('unavailable', name, self.services[name], 'timedOut');
        }
    }
};

/**
 * Send an event to all discovered services.
 * @param {String} eventName The name of the event.
 * @param {Object} [data] User data sent along with the event. Optional.
 * @return {Boolean} true, if successful false otherwise.
 */
Discovery.prototype.sendEvent = function(eventName, data) {
    if (!is.nonEmptyStr(eventName)) {
        debug('sendEvent has a bad param for eventName: '+inspect(eventName));
        return false;
    }

    return this.sendEventToAddress(MULTICAST_ADDRESS, eventName, data);
};

/**
 * Send an event to a service, an array of services, or services matching a
 * query.
 * @param {String|Array|FUnction} dest The service name, an array of services
 *      or a query to select serices.
 * @param {String} eventName The name of the event.
 * @param {Object} [data] User data sent along with the event. Optional.
 * @return {Boolean} true on success, false otherwise.
 */
Discovery.prototype.sendEventTo = function(dest, eventName, data) {
    if (!is.nonEmptyStr(dest) && !is.nonEmptyArray(dest) &&
        !is.function(dest)) {
        debug('Discovery.sendEventTo received bad dest parameter: '+
              inspect(dest));
        return false;
    }

    if (!is.nonEmptyStr(eventName)) {
        debug('Discovery.sendEventTo received bad name parameter: '+
              inspect(eventName));
        return false;
    }

    var i;
    // handle the case where dest is a service name
    if (is.nonEmptyStr(dest)) {
        this.sendEventToService(dest, eventName, data);
    } else if (is.nonEmptyArray(dest)) {
        for (i=0; i<dest.length; i++)
            this.sendEventToService(dest[i], eventName, data);
    } else if (is.function(dest)) {
        var queryFunc = dest;
        var matches = [];
        for (i=0; i<this.services; i++) {
            if (queryFunc(this.services[i]) === true)
                matches.push(this.services[i]);
        }
        for (i=0; i<matches.length; i++) {
            this.sendEventToService(matches[i].name, eventName, data);
        }
    }
    return true;
};

/**
 * Send event to either the local process or remote process.
 * @param {String} name The name of the service to receive the message.
 * @param {Object} [data] Optional user data to send with message.
 * @return {Boolean} true on success, false otherwise.
 * @private
 */
Discovery.prototype.sendEventToService = function(name, eventName, data) {
    if (!is.nonEmptyStr(name)) {
        debug('Discovery.sendEvent received bad name parameter: '+
              inspect(name));
        return false;
    }

    if (!this.services[name])    {
        debug('Discovery.sendEvent no such service name as: '+inspect(name));
        return false;
    }

    if (!is.nonEmptyStr(eventName)) {
        debug('Discovery.sendEvent invalid event name: '+inspect(eventName));
        return false;
    }

    if (this.services[name].local)
        this.emit(GLOBAL_EVENT_NAME, eventName, data);
    else
        this.sendEventToAddr(this.services[name].addr, eventName, data);

    return true;
};

/**
 * Send event to either the local process or remote process.
 * @param {String} addr The address of the service to receive the message.
 * @param {String} eventName The event name to send.
 * @param {Object} [data] Optional user data to send with message.
 * @return {Boolean} true on success, false otherwise.
 * @private
 */
Discovery.prototype.sendEventToAddress = function(addr, eventName, data) {
    if (!is.nonEmptyStr(eventName)) {
        debug('Discovery.sendEventToAddress invalid event name: '+
                    inspect(eventName));
        return false;
    }

    if (!is.nonEmptyStr(addr)) {
        debug('Discovery.sendEventToAddress invalid addr: '+inspect(addr));
        return false;
    }

    var obj = {
        eventName: eventName,
        data: data
    };

    // convert data to JSON string
    var str = objToJson.jsonStringify(obj);
    if (!str) {
        debug('Discovery.sendEvent could not stringify data param: '+
              inspect(data));
        return false;
    }
    var buf = new Buffer(str);
    this.socket.send(buf, 0, buf.length, this.port, addr);

    return true;
};


/**
 * @file edp-provider-rider
 * @author junmer[junmer@foxmail.com],
 *         Firede[firede@firede.us]
 */

var stylus = require('stylus');
var rider = require('rider');
var postcss = require('postcss');
var mqpacker = require('css-mqpacker');
var ap = require('autoprefixer-core');
var husl = require('husl');

/**
 * 后处理器
 *
 * @param  {Object} options 选项
 * @param  {Array|boolean=} options.autoprefixer Autoprefixer支持
 * @param  {boolean=} options.mqpacker mqpacker启用状态
 * @param  {Function} callback callback
 * @return {Function} autoprefixer function
 */
function postprocessor(options, callback) {

    var defaultBrowsers = ['Android >= 2.3', 'iOS >= 6', 'ExplorerMobile >= 10'];

    return function (err, css) {
        if (err) {
            callback(err);
        }

        var processors = [];

        if (options.autoprefixer !== false) {
            processors.push(
                ap({browsers: options.autoprefixer || defaultBrowsers})
            );
        }

        if (options.mqpacker !== false) {
            processors.push(mqpacker);
        }

        if (processors.length > 0) {
            return postcss(processors).process(css).css;
        }

        return css;
    };
}

/**
 * 编译
 *
 * @param  {Object} options options
 * @param  {boolean=} options.implicit 引入rider
 * @param  {Array|boolean=} options.autoprefixer autoprefixer支持
 * @param  {boolean=} options.husl husl支持
 * @param  {boolean=} options.resolveUrl resolve url
 * @param  {Function=} options.use use
 * @param  {Function=} callback provider callback
 */
function plugin(options, callback) {
    options = options || {};

    return function (style) {

        if (options.resolveUrl !== false) {
            style.define('url', stylus.resolver());
        }

        style.use(
            rider({
                implicit: options.implicit
            })
        );

        style.on('end', postprocessor(options, callback));

        if (options.husl) {
            // define husl & huslp
            style.define('husl', function (H, S, L, A) {
                var rgb = husl._rgbPrepare(husl._conv.husl.rgb([H.val, S.val, L.val]));
                var a = (A !== undefined ? A.val : 1);
                return new stylus.nodes.RGBA(rgb[0], rgb[1], rgb[2], a);
            });
            style.define('huslp', function (H, S, L, A) {
                var rgb = husl._rgbPrepare(husl._conv.huslp.rgb([H.val, S.val, L.val]));
                var a = (A !== undefined ? A.val : 1);
                return new stylus.nodes.RGBA(rgb[0], rgb[1], rgb[2], a);
            });
        }

        if (options.use) {
            style.use(options.use);
        }
    };
}

module.exports = exports = {
    stylus: stylus,
    rider: rider,
    autoprefixer: ap,
    husl: husl,
    plugin: plugin,
    postcss: postcss,
    mqpacker: mqpacker
};


(function() {

    'use strict';

    var EPSILON = require('./Epsilon');

    /**
     * Instantiates a Vec2 object.
     * @class Vec2
     * @classdesc A two component vector.
     */
    function Vec2() {
        switch ( arguments.length ) {
            case 1:
                // array or VecN argument
                var argument = arguments[0];
                this.x = argument.x || argument[0] || 0.0;
                this.y = argument.y || argument[1] || 0.0;
                break;
            case 2:
                // individual component arguments
                this.x = arguments[0];
                this.y = arguments[1];
                break;
            default:
                this.x = 0;
                this.y = 0;
                break;
        }
    }

    /**
     * Returns a new Vec2 with each component negated.
     * @memberof Vec2
     *
     * @returns {Vec2} The negated vector.
     */
    Vec2.prototype.negate = function() {
        return new Vec2( -this.x, -this.y );
    };

    /**
     * Adds the vector with the provided vector argument, returning a new Vec2
     * object representing the sum.
     * @memberof Vec2
     *
     * @param {Vec2|Vec3|Vec4|Array} that - The vector to add.
     *
     * @returns {Vec2} The sum of the two vectors.
     */
    Vec2.prototype.add = function( that ) {
        if ( that instanceof Array ) {
            return new Vec2( this.x + that[0], this.y + that[1] );
        }
        return new Vec2( this.x + that.x, this.y + that.y );
    };

    /**
     * Subtracts the provided vector argument from the vector, returning a new Vec2
     * object representing the difference.
     * @memberof Vec2
     *
     * @param {Vec2|Vec3|Vec4|Array} - The vector to subtract.
     *
     * @returns {Vec2} The difference of the two vectors.
     */
    Vec2.prototype.sub = function( that ) {
        if ( that instanceof Array ) {
            return new Vec2( this.x - that[0], this.y - that[1] );
        }
        return new Vec2( this.x - that.x, this.y - that.y );
    };

    /**
     * Multiplies the vector with the provided scalar argument, returning a new Vec2
     * object representing the scaled vector.
     * @memberof Vec2
     *
     * @param {number} - The scalar to multiply the vector by.
     *
     * @returns {Vec2} The scaled vector.
     */
    Vec2.prototype.multScalar = function( that ) {
        return new Vec2( this.x * that, this.y * that );
    };

    /**
     * Divides the vector with the provided scalar argument, returning a new Vec2
     * object representing the scaled vector.
     * @memberof Vec2
     *
     * @param {number} - The scalar to divide the vector by.
     *
     * @returns {Vec2} The scaled vector.
     */
    Vec2.prototype.divScalar = function( that ) {
        return new Vec2( this.x / that, this.y / that );
    };

    /**
     * Calculates and returns the dot product of the vector and the provided
     * vector argument.
     * @memberof Vec2
     *
     * @param {Vec2|Vec3|Vec4|Array} - The other vector argument.
     *
     * @returns {number} The dot product.
     */
    Vec2.prototype.dot = function( that ) {
        if ( that instanceof Array ) {
            return ( this.x * that[0] ) + ( this.y * that[1] );
        }
        return ( this.x * that.x ) + ( this.y * that.y );
    };

    /**
     * Calculates and returns 2D cross product of the vector and the provided
     * vector argument. This value represents the magnitude of the vector that
     * would result from a regular 3D cross product of the input vectors,
     * taking their Z values as 0.
     * @memberof Vec2
     *
     * @param {Vec2|Vec3|Vec4|Array} - The other vector argument.
     *
     * @returns {number} The 2D cross product.
     */
    Vec2.prototype.cross = function( that ) {
        if ( that instanceof Array ) {
            return ( this.x * that[1] ) - ( this.y * that[0] );
        }
        return ( this.x * that.y ) - ( this.y * that.x );
    };

    /**
     * If no argument is provided, this function returns the scalar length of
     * the vector. If an argument is provided, this method will return a new
     * Vec2 scaled to the provided length.
     * @memberof Vec2
     *
     * @param {number} - The length to scale the vector to. Optional.
     *
     * @returns {number|Vec2} Either the length, or new scaled vector.
     */
    Vec2.prototype.length = function( length ) {
        if ( length === undefined ) {
            var len = this.dot( this );
            if ( Math.abs( len - 1.0 ) < EPSILON ) {
                return len;
            } else {
                return Math.sqrt( len );
            }
        }
        return this.normalize().multScalar( length );
    };

    /**
     * Returns the squared length of the vector.
     * @memberof Vec2
     *
     * @returns {number} The squared length of the vector.
     */
    Vec2.prototype.lengthSquared = function() {
        return this.dot( this );
    };

    /**
     * Returns true if the vector components match those of a provided vector.
     * An optional epsilon value may be provided.
     * @memberof Vec2
     *
     * @param {Vec2|Vec3|Vec4|Array} that - The vector to test equality with.
     * @param {number} epsilon - The epsilon value. Optional.
     *
     * @returns {boolean} Whether or not the vector components match.
     */
    Vec2.prototype.equals = function( that, epsilon ) {
        var x = that.x !== undefined ? that.x : that[0],
            y = that.y !== undefined ? that.y : that[1];
        epsilon = epsilon === undefined ? 0 : epsilon;
        return ( this.x === x || Math.abs( this.x - x ) <= epsilon ) &&
            ( this.y === y || Math.abs( this.y - y ) <= epsilon );
    };

    /**
     * Returns a new Vec2 of unit length.
     * @memberof Vec2
     *
     * @returns {Vec2} The vector of unit length.
     */
    Vec2.prototype.normalize = function() {
        var mag = this.length();
        if ( mag === 0 ) {
            throw 'Cannot normalize a vector of zero length';
        }
        return new Vec2(
            this.x / mag,
            this.y / mag );
    };

    /**
     * Returns the unsigned angle between this angle and the argument in radians.
     * @memberof Vec2
     *
     * @param {Vec2|Vec3|Vec4|Array} that - The vector to measure the angle from.
     *
     * @returns {number} The unsigned angle in radians.
     */
    Vec2.prototype.unsignedAngle = function( that ) {
        var x = that.x !== undefined ? that.x : that[0];
        var y = that.y !== undefined ? that.y : that[1];
        var angle = Math.atan2( y, x ) - Math.atan2( this.y, this.x );
        if (angle < 0) {
            angle += 2 * Math.PI;
        }
        return angle;
    };

    /**
     * Returns a random Vec2 of unit length.
     * @memberof Vec2
     *
     * @returns {Vec2} A random vector of unit length.
     */
    Vec2.random = function() {
        return new Vec2(
            Math.random(),
            Math.random() ).normalize();
    };

    /**
     * Returns a string representation of the vector.
     * @memberof Vec2
     *
     * @returns {String} The string representation of the vector.
     */
    Vec2.prototype.toString = function() {
        return this.x + ', ' + this.y;
    };

    /**
     * Returns an array representation of the vector.
     * @memberof Vec2
     *
     * @returns {Array} The vector as an array.
     */
    Vec2.prototype.toArray = function() {
        return [ this.x, this.y ];
    };

    module.exports = Vec2;

}());