Source: math/Matrix4.js

/**
 * jslint browser: true
 */

/**
 * Creates new matrix4 as identity matrix
 * @class
 * @constructor
 */
Ayce.Matrix4 = function () {
    this.data = new Float32Array([
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    ]);
};

/**
 * Creates perspective matrix for viewing frustum
 * @param {Number} fieldOfView
 * @param {Number} aspect
 * @param {Number} near
 * @param {Number} far
 * @return {Ayce.Matrix4} m
 */
Ayce.Matrix4.makePerspective = function (fieldOfView, aspect, near, far) {
    var m = new Ayce.Matrix4();
    var radians = fieldOfView / 2.0 * Math.PI / 180.0;
    var sine = Math.sin(radians);
    var f = Math.cos(radians) / sine;

    var rightHanded = true;
    var handednessScale = rightHanded ? -1.0 : 1.0;

    m.data = new Float32Array([
        f / aspect, 0, 0, 0,
        0, f, 0, 0,
        0, 0, far / (near - far) * -handednessScale, handednessScale,
        0, 0, (far * near) / (near - far), 0
    ]);

    return m;
};
/**
 * Creates orthographic perspective matrix
 * @param {Number} left
 * @param {Number} right
 * @param {Number} top
 * @param {Number} bottom
 * @param {Number} near
 * @param {Number} far
 * @return {Ayce.Matrix4} m
 */
Ayce.Matrix4.makeOrthoPerspective = function (left, right, top, bottom, near, far) {

    var m = new Ayce.Matrix4();

    var lr = 1 / (left - right);
    var bt = 1 / (bottom - top);
    var nf = 1 / (near - far);

    m.data = new Float32Array([
        -2 * lr, 0, 0, 0,
        0, -2 * bt, 0, 0,
        0, 0, 2 * nf, 0,
        (left + right) * lr, (top + bottom) * bt, (far + near) * nf, 1
    ]);


    return m;
};
/**
 * Creates perspective matrix for use with the Oculus Rift
 * @param {Number} VRfov
 * @param {Number} zNear
 * @param {Number} zFar
 * @return {Ayce.Matrix4} m
 */
Ayce.Matrix4.makeVRPerspective = function (VRfov, zNear, zFar) {
    var DEG2RAD = Math.PI / 180.0;

    var upTan = Math.tan(VRfov.upDegrees * DEG2RAD);
    var downTan = Math.tan(VRfov.downDegrees * DEG2RAD);
    var leftTan = Math.tan(VRfov.leftDegrees * DEG2RAD);
    var rightTan = Math.tan(VRfov.rightDegrees * DEG2RAD);

    var pxscale = 2.0 / (leftTan + rightTan);
    var pxoffset = (leftTan - rightTan) * pxscale * 0.5;
    var pyscale = 2.0 / (upTan + downTan);
    var pyoffset = (upTan - downTan) * pyscale * 0.5;

    var rightHanded = true;
    var handednessScale = rightHanded ? -1.0 : 1.0;

    var m = new Ayce.Matrix4();
    m.data = new Float32Array([
        pxscale, 0, 0, 0,
        0, pyscale, 0, 0,
        pxoffset * handednessScale, pyoffset * handednessScale, zFar / (zNear - zFar) * -handednessScale, handednessScale,
        0, 0, (zFar * zNear) / (zNear - zFar), 0
    ]);

    return m;
};

Ayce.Matrix4.prototype = {
    poolMatrix: new Ayce.Matrix4(),
    poolVector3: new Ayce.Vector3(),
    /**
     * Description
     */
    identity: function () {
        this.data[0] = 1;
        this.data[1] = 0;
        this.data[2] = 0;
        this.data[3] = 0;
        this.data[4] = 0;
        this.data[5] = 1;
        this.data[6] = 0;
        this.data[7] = 0;
        this.data[8] = 0;
        this.data[9] = 0;
        this.data[10] = 1;
        this.data[11] = 0;
        this.data[12] = 0;
        this.data[13] = 0;
        this.data[14] = 0;
        this.data[15] = 1;
    },
    /**
     * Applies transformation matrix
     * @param {Ayce.Matrix4} lhs
     */
    apply: function (lhs) {
        var m111 = this.data[0],
            m121 = this.data[4],
            m131 = this.data[8],
            m141 = this.data[12],
            m112 = this.data[1],
            m122 = this.data[5],
            m132 = this.data[9],
            m142 = this.data[13],
            m113 = this.data[2],
            m123 = this.data[6],
            m133 = this.data[10],
            m143 = this.data[14],
            m114 = this.data[3],
            m124 = this.data[7],
            m134 = this.data[11],
            m144 = this.data[15],
            m211 = lhs.data[0],
            m221 = lhs.data[4],
            m231 = lhs.data[8],
            m241 = lhs.data[12],
            m212 = lhs.data[1],
            m222 = lhs.data[5],
            m232 = lhs.data[9],
            m242 = lhs.data[13],
            m213 = lhs.data[2],
            m223 = lhs.data[6],
            m233 = lhs.data[10],
            m243 = lhs.data[14],
            m214 = lhs.data[3],
            m224 = lhs.data[7],
            m234 = lhs.data[11],
            m244 = lhs.data[15];

        this.data[0] = m111 * m211 + m112 * m221 + m113 * m231 + m114 * m241;
        this.data[1] = m111 * m212 + m112 * m222 + m113 * m232 + m114 * m242;
        this.data[2] = m111 * m213 + m112 * m223 + m113 * m233 + m114 * m243;
        this.data[3] = m111 * m214 + m112 * m224 + m113 * m234 + m114 * m244;

        this.data[4] = m121 * m211 + m122 * m221 + m123 * m231 + m124 * m241;
        this.data[5] = m121 * m212 + m122 * m222 + m123 * m232 + m124 * m242;
        this.data[6] = m121 * m213 + m122 * m223 + m123 * m233 + m124 * m243;
        this.data[7] = m121 * m214 + m122 * m224 + m123 * m234 + m124 * m244;

        this.data[8] = m131 * m211 + m132 * m221 + m133 * m231 + m134 * m241;
        this.data[9] = m131 * m212 + m132 * m222 + m133 * m232 + m134 * m242;
        this.data[10] = m131 * m213 + m132 * m223 + m133 * m233 + m134 * m243;
        this.data[11] = m131 * m214 + m132 * m224 + m133 * m234 + m134 * m244;

        this.data[12] = m141 * m211 + m142 * m221 + m143 * m231 + m144 * m241;
        this.data[13] = m141 * m212 + m142 * m222 + m143 * m232 + m144 * m242;
        this.data[14] = m141 * m213 + m142 * m223 + m143 * m233 + m144 * m243;
        this.data[15] = m141 * m214 + m142 * m224 + m143 * m234 + m144 * m244;
    },
    /**
     * Applies rotation to matrix
     * @param {Number} degrees
     * @param {Ayce.Vector3} axis
     * @param {Ayce.Vector3} pivotPoint
     */
    applyRotation: function (degrees, axis, pivotPoint) {
        this.poolMatrix.data[0] = 1;
        this.poolMatrix.data[1] = 0;
        this.poolMatrix.data[2] = 0;
        this.poolMatrix.data[3] = 0;
        this.poolMatrix.data[4] = 0;
        this.poolMatrix.data[5] = 1;
        this.poolMatrix.data[6] = 0;
        this.poolMatrix.data[7] = 0;
        this.poolMatrix.data[8] = 0;
        this.poolMatrix.data[9] = 0;
        this.poolMatrix.data[10] = 1;
        this.poolMatrix.data[11] = 0;
        this.poolMatrix.data[12] = 0;
        this.poolMatrix.data[13] = 0;
        this.poolMatrix.data[14] = 0;
        this.poolMatrix.data[15] = 1;
        this.getAxisRotation(axis.x, axis.y, axis.z, degrees, this.poolMatrix);
        if (pivotPoint !== undefined) {
            var p = pivotPoint;
            this.poolMatrix.applyTranslation(p.x, p.y, p.z);
        }
        this.apply(this.poolMatrix);
    },
    /**
     * Applies scaling to matrix
     * @param {Number} xScale
     * @param {Number} yScale
     * @param {Number} zScale
     */
    applyScale: function (xScale, yScale, zScale) {
        this.poolMatrix.data[0] = xScale;
        this.poolMatrix.data[1] = 0;
        this.poolMatrix.data[2] = 0;
        this.poolMatrix.data[3] = 0;
        this.poolMatrix.data[4] = 0;
        this.poolMatrix.data[5] = yScale;
        this.poolMatrix.data[6] = 0;
        this.poolMatrix.data[7] = 0;
        this.poolMatrix.data[8] = 0;
        this.poolMatrix.data[9] = 0;
        this.poolMatrix.data[10] = zScale;
        this.poolMatrix.data[11] = 0;
        this.poolMatrix.data[12] = 0;
        this.poolMatrix.data[13] = 0;
        this.poolMatrix.data[14] = 0;
        this.poolMatrix.data[15] = 1;
        this.apply(this.poolMatrix);
    },
    /**
     * Applies translation to matrix
     * @param {Number} x
     * @param {Number} y
     * @param {Number} z
     */
    applyTranslation: function (x, y, z) {
        this.data[12] += x;
        this.data[13] += y;
        this.data[14] += z;
    },
    /**
     * TODO: Description
     * @param {Number} x
     * @param {Number} y
     * @param {Number} z
     * @param {Number} degrees
     * @param {Boolean} toMatrix
     * @return {Ayce.Matrix4} m
     */
    getAxisRotation: function (x, y, z, degrees, toMatrix) {
        var m;
        if(!toMatrix){
            m = new Ayce.Matrix4();
        }
        else{
            m = toMatrix;
        }

        var a1 = this.poolVector3;
        a1.x = x;
        a1.y = y;
        a1.z = z;

        var rad = -degrees * (Math.PI / 180);
        var c = Math.cos(rad);
        var s = Math.sin(rad);
        var t = 1.0 - c;

        m.data[0] = c + a1.x * a1.x * t;
        m.data[5] = c + a1.y * a1.y * t;
        m.data[10] = c + a1.z * a1.z * t;

        var tmp1 = a1.x * a1.y * t;
        var tmp2 = a1.z * s;
        m.data[4] = tmp1 + tmp2;
        m.data[1] = tmp1 - tmp2;
        tmp1 = a1.x * a1.z * t;
        tmp2 = a1.y * s;
        m.data[8] = tmp1 - tmp2;
        m.data[2] = tmp1 + tmp2;
        tmp1 = a1.y * a1.z * t;
        tmp2 = a1.x * s;
        m.data[9] = tmp1 + tmp2;
        m.data[6] = tmp1 - tmp2;

        return m;
    },
    /**
     * Returns Determinant of matrix
     * @return {Number} determinant
     */
    getDeterminant: function () {
        return 1 * (
                (this.data[0] * this.data[5] - this.data[4] * this.data[1]) *
                (this.data[10] * this.data[15] - this.data[14] * this.data[11]) -
                (this.data[0] * this.data[9] - this.data[8] * this.data[1]) *
                (this.data[6] * this.data[15] - this.data[14] * this.data[7]) +
                (this.data[0] * this.data[13] - this.data[12] * this.data[1]) *
                (this.data[6] * this.data[11] - this.data[10] * this.data[7]) +
                (this.data[4] * this.data[9] - this.data[8] * this.data[5]) *
                (this.data[2] * this.data[15] - this.data[14] * this.data[3]) -
                (this.data[4] * this.data[13] - this.data[12] * this.data[5]) *
                (this.data[2] * this.data[11] - this.data[10] * this.data[3]) +
                (this.data[8] * this.data[13] - this.data[12] * this.data[9]) *
                (this.data[2] * this.data[7] - this.data[6] * this.data[3])
            );
    },
    /**
     * Transposes the matrix
     */
    transpose: function () {
        this.copyToMatrix(this, this.poolMatrix);

        this.data[1] = this.poolMatrix.data[4];
        this.data[2] = this.poolMatrix.data[8];
        this.data[3] = this.poolMatrix.data[12];
        this.data[4] = this.poolMatrix.data[1];
        this.data[6] = this.poolMatrix.data[9];
        this.data[7] = this.poolMatrix.data[13];
        this.data[8] = this.poolMatrix.data[2];
        this.data[9] = this.poolMatrix.data[6];
        this.data[11] = this.poolMatrix.data[14];
        this.data[12] = this.poolMatrix.data[3];
        this.data[13] = this.poolMatrix.data[7];
        this.data[14] = this.poolMatrix.data[11];
    },
    /**
     * Inverts the matrix
     * @return {Boolean} invertable
     */
    invert: function () {
        var d = this.getDeterminant();
        var invertable = Math.abs(d) > 0.00000000001;

        if (invertable) {
            d = 1 / d;
            var m11 = this.data[0];
            var m21 = this.data[4];
            var m31 = this.data[8];
            var m41 = this.data[12];
            var m12 = this.data[1];
            var m22 = this.data[5];
            var m32 = this.data[9];
            var m42 = this.data[13];
            var m13 = this.data[2];
            var m23 = this.data[6];
            var m33 = this.data[10];
            var m43 = this.data[14];
            var m14 = this.data[3];
            var m24 = this.data[7];
            var m34 = this.data[11];
            var m44 = this.data[15];

            this.data[0] = d * (m22 * (m33 * m44 - m43 * m34) - m32 * (m23 * m44 - m43 * m24) + m42 * (m23 * m34 - m33 * m24));
            this.data[1] = -d * (m12 * (m33 * m44 - m43 * m34) - m32 * (m13 * m44 - m43 * m14) + m42 * (m13 * m34 - m33 * m14));
            this.data[2] = d * (m12 * (m23 * m44 - m43 * m24) - m22 * (m13 * m44 - m43 * m14) + m42 * (m13 * m24 - m23 * m14));
            this.data[3] = -d * (m12 * (m23 * m34 - m33 * m24) - m22 * (m13 * m34 - m33 * m14) + m32 * (m13 * m24 - m23 * m14));
            this.data[4] = -d * (m21 * (m33 * m44 - m43 * m34) - m31 * (m23 * m44 - m43 * m24) + m41 * (m23 * m34 - m33 * m24));
            this.data[5] = d * (m11 * (m33 * m44 - m43 * m34) - m31 * (m13 * m44 - m43 * m14) + m41 * (m13 * m34 - m33 * m14));
            this.data[6] = -d * (m11 * (m23 * m44 - m43 * m24) - m21 * (m13 * m44 - m43 * m14) + m41 * (m13 * m24 - m23 * m14));
            this.data[7] = d * (m11 * (m23 * m34 - m33 * m24) - m21 * (m13 * m34 - m33 * m14) + m31 * (m13 * m24 - m23 * m14));
            this.data[8] = d * (m21 * (m32 * m44 - m42 * m34) - m31 * (m22 * m44 - m42 * m24) + m41 * (m22 * m34 - m32 * m24));
            this.data[9] = -d * (m11 * (m32 * m44 - m42 * m34) - m31 * (m12 * m44 - m42 * m14) + m41 * (m12 * m34 - m32 * m14));
            this.data[10] = d * (m11 * (m22 * m44 - m42 * m24) - m21 * (m12 * m44 - m42 * m14) + m41 * (m12 * m24 - m22 * m14));
            this.data[11] = -d * (m11 * (m22 * m34 - m32 * m24) - m21 * (m12 * m34 - m32 * m14) + m31 * (m12 * m24 - m22 * m14));
            this.data[12] = -d * (m21 * (m32 * m43 - m42 * m33) - m31 * (m22 * m43 - m42 * m23) + m41 * (m22 * m33 - m32 * m23));
            this.data[13] = d * (m11 * (m32 * m43 - m42 * m33) - m31 * (m12 * m43 - m42 * m13) + m41 * (m12 * m33 - m32 * m13));
            this.data[14] = -d * (m11 * (m22 * m43 - m42 * m23) - m21 * (m12 * m43 - m42 * m13) + m41 * (m12 * m23 - m22 * m13));
            this.data[15] = d * (m11 * (m22 * m33 - m32 * m23) - m21 * (m12 * m33 - m32 * m13) + m31 * (m12 * m23 - m22 * m13));
        } else {
            //TODO return false, don't throw
            throw "Can't invert Matrix";
        }
        return invertable;
    },
    /**
     * Applies matrix to vector
     * @param {Ayce.Vector3} vector
     * @return {Ayce.Vector3} vector
     */
    transformVector: function (vector) {
        var x = vector.x;
        var y = vector.y;
        var z = vector.z;

        vector.x = (x * this.data[0] + y * this.data[4] + z * this.data[8] + this.data[12]);
        vector.y = (x * this.data[1] + y * this.data[5] + z * this.data[9] + this.data[13]);
        vector.z = (x * this.data[2] + y * this.data[6] + z * this.data[10] + this.data[14]);

        return vector;
    },
    /**
     * Copies values from one matrix to another
     * @param {Ayce.Matrix4} from
     * @param {Ayce.Matrix4} to
     */
    copyToMatrix: function(from, to){
        to.data[0] = from.data[0];
        to.data[1] = from.data[1];
        to.data[2] = from.data[2];
        to.data[3] = from.data[3];
        to.data[4] = from.data[4];
        to.data[5] = from.data[5];
        to.data[6] = from.data[6];
        to.data[7] = from.data[7];
        to.data[8] = from.data[8];
        to.data[9] = from.data[9];
        to.data[10] = from.data[10];
        to.data[11] = from.data[11];
        to.data[12] = from.data[12];
        to.data[13] = from.data[13];
        to.data[14] = from.data[14];
        to.data[15] = from.data[15];
    },
    /**
     * Creates a copy of the matrix
     * @return {Ayce.Matrix4} m
     */
    copy: function () {
        var m = new Ayce.Matrix4();
        m.data = new Float32Array(this.data);
        return m;
    },
    /**
     * TODO: Converts Ayce.Matrix4 to Ayce.Matrix3
     * @param {Ayce.Matrix4} toMatrix
     * @return {Ayce.Matrix3} m3
     */
    getMatrix3: function (toMatrix) {
        var m3;
        if(toMatrix){
            m3 = toMatrix;
        }
        else{
            m3 = new Ayce.Matrix3();
        }
        m3.data[0] = this.data[0];
        m3.data[1] = this.data[1];
        m3.data[2] = this.data[2];
        m3.data[3] = this.data[4];
        m3.data[4] = this.data[5];
        m3.data[5] = this.data[6];
        m3.data[6] = this.data[8];
        m3.data[7] = this.data[9];
        m3.data[8] = this.data[10];
        return m3;
    }
};