"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Quaternion_1 = require("./Quaternion");
var Vector3_1 = require("./Vector3");
// don't use elements: number[][], because Matrix3/4 have fixed lengths
// perfomance: 1D array is slightly better in JS, but 2D is more intuitive -> less bugs
/** 4x4 Matrix */
var Matrix4 = /** @class */ (function () {
    function Matrix4(elements) {
        this.elements = elements;
    }
    //#region create / init matrix (identity)
    /**
     * Creates a new Matrix4
     * @param elements (optional)
     */
    //prettier-ignore
    Matrix4.create = function (elements) {
        if (elements) {
            if (elements.length !== 4 && elements.length[0] !== 4) {
                console.warn("input isn't 4x4 array!");
                return Matrix4.identity();
            }
            var copyElements = [
                [0, 0, 0, 0],
                [0, 0, 0, 0],
                [0, 0, 0, 0],
                [0, 0, 0, 0],
            ];
            for (var row = 0; row < copyElements.length; row++) {
                copyElements[row] = elements[row].slice(0, 4);
            }
            return {
                elements: copyElements,
            };
        }
        else {
            return Matrix4.identity();
        }
    };
    /**
     * Creates a new Matrix4 with rotation
     * @param quaternion Quaternion
     */
    Matrix4.createFromQuaternion = function (quaternion) {
        return Matrix4.compose({ x: 0, y: 0, z: 0 }, quaternion, { x: 1, y: 1, z: 1 });
    };
    /**
     * Returns an identity Matrix4
     */
    Matrix4.identity = function () {
        return {
            //prettier-ignore
            elements: [
                [1, 0, 0, 0],
                [0, 1, 0, 0],
                [0, 0, 1, 0],
                [0, 0, 0, 1],
            ]
        };
    };
    /**
     * Sets the given matrix in place
     * @param matrix Matrix4
     * @param n11_n44 number elements
     */
    Matrix4.set = function (matrix, n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44) {
        var me = matrix.elements;
        me[0][0] = n11;
        me[0][1] = n12;
        me[0][2] = n13;
        me[0][3] = n14;
        me[1][0] = n21;
        me[1][1] = n22;
        me[1][2] = n23;
        me[1][3] = n24;
        me[2][0] = n31;
        me[2][1] = n32;
        me[2][2] = n33;
        me[2][3] = n34;
        me[3][0] = n41;
        me[3][1] = n42;
        me[3][2] = n43;
        me[3][3] = n44;
    };
    //#endregion create / init matrix
    //#region clone / copy
    /**
     * Creates new matrix from input
     * @param matrix Matrix4
     */
    Matrix4.clone = function (matrix) {
        var result = Matrix4.create();
        for (var row = 0; row < matrix.elements.length; row++) {
            result.elements[row] = matrix.elements[row].slice(0, 4);
        }
        return result;
    };
    /**
     * Copies values from input matrix 1 to input matrix 2
     * @param from Matrix4 1
     * @param to Matrix4 2
     */
    Matrix4.copy = function (from, to) {
        for (var row = 0; row < from.elements.length; row++) {
            to.elements[row] = from.elements[row].slice(0, 4);
        }
    };
    //#endregion clone / copy
    //#region Absolute matrix
    /**
     * Creates a new Matrix3 with absolute values of elements
     * @param input
     */
    Matrix4.absoluteElements = function (input) {
        var result = Matrix4.clone(input);
        for (var row = 0; row < result.elements.length; row++) {
            for (var col = 0; col < result.elements[row].length; col++) {
                result.elements[row][col] = Math.abs(result.elements[row][col]);
            }
        }
        return result;
    };
    //#endregion Absolute matrix
    //#region Simple Algebra (addition + multiplication)
    /**
     * Creates a new Matrix4 with A + B
     * @param matA Matrix4 A
     * @param matB Matrix4 B
     */
    Matrix4.add = function (matA, matB) {
        var result = Matrix4.clone(matA);
        for (var row = 0; row < result.elements.length; row++) {
            for (var col = 0; col < result.elements[row].length; col++) {
                result.elements[row][col] += matB.elements[row][col];
            }
        }
        return result;
    };
    /**
     * Creates new Matrix4 and multiplies matrix with scalar (on all elements)
     * @param matrix Matrix4
     * @param scalar number
     */
    //prettier-ignore
    Matrix4.multiplyScalar = function (matrix, scalar) {
        var result = Matrix4.clone(matrix);
        var re = result.elements;
        re[0][0] *= scalar;
        re[0][1] *= scalar;
        re[0][2] *= scalar;
        re[0][3] *= scalar;
        re[1][0] *= scalar;
        re[1][1] *= scalar;
        re[1][2] *= scalar;
        re[1][3] *= scalar;
        re[2][0] *= scalar;
        re[2][1] *= scalar;
        re[2][2] *= scalar;
        re[2][3] *= scalar;
        re[3][0] *= scalar;
        re[3][1] *= scalar;
        re[3][2] *= scalar;
        re[3][3] *= scalar;
        return result;
    };
    /**
     * Creates a new Matrix4 with A * B
     * @param matA Matrix4 A
     * @param matB Matrix4 B
     */
    //prettier-ignore
    Matrix4.multiply = function (a, b) {
        var result = Matrix4.create();
        var ae = a.elements;
        var be = b.elements;
        var re = result.elements;
        // tslint:disable: one-variable-per-declaration
        var a11 = ae[0][0], a12 = ae[0][1], a13 = ae[0][2], a14 = ae[0][3];
        var a21 = ae[1][0], a22 = ae[1][1], a23 = ae[1][2], a24 = ae[1][3];
        var a31 = ae[2][0], a32 = ae[2][1], a33 = ae[2][2], a34 = ae[2][3];
        var a41 = ae[3][0], a42 = ae[3][1], a43 = ae[3][2], a44 = ae[3][3];
        var b11 = be[0][0], b12 = be[0][1], b13 = be[0][2], b14 = be[0][3];
        var b21 = be[1][0], b22 = be[1][1], b23 = be[1][2], b24 = be[1][3];
        var b31 = be[2][0], b32 = be[2][1], b33 = be[2][2], b34 = be[2][3];
        var b41 = be[3][0], b42 = be[3][1], b43 = be[3][2], b44 = be[3][3];
        re[0][0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
        re[0][1] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
        re[0][2] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
        re[0][3] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
        re[1][0] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
        re[1][1] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
        re[1][2] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
        re[1][3] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
        re[2][0] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
        re[2][1] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
        re[2][2] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
        re[2][3] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
        re[3][0] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
        re[3][1] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
        re[3][2] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
        re[3][3] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
        return result;
    };
    //#endregion Simple Algebra
    //#region Inverse / Transpose
    /**
     * Returns the inverse from matrix
     * @param matrix Matrix4
     */
    //prettier-ignore
    Matrix4.getInverse = function (matrix) {
        // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
        var result = Matrix4.create();
        var me = matrix.elements;
        var re = result.elements;
        var n11 = me[0][0], n21 = me[0][1], n31 = me[0][2], n41 = me[0][3];
        var n12 = me[1][0], n22 = me[1][1], n32 = me[1][2], n42 = me[1][3];
        var n13 = me[2][0], n23 = me[2][1], n33 = me[2][2], n43 = me[2][3];
        var n14 = me[3][0], n24 = me[3][1], n34 = me[3][2], n44 = me[3][3];
        var t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44;
        var t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44;
        var t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44;
        var t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
        var det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
        if (det === 0) {
            console.warn("getInverse4() can't invert matrix, determinant is 0");
            return undefined;
        }
        var detInv = 1 / det;
        re[0][0] = t11 * detInv;
        re[0][1] = (n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) * detInv;
        re[0][2] = (n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) * detInv;
        re[0][3] = (n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) * detInv;
        re[1][0] = t12 * detInv;
        re[1][1] = (n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) * detInv;
        re[1][2] = (n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) * detInv;
        re[1][3] = (n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) * detInv;
        re[2][0] = t13 * detInv;
        re[2][1] = (n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) * detInv;
        re[2][2] = (n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) * detInv;
        re[2][3] = (n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) * detInv;
        re[3][0] = t14 * detInv;
        re[3][1] = (n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) * detInv;
        re[3][2] = (n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) * detInv;
        re[3][3] = (n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) * detInv;
        return result;
    };
    /**
     * Returns the transposed matrix as a new Matrix4
     * @param matrix Matrix4
     */
    //prettier-ignore
    Matrix4.transpose = function (matrix) {
        var result = Matrix4.clone(matrix);
        var m = result.elements;
        var tmp;
        tmp = m[0][1];
        m[0][1] = m[1][0];
        m[1][0] = tmp;
        tmp = m[0][2];
        m[0][2] = m[2][0];
        m[2][0] = tmp;
        tmp = m[1][2];
        m[1][2] = m[2][1];
        m[2][1] = tmp;
        tmp = m[0][3];
        m[0][3] = m[3][0];
        m[3][0] = tmp;
        tmp = m[1][3];
        m[1][3] = m[3][1];
        m[3][1] = tmp;
        tmp = m[2][3];
        m[2][3] = m[3][2];
        m[3][2] = tmp;
        return result;
    };
    //#endregion Inverse / Transpose
    //#region Transformation (Scale, Rotation, Translation, Composing)
    /**
     * Returns the scale matrix for given scale vector
     * @param scale vec3
     */
    //prettier-ignore
    Matrix4.getScaleMatrix = function (scale) {
        return Matrix4.create([
            [scale.x, 0, 0, 0],
            [0, scale.y, 0, 0],
            [0, 0, scale.z, 0],
            [0, 0, 0, 1],
        ]);
    };
    /**
     * Builds a new rotation matrix with quaternion (in place)
     * @param matrix Matrix4
     * @param quaternion Quaternion
     */
    //prettier-ignore
    Matrix4.makeRotationMatrixFromQuaternionInPlace = function (matrix, quaternion) {
        // tslint:disable: one-variable-per-declaration
        var me = matrix.elements;
        var x = quaternion.x, y = quaternion.y, z = quaternion.z, w = quaternion.w;
        var x2 = x + x, y2 = y + y, z2 = z + z;
        var xx = x * x2, xy = x * y2, xz = x * z2;
        var yy = y * y2, yz = y * z2, zz = z * z2;
        var wx = w * x2, wy = w * y2, wz = w * z2;
        // first 3 rows
        me[0][0] = 1 - (yy + zz);
        me[1][0] = xy - wz;
        me[2][0] = xz + wy;
        me[0][1] = xy + wz;
        me[1][1] = 1 - (xx + zz);
        me[2][1] = yz - wx;
        me[0][2] = xz - wy;
        me[1][2] = yz + wx;
        me[2][2] = 1 - (xx + yy);
        me[0][3] = 0;
        me[1][3] = 0;
        me[2][3] = 0;
        // bottom row
        me[3][0] = 0;
        me[3][1] = 0;
        me[3][2] = 0;
        me[3][3] = 1;
    };
    /**
     * Returns the X rotation matrix for given angle
     * @param angle in radians (number)
     */
    //prettier-ignore
    Matrix4.getXRotationMatrix = function (angle) {
        return Matrix4.create([
            [1, 0, 0, 0],
            [0, Math.cos(angle), -Math.sin(angle), 0],
            [0, Math.sin(angle), Math.cos(angle), 0],
            [0, 0, 0, 1],
        ]);
    };
    /**
     * Returns the Y rotation matrix for given angle
     * @param angle in radians (number)
     */
    //prettier-ignore
    Matrix4.getYRotationMatrix = function (angle) {
        return Matrix4.create([
            [Math.cos(angle), 0, Math.sin(angle), 0],
            [0, 1, 0, 0],
            [-Math.sin(angle), 0, Math.cos(angle), 0],
            [0, 0, 0, 1],
        ]);
    };
    /**
     * Returns the Z rotation matrix for given angle
     * @param angle in radians (number)
     */
    //prettier-ignore
    Matrix4.getZRotationMatrix = function (angle) {
        return Matrix4.create([
            [Math.cos(angle), -Math.sin(angle), 0, 0],
            [Math.sin(angle), Math.cos(angle), 0, 0],
            [0, 0, 1, 0],
            [0, 0, 0, 1],
        ]);
    };
    /**
     * Returns the translation matrix for given position vector
     * @param pos vec3
     */
    //prettier-ignore
    Matrix4.getTranslationMatrix = function (pos) {
        return Matrix4.create([
            [1, 0, 0, pos.x],
            [0, 1, 0, pos.y],
            [0, 0, 1, pos.z],
            [0, 0, 0, 1],
        ]);
    };
    /**
     * Returns transformation matrix composed of translation, rotation and scale.
     * @param translation vec3
     * @param rotation Quaternion
     * @param scale vec3
     */
    //prettier-ignore
    Matrix4.compose = function (translation, rotation, scale) {
        var result = Matrix4.create();
        var re = result.elements;
        var tx = translation.x, ty = translation.y, tz = translation.z;
        var rx = rotation.x, ry = rotation.y, rz = rotation.z, rw = rotation.w;
        var sx = scale.x, sy = scale.y, sz = scale.z;
        var x2 = rx + rx, y2 = ry + ry, z2 = rz + rz;
        var xx = rx * x2, xy = rx * y2, xz = rx * z2;
        var yy = ry * y2, yz = ry * z2, zz = rz * z2;
        var wx = rw * x2, wy = rw * y2, wz = rw * z2;
        re[0][0] = (1 - (yy + zz)) * sx;
        re[0][1] = (xy - wz) * sx;
        re[0][2] = (xz + wy) * sx;
        re[0][3] = tx;
        re[1][0] = (xy + wz) * sy;
        re[1][1] = (1 - (xx + zz)) * sy;
        re[1][2] = (yz - wx) * sy;
        re[1][3] = ty;
        re[2][0] = (xz - wy) * sz;
        re[2][1] = (yz + wx) * sz;
        re[2][2] = (1 - (xx + yy)) * sz;
        re[2][3] = tz;
        re[3][0] = 0;
        re[3][1] = 0;
        re[3][2] = 0;
        re[3][3] = 1;
        return result;
    };
    /**
     * Decomposes the given matrix into the translation, rotation and scale components.
     * @param matrix Matrix4
     * @returns Object\[Position as Vector3, Rotation as Quaternion, Scale as Vector3\]
     */
    //prettier-ignore
    Matrix4.decompose = function (matrix) {
        var me = matrix.elements;
        var position = Vector3_1.Vector3.create();
        var quaternion;
        var scale = Vector3_1.Vector3.create();
        var sx = Vector3_1.Vector3.magnitude({ x: me[0][0], y: me[0][1], z: me[0][2] });
        var sy = Vector3_1.Vector3.magnitude({ x: me[1][0], y: me[1][1], z: me[1][2] });
        var sz = Vector3_1.Vector3.magnitude({ x: me[2][0], y: me[2][1], z: me[2][2] });
        // if determine is negative, we need to invert one scale
        var det = Matrix4.determinant(matrix);
        if (det < 0) {
            sx = -sx;
        }
        position.x = me[0][3];
        position.y = me[1][3];
        position.z = me[2][3];
        // scale the rotation part
        var tmp = Matrix4.clone(matrix);
        var te = tmp.elements;
        var invSX = 1 / sx;
        var invSY = 1 / sy;
        var invSZ = 1 / sz;
        te[0][0] *= invSX;
        te[0][1] *= invSX;
        te[0][2] *= invSX;
        te[1][0] *= invSY;
        te[1][1] *= invSY;
        te[1][2] *= invSY;
        te[2][0] *= invSZ;
        te[2][1] *= invSZ;
        te[2][2] *= invSZ;
        quaternion = Quaternion_1.Quaternion.createFromRotationMatrix(tmp);
        scale.x = sx;
        scale.y = sy;
        scale.z = sz;
        return [position, quaternion, scale];
    };
    /**
     * Evaluates a rotation needed to be applied to an object positioned at sourcePoint to face destPoint.
     * When no rotation is applied, the eye looks at positive z-axis.
     * @param eye Vector3 - Coordinates of source point
     * @param target Vector3 - Coordinates of destination point
     * @param up (optional) Vector3 - Where does the up vector points to? (needs to be a unit vector!)
     * @param base (optional) String - Enum: {"x", "y", "z"} Where does your geometry points to when inserted? Default: z
     */
    // prettier-ignore
    Matrix4.lookAt = function (eye, target, up, base) {
        if (base === void 0) { base = "z"; }
        // see: https://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/lookat-function
        // and Three.js Matrix4.js lookAt
        if (base === undefined || !(base === "x" || base === "y" || base === "z")) {
            console.warn("Matrix4.lookAt() WARNING: base param received not allowed value. " +
                "Only \"x\", \"y\" and \"z\" are allowed! Returning to default value (\"z\")");
            base = "z";
        }
        // TODO
        if (base === "y") {
            console.warn("base = y not implemented yet. Switching to z now.");
            base = "z";
        }
        // console.log("lookAt");
        if (!up) { // user didn't specify up vector
            up = new Vector3_1.Vector3(0, 1, 0);
        }
        else {
            // because this function rotates clockwise
            // and we rotate counter-clockwise
            up.x *= -1;
            up.z *= -1;
        }
        // FORWARD
        var newForward = Vector3_1.Vector3.sub(eye, target);
        if (base === "x") {
            // swap values, so current x axis points to z axis
            newForward = Vector3_1.Vector3.create(-newForward.z, -newForward.y, newForward.x);
            up = Vector3_1.Vector3.create(up.z, up.y, up.x);
            // console.log("corrected vector:", newForward, up);
        }
        // swap y-axis, so up looks in the right direction
        if (target.x < 0 && target.z < 0) {
            newForward.y *= -1;
        }
        // swap z-axis, so forward looks in the right direction
        newForward.z *= -1;
        if (Vector3_1.Vector3.magnitudeSquared(newForward) === 0) {
            // eye and target are in the same position
            newForward.z = 1;
        }
        Vector3_1.Vector3.normalizeInPlace(newForward);
        // console.log("forward:", newForward);
        // RIGHT
        var newRight = Vector3_1.Vector3.crossProduct(up, newForward);
        /*
        if (base && Vector3.isEqual(base, {x: 1, y: 0, z: 0})) {
            newRight = Vector3.crossProduct(newForward, up);
        }
        */
        // if (newForward.z < 0) { newRight = Vector3.crossProduct(newForward, up); }
        if (Vector3_1.Vector3.magnitudeSquared(newRight) === 0) {
            // console.log("up and newForward parallel");
            // up and newForward are parallel
            if (newForward.y > 0) {
                up = new Vector3_1.Vector3(0, 0, -1);
            }
            else {
                up = new Vector3_1.Vector3(0, 0, 1);
            }
            newRight = Vector3_1.Vector3.crossProduct(up, newForward);
        }
        Vector3_1.Vector3.normalizeInPlace(newRight);
        // console.log("right:", newRight);
        // UP
        var newUp = Vector3_1.Vector3.crossProduct(newForward, newRight);
        Vector3_1.Vector3.normalizeInPlace(newUp);
        var rotationMat = Matrix4.create();
        var te = rotationMat.elements;
        if (base === "x") {
            te[0][0] = newForward.z;
            te[0][1] = newUp.z;
            te[0][2] = newRight.z;
            te[0][3] = eye.x;
            te[1][0] = newForward.y;
            te[1][1] = newUp.y;
            te[1][2] = newRight.y;
            te[1][3] = eye.y;
            te[2][0] = newForward.x;
            te[2][1] = newUp.x;
            te[2][2] = newRight.x;
            te[2][3] = eye.z;
            //         0,                       0,                  0,                     1
        }
        else { // z
            te[0][0] = newRight.x;
            te[0][1] = newRight.y;
            te[0][2] = newRight.z;
            te[0][3] = eye.x;
            te[1][0] = newUp.x;
            te[1][1] = newUp.y;
            te[1][2] = newUp.z;
            te[1][3] = eye.y;
            te[2][0] = newForward.x;
            te[2][1] = newForward.y;
            te[2][2] = newForward.z;
            te[2][3] = eye.z;
            //         0,                       0,                       0,                       1
        }
        return rotationMat;
    };
    //#endregion Transformation
    //#region Helper functions (determinant)
    /**
     * Computes determinant of given Matrix4
     * @param matrix Matrix4
     */
    //prettier-ignore
    Matrix4.determinant = function (matrix) {
        // Based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
        var te = matrix.elements;
        // tslint:disable: one-variable-per-declaration
        var n11 = te[0][0], n12 = te[1][0], n13 = te[2][0], n14 = te[3][0];
        var n21 = te[0][1], n22 = te[1][1], n23 = te[2][1], n24 = te[3][1];
        var n31 = te[0][2], n32 = te[1][2], n33 = te[2][2], n34 = te[3][2];
        var n41 = te[0][3], n42 = te[1][3], n43 = te[2][3], n44 = te[3][3];
        //prettier-ignore
        return (n41 * (+n14 * n23 * n32
            - n13 * n24 * n32
            - n14 * n22 * n33
            + n12 * n24 * n33
            + n13 * n22 * n34
            - n12 * n23 * n34) +
            n42 * (+n11 * n23 * n34
                - n11 * n24 * n33
                + n14 * n21 * n33
                - n13 * n21 * n34
                + n13 * n24 * n31
                - n14 * n23 * n31) +
            n43 * (+n11 * n24 * n32
                - n11 * n22 * n34
                - n14 * n21 * n32
                + n12 * n21 * n34
                + n14 * n22 * n31
                - n12 * n24 * n31) +
            n44 * (-n13 * n22 * n31
                - n11 * n23 * n32
                + n11 * n22 * n33
                + n13 * n21 * n32
                - n12 * n21 * n33
                + n12 * n23 * n31));
    };
    /**
     * Checks if both matrices are equal
     * @param matA Matrix3|Matrix4
     * @param matB Matrix3|Matrix4
     */
    Matrix4.equals = function (matA, matB) {
        if (matA.elements.length !== matB.elements.length) {
            console.warn("Matrices must be of the same type!");
            return false;
        }
        var a = matA.elements;
        var b = matB.elements;
        for (var i = 0; i < a.length; i++) {
            for (var j = 0; j < a[i].length; j++) {
                if (a[i][j] !== b[i][j]) {
                    return false;
                }
            }
        }
        return true;
    };
    /**
     * Returns a String representing the given matrix
     * @param matrix Matrix4
     */
    //prettier-ignore
    Matrix4.stringify = function (matrix) {
        // tslint:disable: indent
        var te = matrix.elements;
        // calculate space between elements
        var colLen = [0, 0, 0, 0]; // max lengths of number
        for (var col = 0; col < 4; col++) {
            for (var row = 0; row < 4; row++) {
                var len = te[row][col].toString().length;
                if (len > colLen[col]) {
                    colLen[col] = len;
                }
            }
        }
        var spaces = new Array();
        for (var row = 0; row < 4; row++) {
            spaces[row] = new Array();
            for (var col = 0; col < 4; col++) {
                var len = te[row][col].toString().length;
                spaces[row][col] = " ".repeat(colLen[col] - len);
            }
        }
        return "\n" +
            ("[" + spaces[0][0] + te[0][0] + ", " + spaces[0][1] + te[0][1] + ", " + spaces[0][2] + te[0][2] + ", " + spaces[0][3] + te[0][3] + "],\n") +
            ("[" + spaces[1][0] + te[1][0] + ", " + spaces[1][1] + te[1][1] + ", " + spaces[1][2] + te[1][2] + ", " + spaces[1][3] + te[1][3] + "],\n") +
            ("[" + spaces[2][0] + te[2][0] + ", " + spaces[2][1] + te[2][1] + ", " + spaces[2][2] + te[2][2] + ", " + spaces[2][3] + te[2][3] + "],\n") +
            ("[" + spaces[3][0] + te[3][0] + ", " + spaces[3][1] + te[3][1] + ", " + spaces[3][2] + te[3][2] + ", " + spaces[3][3] + te[3][3] + "]\n");
    };
    return Matrix4;
}());
exports.Matrix4 = Matrix4;
