Source: math/Matrix4.js

import { Vector3 } from './Vector3.js';

var _vec3_1 = new Vector3();
var _mat4_1 = new Matrix4();

var _x = new Vector3();
var _y = new Vector3();
var _z = new Vector3();

/**
 * a 4x4 matrix class
 * @constructor
 * @memberof zen3d
 */
function Matrix4() {
	this.elements = new Float32Array([
		1, 0, 0, 0,
		0, 1, 0, 0,
		0, 0, 1, 0,
		0, 0, 0, 1
	]);
}

Object.assign(Matrix4.prototype, /** @lends zen3d.Matrix4.prototype */{

	/**
     *
     */
	identity: function() {
		this.set(
			1, 0, 0, 0,
			0, 1, 0, 0,
			0, 0, 1, 0,
			0, 0, 0, 1
		);

		return this;
	},

	/**
     *
     */
	set: function(n11, n12, n13, n14,
		n21, n22, n23, n24,
		n31, n32, n33, n34,
		n41, n42, n43, n44) {
		var ele = this.elements;
		ele[0] = n11;
		ele[4] = n12;
		ele[8] = n13;
		ele[12] = n14;
		ele[1] = n21;
		ele[5] = n22;
		ele[9] = n23;
		ele[13] = n24;
		ele[2] = n31;
		ele[6] = n32;
		ele[10] = n33;
		ele[14] = n34;
		ele[3] = n41;
		ele[7] = n42;
		ele[11] = n43;
		ele[15] = n44;

		return this;
	},

	/**
     *
     */
	copy: function(m) {
		this.elements.set(m.elements);

		return this;
	},

	/**
     *
     */
	makeTranslation: function(x, y, z) {
		this.set(
			1, 0, 0, x,
			0, 1, 0, y,
			0, 0, 1, z,
			0, 0, 0, 1
		);

		return this;
	},

	/**
     *
     */
	multiply: function(m) {
		return this.multiplyMatrices(this, m);
	},

	/**
     *
     */
	premultiply: function(m) {
		return this.multiplyMatrices(m, this);
	},

	/**
     *
     */
	multiplyMatrices: function(a, b) {
		var ae = a.elements;
		var be = b.elements;
		var te = this.elements;

		var a11 = ae[0],
			a12 = ae[4],
			a13 = ae[8],
			a14 = ae[12];
		var a21 = ae[1],
			a22 = ae[5],
			a23 = ae[9],
			a24 = ae[13];
		var a31 = ae[2],
			a32 = ae[6],
			a33 = ae[10],
			a34 = ae[14];
		var a41 = ae[3],
			a42 = ae[7],
			a43 = ae[11],
			a44 = ae[15];

		var b11 = be[0],
			b12 = be[4],
			b13 = be[8],
			b14 = be[12];
		var b21 = be[1],
			b22 = be[5],
			b23 = be[9],
			b24 = be[13];
		var b31 = be[2],
			b32 = be[6],
			b33 = be[10],
			b34 = be[14];
		var b41 = be[3],
			b42 = be[7],
			b43 = be[11],
			b44 = be[15];

		te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
		te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
		te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
		te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;

		te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
		te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
		te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
		te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;

		te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
		te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
		te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
		te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;

		te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
		te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
		te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
		te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;

		return this;
	},

	/**
     *
     */
	transpose: function() {
		var te = this.elements;
		var tmp;

		tmp = te[1];
		te[1] = te[4];
		te[4] = tmp;
		tmp = te[2];
		te[2] = te[8];
		te[8] = tmp;
		tmp = te[6];
		te[6] = te[9];
		te[9] = tmp;

		tmp = te[3];
		te[3] = te[12];
		te[12] = tmp;
		tmp = te[7];
		te[7] = te[13];
		te[13] = tmp;
		tmp = te[11];
		te[11] = te[14];
		te[14] = tmp;

		return this;
	},

	/**
     *
     */
	inverse: function() {
		return this.getInverse(this);
	},

	/**
     *
     */
	getInverse: function(m) {
		// based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
		var te = this.elements,
			me = m.elements,

			n11 = me[0],
			n21 = me[1],
			n31 = me[2],
			n41 = me[3],
			n12 = me[4],
			n22 = me[5],
			n32 = me[6],
			n42 = me[7],
			n13 = me[8],
			n23 = me[9],
			n33 = me[10],
			n43 = me[11],
			n14 = me[12],
			n24 = me[13],
			n34 = me[14],
			n44 = me[15],

			t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,
			t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,
			t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,
			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("can't invert matrix, determinant is 0");

			return this.identity();
		}

		var detInv = 1 / det;

		te[0] = t11 * detInv;
		te[1] = (n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) * detInv;
		te[2] = (n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) * detInv;
		te[3] = (n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) * detInv;

		te[4] = t12 * detInv;
		te[5] = (n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) * detInv;
		te[6] = (n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) * detInv;
		te[7] = (n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) * detInv;

		te[8] = t13 * detInv;
		te[9] = (n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) * detInv;
		te[10] = (n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) * detInv;
		te[11] = (n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) * detInv;

		te[12] = t14 * detInv;
		te[13] = (n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) * detInv;
		te[14] = (n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) * detInv;
		te[15] = (n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) * detInv;

		return this;
	},

	/**
     * Make transform from pos&scale&rotation(Quaternion).
     * @method
     */
	transform: function(pos, scale, rot) {
		var rotMatrix = rot.toMatrix4(_mat4_1);

		var rele = rotMatrix.elements;
		var ele = this.elements;

		ele[0] = rele[0] * scale.x;
		ele[1] = rele[1] * scale.x;
		ele[2] = rele[2] * scale.x;
		ele[3] = 0;

		ele[4] = rele[4] * scale.y;
		ele[5] = rele[5] * scale.y;
		ele[6] = rele[6] * scale.y;
		ele[7] = 0;

		ele[8] = rele[8] * scale.z;
		ele[9] = rele[9] * scale.z;
		ele[10] = rele[10] * scale.z;
		ele[11] = 0;

		ele[12] = pos.x;
		ele[13] = pos.y;
		ele[14] = pos.z;
		ele[15] = 1;

		return this;
	},

	/**
     *
     */
	makeRotationFromQuaternion: function(q) {
		var te = this.elements;

		var x = q.x,
			y = q.y,
			z = q.z,
			w = q.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;

		te[0] = 1 - (yy + zz);
		te[4] = xy - wz;
		te[8] = xz + wy;

		te[1] = xy + wz;
		te[5] = 1 - (xx + zz);
		te[9] = yz - wx;

		te[2] = xz - wy;
		te[6] = yz + wx;
		te[10] = 1 - (xx + yy);

		// last column
		te[3] = 0;
		te[7] = 0;
		te[11] = 0;

		// bottom row
		te[12] = 0;
		te[13] = 0;
		te[14] = 0;
		te[15] = 1;

		return this;
	},

	/**
     * @method
     */
	extractRotation: function(m) {
		// this method does not support reflection matrices

		var te = this.elements;
		var me = m.elements;

		var scaleX = 1 / _vec3_1.setFromMatrixColumn(m, 0).getLength();
		var scaleY = 1 / _vec3_1.setFromMatrixColumn(m, 1).getLength();
		var scaleZ = 1 / _vec3_1.setFromMatrixColumn(m, 2).getLength();

		te[0] = me[0] * scaleX;
		te[1] = me[1] * scaleX;
		te[2] = me[2] * scaleX;
		te[3] = 0;

		te[4] = me[4] * scaleY;
		te[5] = me[5] * scaleY;
		te[6] = me[6] * scaleY;
		te[7] = 0;

		te[8] = me[8] * scaleZ;
		te[9] = me[9] * scaleZ;
		te[10] = me[10] * scaleZ;
		te[11] = 0;

		te[12] = 0;
		te[13] = 0;
		te[14] = 0;
		te[15] = 1;

		return this;
	},

	/**
     * @method
     */
	lookAtRH: function(eye, target, up) {
		var te = this.elements;

		_z.subVectors(eye, target);

		if (_z.getLengthSquared() === 0) {
			// eye and target are in the same position

			_z.z = 1;
		}

		_z.normalize();
		_x.crossVectors(up, _z);

		if (_x.getLengthSquared() === 0) {
			// up and z are parallel

			if (Math.abs(up.z) === 1) {
				_z.x += 0.0001;
			} else {
				_z.z += 0.0001;
			}

			_z.normalize();
			_x.crossVectors(up, _z);
		}

		_x.normalize();
		_y.crossVectors(_z, _x);

		te[0] = _x.x; te[4] = _y.x; te[8] = _z.x;
		te[1] = _x.y; te[5] = _y.y; te[9] = _z.y;
		te[2] = _x.z; te[6] = _y.z; te[10] = _z.z;

		return this;
	},

	/**
     * @method
     */
	decompose: function(position, quaternion, scale) {
		var te = this.elements;

		var sx = _vec3_1.set(te[0], te[1], te[2]).getLength();
		var sy = _vec3_1.set(te[4], te[5], te[6]).getLength();
		var sz = _vec3_1.set(te[8], te[9], te[10]).getLength();

		// if determine is negative, we need to invert one scale
		var det = this.determinant();
		if (det < 0) {
			sx = -sx;
		}

		position.x = te[12];
		position.y = te[13];
		position.z = te[14];

		// scale the rotation part
		_mat4_1.copy(this);

		var invSX = 1 / sx;
		var invSY = 1 / sy;
		var invSZ = 1 / sz;

		_mat4_1.elements[0] *= invSX;
		_mat4_1.elements[1] *= invSX;
		_mat4_1.elements[2] *= invSX;

		_mat4_1.elements[4] *= invSY;
		_mat4_1.elements[5] *= invSY;
		_mat4_1.elements[6] *= invSY;

		_mat4_1.elements[8] *= invSZ;
		_mat4_1.elements[9] *= invSZ;
		_mat4_1.elements[10] *= invSZ;

		quaternion.setFromRotationMatrix(_mat4_1);

		scale.x = sx;
		scale.y = sy;
		scale.z = sz;

		return this;
	},

	/**
     * @method
     */
	determinant: function() {
		var te = this.elements;

		var n11 = te[0],
			n12 = te[4],
			n13 = te[8],
			n14 = te[12];
		var n21 = te[1],
			n22 = te[5],
			n23 = te[9],
			n24 = te[13];
		var n31 = te[2],
			n32 = te[6],
			n33 = te[10],
			n34 = te[14];
		var n41 = te[3],
			n42 = te[7],
			n43 = te[11],
			n44 = te[15];

		// TODO: make this more efficient
		// ( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )

		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
            )

		);
	},

	/**
     *
     */
	fromArray: function(array, offset) {
		if (offset === undefined) offset = 0;

		for (var i = 0; i < 16; i++) {
			this.elements[i] = array[i + offset];
		}

		return this;
	},

	/**
     *
     */
	getMaxScaleOnAxis: function() {
		var te = this.elements;

		var scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2];
		var scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6];
		var scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10];

		return Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq));
	},

	/**
     *
     */
	toArray: function(array, offset) {
		if (array === undefined) array = [];
		if (offset === undefined) offset = 0;

		var te = this.elements;

		array[offset] = te[0];
		array[offset + 1] = te[1];
		array[offset + 2] = te[2];
		array[offset + 3] = te[3];

		array[offset + 4] = te[4];
		array[offset + 5] = te[5];
		array[offset + 6] = te[6];
		array[offset + 7] = te[7];

		array[offset + 8] = te[8];
		array[offset + 9] = te[9];
		array[offset + 10] = te[10];
		array[offset + 11] = te[11];

		array[offset + 12] = te[12];
		array[offset + 13] = te[13];
		array[offset + 14] = te[14];
		array[offset + 15] = te[15];

		return array;
	}

});

export { Matrix4 };