// Adapted from [gl-matrix](https://github.com/toji/gl-matrix) by toji // and [vecmath](https://github.com/mattdesl/vecmath) by mattdesl var Class = require('../utils/Class'); var EPSILON = 0.000001; var Matrix4 = new Class({ initialize: function Matrix4 (m) { this.val = new Float32Array(16); if (m) { // Assume Matrix4 with val: this.copy(m); } else { // Default to identity this.identity(); } }, clone: function () { return new Matrix4(this); }, set: function (src) { return this.copy(src); }, copy: function (src) { var out = this.val; var a = src.val; out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return this; }, fromArray: function (a) { var out = this.val; out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; out[4] = a[4]; out[5] = a[5]; out[6] = a[6]; out[7] = a[7]; out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; out[11] = a[11]; out[12] = a[12]; out[13] = a[13]; out[14] = a[14]; out[15] = a[15]; return this; }, identity: function () { var out = this.val; out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return this; }, transpose: function () { var a = this.val; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a12 = a[6]; var a13 = a[7]; var a23 = a[11]; a[1] = a[4]; a[2] = a[8]; a[3] = a[12]; a[4] = a01; a[6] = a[9]; a[7] = a[13]; a[8] = a02; a[9] = a12; a[11] = a[14]; a[12] = a03; a[13] = a13; a[14] = a23; return this; }, invert: function () { var a = this.val; var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; var a30 = a[12]; var a31 = a[13]; var a32 = a[14]; var a33 = a[15]; var b00 = a00 * a11 - a01 * a10; var b01 = a00 * a12 - a02 * a10; var b02 = a00 * a13 - a03 * a10; var b03 = a01 * a12 - a02 * a11; var b04 = a01 * a13 - a03 * a11; var b05 = a02 * a13 - a03 * a12; var b06 = a20 * a31 - a21 * a30; var b07 = a20 * a32 - a22 * a30; var b08 = a20 * a33 - a23 * a30; var b09 = a21 * a32 - a22 * a31; var b10 = a21 * a33 - a23 * a31; var b11 = a22 * a33 - a23 * a32; // Calculate the determinant var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; if (!det) { return null; } det = 1 / det; a[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; a[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; a[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; a[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; a[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; a[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; a[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; a[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; a[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; a[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; a[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; a[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; a[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; a[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; a[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; a[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; return this; }, adjoint: function () { var a = this.val; var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; var a30 = a[12]; var a31 = a[13]; var a32 = a[14]; var a33 = a[15]; a[0] = (a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22)); a[1] = -(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22)); a[2] = (a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12)); a[3] = -(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12)); a[4] = -(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22)); a[5] = (a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22)); a[6] = -(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12)); a[7] = (a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12)); a[8] = (a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21)); a[9] = -(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21)); a[10] = (a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11)); a[11] = -(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11)); a[12] = -(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21)); a[13] = (a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21)); a[14] = -(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11)); a[15] = (a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11)); return this; }, determinant: function () { var a = this.val; var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; var a30 = a[12]; var a31 = a[13]; var a32 = a[14]; var a33 = a[15]; var b00 = a00 * a11 - a01 * a10; var b01 = a00 * a12 - a02 * a10; var b02 = a00 * a13 - a03 * a10; var b03 = a01 * a12 - a02 * a11; var b04 = a01 * a13 - a03 * a11; var b05 = a02 * a13 - a03 * a12; var b06 = a20 * a31 - a21 * a30; var b07 = a20 * a32 - a22 * a30; var b08 = a20 * a33 - a23 * a30; var b09 = a21 * a32 - a22 * a31; var b10 = a21 * a33 - a23 * a31; var b11 = a22 * a33 - a23 * a32; // Calculate the determinant return b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; }, multiply: function (src) { var a = this.val; var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; var a30 = a[12]; var a31 = a[13]; var a32 = a[14]; var a33 = a[15]; var b = src.val; // Cache only the current line of the second matrix var b0 = b[0]; var b1 = b[1]; var b2 = b[2]; var b3 = b[3]; a[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; a[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; a[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; a[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7]; a[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; a[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; a[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; a[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11]; a[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; a[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; a[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; a[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15]; a[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; a[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; a[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; a[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; return this; }, translate: function (v) { var x = v.x; var y = v.y; var z = v.z; var a = this.val; a[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; a[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; a[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; a[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; return this; }, scale: function (v) { var x = v.x; var y = v.y; var z = v.z; var a = this.val; a[0] = a[0] * x; a[1] = a[1] * x; a[2] = a[2] * x; a[3] = a[3] * x; a[4] = a[4] * y; a[5] = a[5] * y; a[6] = a[6] * y; a[7] = a[7] * y; a[8] = a[8] * z; a[9] = a[9] * z; a[10] = a[10] * z; a[11] = a[11] * z; return this; }, rotate: function (rad, axis) { var a = this.val; var x = axis.x; var y = axis.y; var z = axis.z; var len = Math.sqrt(x * x + y * y + z * z); if (Math.abs(len) < EPSILON) { return null; } len = 1 / len; x *= len; y *= len; z *= len; var s = Math.sin(rad); var c = Math.cos(rad); var t = 1 - c; var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; // Construct the elements of the rotation matrix var b00 = x * x * t + c; var b01 = y * x * t + z * s; var b02 = z * x * t - y * s; var b10 = x * y * t - z * s; var b11 = y * y * t + c; var b12 = z * y * t + x * s; var b20 = x * z * t + y * s; var b21 = y * z * t - x * s; var b22 = z * z * t + c; // Perform rotation-specific matrix multiplication a[0] = a00 * b00 + a10 * b01 + a20 * b02; a[1] = a01 * b00 + a11 * b01 + a21 * b02; a[2] = a02 * b00 + a12 * b01 + a22 * b02; a[3] = a03 * b00 + a13 * b01 + a23 * b02; a[4] = a00 * b10 + a10 * b11 + a20 * b12; a[5] = a01 * b10 + a11 * b11 + a21 * b12; a[6] = a02 * b10 + a12 * b11 + a22 * b12; a[7] = a03 * b10 + a13 * b11 + a23 * b12; a[8] = a00 * b20 + a10 * b21 + a20 * b22; a[9] = a01 * b20 + a11 * b21 + a21 * b22; a[10] = a02 * b20 + a12 * b21 + a22 * b22; a[11] = a03 * b20 + a13 * b21 + a23 * b22; return this; }, rotateX: function (rad) { var a = this.val; var s = Math.sin(rad); var c = Math.cos(rad); var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; // Perform axis-specific matrix multiplication a[4] = a10 * c + a20 * s; a[5] = a11 * c + a21 * s; a[6] = a12 * c + a22 * s; a[7] = a13 * c + a23 * s; a[8] = a20 * c - a10 * s; a[9] = a21 * c - a11 * s; a[10] = a22 * c - a12 * s; a[11] = a23 * c - a13 * s; return this; }, rotateY: function (rad) { var a = this.val; var s = Math.sin(rad); var c = Math.cos(rad); var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a20 = a[8]; var a21 = a[9]; var a22 = a[10]; var a23 = a[11]; // Perform axis-specific matrix multiplication a[0] = a00 * c - a20 * s; a[1] = a01 * c - a21 * s; a[2] = a02 * c - a22 * s; a[3] = a03 * c - a23 * s; a[8] = a00 * s + a20 * c; a[9] = a01 * s + a21 * c; a[10] = a02 * s + a22 * c; a[11] = a03 * s + a23 * c; return this; }, rotateZ: function (rad) { var a = this.val; var s = Math.sin(rad); var c = Math.cos(rad); var a00 = a[0]; var a01 = a[1]; var a02 = a[2]; var a03 = a[3]; var a10 = a[4]; var a11 = a[5]; var a12 = a[6]; var a13 = a[7]; // Perform axis-specific matrix multiplication a[0] = a00 * c + a10 * s; a[1] = a01 * c + a11 * s; a[2] = a02 * c + a12 * s; a[3] = a03 * c + a13 * s; a[4] = a10 * c - a00 * s; a[5] = a11 * c - a01 * s; a[6] = a12 * c - a02 * s; a[7] = a13 * c - a03 * s; return this; }, fromRotationTranslation: function (q, v) { // Quaternion math var out = this.val; var x = q.x; var y = q.y; var z = q.z; var w = q.w; var x2 = x + x; var y2 = y + y; var z2 = z + z; var xx = x * x2; var xy = x * y2; var xz = x * z2; var yy = y * y2; var yz = y * z2; var zz = z * z2; var wx = w * x2; var wy = w * y2; var wz = w * z2; out[0] = 1 - (yy + zz); out[1] = xy + wz; out[2] = xz - wy; out[3] = 0; out[4] = xy - wz; out[5] = 1 - (xx + zz); out[6] = yz + wx; out[7] = 0; out[8] = xz + wy; out[9] = yz - wx; out[10] = 1 - (xx + yy); out[11] = 0; out[12] = v.x; out[13] = v.y; out[14] = v.z; out[15] = 1; return this; }, fromQuat: function (q) { var out = this.val; var x = q.x; var y = q.y; var z = q.z; var w = q.w; var x2 = x + x; var y2 = y + y; var z2 = z + z; var xx = x * x2; var xy = x * y2; var xz = x * z2; var yy = y * y2; var yz = y * z2; var zz = z * z2; var wx = w * x2; var wy = w * y2; var wz = w * z2; out[0] = 1 - (yy + zz); out[1] = xy + wz; out[2] = xz - wy; out[3] = 0; out[4] = xy - wz; out[5] = 1 - (xx + zz); out[6] = yz + wx; out[7] = 0; out[8] = xz + wy; out[9] = yz - wx; out[10] = 1 - (xx + yy); out[11] = 0; out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; return this; }, /** * Generates a frustum matrix with the given bounds * * @param {Number} left Left bound of the frustum * @param {Number} right Right bound of the frustum * @param {Number} bottom Bottom bound of the frustum * @param {Number} top Top bound of the frustum * @param {Number} near Near bound of the frustum * @param {Number} far Far bound of the frustum * @returns {Matrix4} this for chaining */ frustum: function (left, right, bottom, top, near, far) { var out = this.val; var rl = 1 / (right - left); var tb = 1 / (top - bottom); var nf = 1 / (near - far); out[0] = (near * 2) * rl; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = (near * 2) * tb; out[6] = 0; out[7] = 0; out[8] = (right + left) * rl; out[9] = (top + bottom) * tb; out[10] = (far + near) * nf; out[11] = -1; out[12] = 0; out[13] = 0; out[14] = (far * near * 2) * nf; out[15] = 0; return this; }, /** * Generates a perspective projection matrix with the given bounds * * @param {number} fovy Vertical field of view in radians * @param {number} aspect Aspect ratio. typically viewport width/height * @param {number} near Near bound of the frustum * @param {number} far Far bound of the frustum * @returns {Matrix4} this for chaining */ perspective: function (fovy, aspect, near, far) { var out = this.val; var f = 1.0 / Math.tan(fovy / 2); var nf = 1 / (near - far); out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = (far + near) * nf; out[11] = -1; out[12] = 0; out[13] = 0; out[14] = (2 * far * near) * nf; out[15] = 0; return this; }, /** * Generates a orthogonal projection matrix with the given bounds * * @param {number} left Left bound of the frustum * @param {number} right Right bound of the frustum * @param {number} bottom Bottom bound of the frustum * @param {number} top Top bound of the frustum * @param {number} near Near bound of the frustum * @param {number} far Far bound of the frustum * @returns {Matrix4} this for chaining */ ortho: function (left, right, bottom, top, near, far) { var out = this.val; var lr = left - right; var bt = bottom - top; var nf = near - far; // Avoid division by zero lr = (lr === 0) ? lr : 1 / lr; bt = (bt === 0) ? bt : 1 / bt; nf = (nf === 0) ? nf : 1 / nf; out[0] = -2 * lr; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = -2 * bt; out[6] = 0; out[7] = 0; out[8] = 0; out[9] = 0; out[10] = 2 * nf; out[11] = 0; out[12] = (left + right) * lr; out[13] = (top + bottom) * bt; out[14] = (far + near) * nf; out[15] = 1; return this; }, /** * Generates a look-at matrix with the given eye position, focal point, and up axis * * @param {Vector3} eye Position of the viewer * @param {Vector3} center Point the viewer is looking at * @param {Vector3} up vec3 pointing up * @returns {Matrix4} this for chaining */ lookAt: function (eye, center, up) { var out = this.val; var eyex = eye.x; var eyey = eye.y; var eyez = eye.z; var upx = up.x; var upy = up.y; var upz = up.z; var centerx = center.x; var centery = center.y; var centerz = center.z; if (Math.abs(eyex - centerx) < EPSILON && Math.abs(eyey - centery) < EPSILON && Math.abs(eyez - centerz) < EPSILON) { return this.identity(); } var z0 = eyex - centerx; var z1 = eyey - centery; var z2 = eyez - centerz; var len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); z0 *= len; z1 *= len; z2 *= len; var x0 = upy * z2 - upz * z1; var x1 = upz * z0 - upx * z2; var x2 = upx * z1 - upy * z0; len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); if (!len) { x0 = 0; x1 = 0; x2 = 0; } else { len = 1 / len; x0 *= len; x1 *= len; x2 *= len; } var y0 = z1 * x2 - z2 * x1; var y1 = z2 * x0 - z0 * x2; var y2 = z0 * x1 - z1 * x0; len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); if (!len) { y0 = 0; y1 = 0; y2 = 0; } else { len = 1 / len; y0 *= len; y1 *= len; y2 *= len; } out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0; out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0; out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0; out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez); out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez); out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez); out[15] = 1; return this; } }); Matrix4.mul = Matrix4.multiply; Matrix4.idt = Matrix4.identity; Matrix4.reset = Matrix4.idt; module.exports = Matrix4;