michael@0: /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: const {Cu} = require("chrome"); michael@0: michael@0: let TiltUtils = require("devtools/tilt/tilt-utils"); michael@0: michael@0: /** michael@0: * Module containing high performance matrix and vector operations for WebGL. michael@0: * Inspired by glMatrix, version 0.9.6, (c) 2011 Brandon Jones. michael@0: */ michael@0: michael@0: let EPSILON = 0.01; michael@0: exports.EPSILON = EPSILON; michael@0: michael@0: const PI_OVER_180 = Math.PI / 180; michael@0: const INV_PI_OVER_180 = 180 / Math.PI; michael@0: const FIFTEEN_OVER_225 = 15 / 225; michael@0: const ONE_OVER_255 = 1 / 255; michael@0: michael@0: /** michael@0: * vec3 - 3 Dimensional Vector. michael@0: */ michael@0: let vec3 = { michael@0: michael@0: /** michael@0: * Creates a new instance of a vec3 using the Float32Array type. michael@0: * Any array containing at least 3 numeric elements can serve as a vec3. michael@0: * michael@0: * @param {Array} aVec michael@0: * optional, vec3 containing values to initialize with michael@0: * michael@0: * @return {Array} a new instance of a vec3 michael@0: */ michael@0: create: function V3_create(aVec) michael@0: { michael@0: let dest = new Float32Array(3); michael@0: michael@0: if (aVec) { michael@0: vec3.set(aVec, dest); michael@0: } else { michael@0: vec3.zero(dest); michael@0: } michael@0: return dest; michael@0: }, michael@0: michael@0: /** michael@0: * Copies the values of one vec3 to another. michael@0: * michael@0: * @param {Array} aVec michael@0: * vec3 containing values to copy michael@0: * @param {Array} aDest michael@0: * vec3 receiving copied values michael@0: * michael@0: * @return {Array} the destination vec3 receiving copied values michael@0: */ michael@0: set: function V3_set(aVec, aDest) michael@0: { michael@0: aDest[0] = aVec[0]; michael@0: aDest[1] = aVec[1]; michael@0: aDest[2] = aVec[2] || 0; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Sets a vec3 to an zero vector. michael@0: * michael@0: * @param {Array} aDest michael@0: * vec3 to set michael@0: * michael@0: * @return {Array} the same vector michael@0: */ michael@0: zero: function V3_zero(aDest) michael@0: { michael@0: aDest[0] = 0; michael@0: aDest[1] = 0; michael@0: aDest[2] = 0; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Performs a vector addition. michael@0: * michael@0: * @param {Array} aVec michael@0: * vec3, first operand michael@0: * @param {Array} aVec2 michael@0: * vec3, second operand michael@0: * @param {Array} aDest michael@0: * optional, vec3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec3 if specified, first operand otherwise michael@0: */ michael@0: add: function V3_add(aVec, aVec2, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: aDest[0] = aVec[0] + aVec2[0]; michael@0: aDest[1] = aVec[1] + aVec2[1]; michael@0: aDest[2] = aVec[2] + aVec2[2]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Performs a vector subtraction. michael@0: * michael@0: * @param {Array} aVec michael@0: * vec3, first operand michael@0: * @param {Array} aVec2 michael@0: * vec3, second operand michael@0: * @param {Array} aDest michael@0: * optional, vec3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec3 if specified, first operand otherwise michael@0: */ michael@0: subtract: function V3_subtract(aVec, aVec2, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: aDest[0] = aVec[0] - aVec2[0]; michael@0: aDest[1] = aVec[1] - aVec2[1]; michael@0: aDest[2] = aVec[2] - aVec2[2]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Negates the components of a vec3. michael@0: * michael@0: * @param {Array} aVec michael@0: * vec3 to negate michael@0: * @param {Array} aDest michael@0: * optional, vec3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec3 if specified, first operand otherwise michael@0: */ michael@0: negate: function V3_negate(aVec, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: aDest[0] = -aVec[0]; michael@0: aDest[1] = -aVec[1]; michael@0: aDest[2] = -aVec[2]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Multiplies the components of a vec3 by a scalar value. michael@0: * michael@0: * @param {Array} aVec michael@0: * vec3 to scale michael@0: * @param {Number} aVal michael@0: * numeric value to scale by michael@0: * @param {Array} aDest michael@0: * optional, vec3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec3 if specified, first operand otherwise michael@0: */ michael@0: scale: function V3_scale(aVec, aVal, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: aDest[0] = aVec[0] * aVal; michael@0: aDest[1] = aVec[1] * aVal; michael@0: aDest[2] = aVec[2] * aVal; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Generates a unit vector of the same direction as the provided vec3. michael@0: * If vector length is 0, returns [0, 0, 0]. michael@0: * michael@0: * @param {Array} aVec michael@0: * vec3 to normalize michael@0: * @param {Array} aDest michael@0: * optional, vec3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec3 if specified, first operand otherwise michael@0: */ michael@0: normalize: function V3_normalize(aVec, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: let x = aVec[0]; michael@0: let y = aVec[1]; michael@0: let z = aVec[2]; michael@0: let len = Math.sqrt(x * x + y * y + z * z); michael@0: michael@0: if (Math.abs(len) < EPSILON) { michael@0: aDest[0] = 0; michael@0: aDest[1] = 0; michael@0: aDest[2] = 0; michael@0: return aDest; michael@0: } michael@0: michael@0: len = 1 / len; michael@0: aDest[0] = x * len; michael@0: aDest[1] = y * len; michael@0: aDest[2] = z * len; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Generates the cross product of two vectors. michael@0: * michael@0: * @param {Array} aVec michael@0: * vec3, first operand michael@0: * @param {Array} aVec2 michael@0: * vec3, second operand michael@0: * @param {Array} aDest michael@0: * optional, vec3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec3 if specified, first operand otherwise michael@0: */ michael@0: cross: function V3_cross(aVec, aVec2, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: let x = aVec[0]; michael@0: let y = aVec[1]; michael@0: let z = aVec[2]; michael@0: let x2 = aVec2[0]; michael@0: let y2 = aVec2[1]; michael@0: let z2 = aVec2[2]; michael@0: michael@0: aDest[0] = y * z2 - z * y2; michael@0: aDest[1] = z * x2 - x * z2; michael@0: aDest[2] = x * y2 - y * x2; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Caclulate the dot product of two vectors. michael@0: * michael@0: * @param {Array} aVec michael@0: * vec3, first operand michael@0: * @param {Array} aVec2 michael@0: * vec3, second operand michael@0: * michael@0: * @return {Array} dot product of the first and second operand michael@0: */ michael@0: dot: function V3_dot(aVec, aVec2) michael@0: { michael@0: return aVec[0] * aVec2[0] + aVec[1] * aVec2[1] + aVec[2] * aVec2[2]; michael@0: }, michael@0: michael@0: /** michael@0: * Caclulate the length of a vec3. michael@0: * michael@0: * @param {Array} aVec michael@0: * vec3 to calculate length of michael@0: * michael@0: * @return {Array} length of the vec3 michael@0: */ michael@0: length: function V3_length(aVec) michael@0: { michael@0: let x = aVec[0]; michael@0: let y = aVec[1]; michael@0: let z = aVec[2]; michael@0: michael@0: return Math.sqrt(x * x + y * y + z * z); michael@0: }, michael@0: michael@0: /** michael@0: * Generates a unit vector pointing from one vector to another. michael@0: * michael@0: * @param {Array} aVec michael@0: * origin vec3 michael@0: * @param {Array} aVec2 michael@0: * vec3 to point to michael@0: * @param {Array} aDest michael@0: * optional, vec3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec3 if specified, first operand otherwise michael@0: */ michael@0: direction: function V3_direction(aVec, aVec2, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: let x = aVec[0] - aVec2[0]; michael@0: let y = aVec[1] - aVec2[1]; michael@0: let z = aVec[2] - aVec2[2]; michael@0: let len = Math.sqrt(x * x + y * y + z * z); michael@0: michael@0: if (Math.abs(len) < EPSILON) { michael@0: aDest[0] = 0; michael@0: aDest[1] = 0; michael@0: aDest[2] = 0; michael@0: return aDest; michael@0: } michael@0: michael@0: len = 1 / len; michael@0: aDest[0] = x * len; michael@0: aDest[1] = y * len; michael@0: aDest[2] = z * len; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Performs a linear interpolation between two vec3. michael@0: * michael@0: * @param {Array} aVec michael@0: * first vector michael@0: * @param {Array} aVec2 michael@0: * second vector michael@0: * @param {Number} aLerp michael@0: * interpolation amount between the two inputs michael@0: * @param {Array} aDest michael@0: * optional, vec3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec3 if specified, first operand otherwise michael@0: */ michael@0: lerp: function V3_lerp(aVec, aVec2, aLerp, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: aDest[0] = aVec[0] + aLerp * (aVec2[0] - aVec[0]); michael@0: aDest[1] = aVec[1] + aLerp * (aVec2[1] - aVec[1]); michael@0: aDest[2] = aVec[2] + aLerp * (aVec2[2] - aVec[2]); michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Projects a 3D point on a 2D screen plane. michael@0: * michael@0: * @param {Array} aP michael@0: * the [x, y, z] coordinates of the point to project michael@0: * @param {Array} aViewport michael@0: * the viewport [x, y, width, height] coordinates michael@0: * @param {Array} aMvMatrix michael@0: * the model view matrix michael@0: * @param {Array} aProjMatrix michael@0: * the projection matrix michael@0: * @param {Array} aDest michael@0: * optional parameter, the array to write the values to michael@0: * michael@0: * @return {Array} the projected coordinates michael@0: */ michael@0: project: function V3_project(aP, aViewport, aMvMatrix, aProjMatrix, aDest) michael@0: { michael@0: /*jshint undef: false */ michael@0: michael@0: let mvpMatrix = new Float32Array(16); michael@0: let coordinates = new Float32Array(4); michael@0: michael@0: // compute the perspective * model view matrix michael@0: mat4.multiply(aProjMatrix, aMvMatrix, mvpMatrix); michael@0: michael@0: // now transform that vector into homogenous coordinates michael@0: coordinates[0] = aP[0]; michael@0: coordinates[1] = aP[1]; michael@0: coordinates[2] = aP[2]; michael@0: coordinates[3] = 1; michael@0: mat4.multiplyVec4(mvpMatrix, coordinates); michael@0: michael@0: // transform the homogenous coordinates into screen space michael@0: coordinates[0] /= coordinates[3]; michael@0: coordinates[0] *= aViewport[2] * 0.5; michael@0: coordinates[0] += aViewport[2] * 0.5; michael@0: coordinates[1] /= coordinates[3]; michael@0: coordinates[1] *= -aViewport[3] * 0.5; michael@0: coordinates[1] += aViewport[3] * 0.5; michael@0: coordinates[2] = 0; michael@0: michael@0: if (!aDest) { michael@0: vec3.set(coordinates, aP); michael@0: } else { michael@0: vec3.set(coordinates, aDest); michael@0: } michael@0: return coordinates; michael@0: }, michael@0: michael@0: /** michael@0: * Unprojects a 2D point to 3D space. michael@0: * michael@0: * @param {Array} aP michael@0: * the [x, y, z] coordinates of the point to unproject; michael@0: * the z value should range between 0 and 1, as clipping plane michael@0: * @param {Array} aViewport michael@0: * the viewport [x, y, width, height] coordinates michael@0: * @param {Array} aMvMatrix michael@0: * the model view matrix michael@0: * @param {Array} aProjMatrix michael@0: * the projection matrix michael@0: * @param {Array} aDest michael@0: * optional parameter, the array to write the values to michael@0: * michael@0: * @return {Array} the unprojected coordinates michael@0: */ michael@0: unproject: function V3_unproject( michael@0: aP, aViewport, aMvMatrix, aProjMatrix, aDest) michael@0: { michael@0: /*jshint undef: false */ michael@0: michael@0: let mvpMatrix = new Float32Array(16); michael@0: let coordinates = new Float32Array(4); michael@0: michael@0: // compute the inverse of the perspective * model view matrix michael@0: mat4.multiply(aProjMatrix, aMvMatrix, mvpMatrix); michael@0: mat4.inverse(mvpMatrix); michael@0: michael@0: // transformation of normalized coordinates (-1 to 1) michael@0: coordinates[0] = +((aP[0] - aViewport[0]) / aViewport[2] * 2 - 1); michael@0: coordinates[1] = -((aP[1] - aViewport[1]) / aViewport[3] * 2 - 1); michael@0: coordinates[2] = 2 * aP[2] - 1; michael@0: coordinates[3] = 1; michael@0: michael@0: // now transform that vector into space coordinates michael@0: mat4.multiplyVec4(mvpMatrix, coordinates); michael@0: michael@0: // invert to normalize x, y, and z values michael@0: coordinates[3] = 1 / coordinates[3]; michael@0: coordinates[0] *= coordinates[3]; michael@0: coordinates[1] *= coordinates[3]; michael@0: coordinates[2] *= coordinates[3]; michael@0: michael@0: if (!aDest) { michael@0: vec3.set(coordinates, aP); michael@0: } else { michael@0: vec3.set(coordinates, aDest); michael@0: } michael@0: return coordinates; michael@0: }, michael@0: michael@0: /** michael@0: * Create a ray between two points using the current model view & projection michael@0: * matrices. This is useful when creating a ray destined for 3D picking. michael@0: * michael@0: * @param {Array} aP0 michael@0: * the [x, y, z] coordinates of the first point michael@0: * @param {Array} aP1 michael@0: * the [x, y, z] coordinates of the second point michael@0: * @param {Array} aViewport michael@0: * the viewport [x, y, width, height] coordinates michael@0: * @param {Array} aMvMatrix michael@0: * the model view matrix michael@0: * @param {Array} aProjMatrix michael@0: * the projection matrix michael@0: * michael@0: * @return {Object} a ray object containing the direction vector between michael@0: * the two unprojected points, the position and the lookAt michael@0: */ michael@0: createRay: function V3_createRay(aP0, aP1, aViewport, aMvMatrix, aProjMatrix) michael@0: { michael@0: // unproject the two points michael@0: vec3.unproject(aP0, aViewport, aMvMatrix, aProjMatrix, aP0); michael@0: vec3.unproject(aP1, aViewport, aMvMatrix, aProjMatrix, aP1); michael@0: michael@0: return { michael@0: origin: aP0, michael@0: direction: vec3.normalize(vec3.subtract(aP1, aP0)) michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Returns a string representation of a vector. michael@0: * michael@0: * @param {Array} aVec michael@0: * vec3 to represent as a string michael@0: * michael@0: * @return {String} representation of the vector michael@0: */ michael@0: str: function V3_str(aVec) michael@0: { michael@0: return '[' + aVec[0] + ", " + aVec[1] + ", " + aVec[2] + ']'; michael@0: } michael@0: }; michael@0: michael@0: exports.vec3 = vec3; michael@0: michael@0: /** michael@0: * mat3 - 3x3 Matrix. michael@0: */ michael@0: let mat3 = { michael@0: michael@0: /** michael@0: * Creates a new instance of a mat3 using the Float32Array array type. michael@0: * Any array containing at least 9 numeric elements can serve as a mat3. michael@0: * michael@0: * @param {Array} aMat michael@0: * optional, mat3 containing values to initialize with michael@0: * michael@0: * @return {Array} a new instance of a mat3 michael@0: */ michael@0: create: function M3_create(aMat) michael@0: { michael@0: let dest = new Float32Array(9); michael@0: michael@0: if (aMat) { michael@0: mat3.set(aMat, dest); michael@0: } else { michael@0: mat3.identity(dest); michael@0: } michael@0: return dest; michael@0: }, michael@0: michael@0: /** michael@0: * Copies the values of one mat3 to another. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat3 containing values to copy michael@0: * @param {Array} aDest michael@0: * mat3 receiving copied values michael@0: * michael@0: * @return {Array} the destination mat3 receiving copied values michael@0: */ michael@0: set: function M3_set(aMat, aDest) michael@0: { michael@0: aDest[0] = aMat[0]; michael@0: aDest[1] = aMat[1]; michael@0: aDest[2] = aMat[2]; michael@0: aDest[3] = aMat[3]; michael@0: aDest[4] = aMat[4]; michael@0: aDest[5] = aMat[5]; michael@0: aDest[6] = aMat[6]; michael@0: aDest[7] = aMat[7]; michael@0: aDest[8] = aMat[8]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Sets a mat3 to an identity matrix. michael@0: * michael@0: * @param {Array} aDest michael@0: * mat3 to set michael@0: * michael@0: * @return {Array} the same matrix michael@0: */ michael@0: identity: function M3_identity(aDest) michael@0: { michael@0: aDest[0] = 1; michael@0: aDest[1] = 0; michael@0: aDest[2] = 0; michael@0: aDest[3] = 0; michael@0: aDest[4] = 1; michael@0: aDest[5] = 0; michael@0: aDest[6] = 0; michael@0: aDest[7] = 0; michael@0: aDest[8] = 1; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Transposes a mat3 (flips the values over the diagonal). michael@0: * michael@0: * @param {Array} aMat michael@0: * mat3 to transpose michael@0: * @param {Array} aDest michael@0: * optional, mat3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat3 if specified, first operand otherwise michael@0: */ michael@0: transpose: function M3_transpose(aMat, aDest) michael@0: { michael@0: if (!aDest || aMat === aDest) { michael@0: let a01 = aMat[1]; michael@0: let a02 = aMat[2]; michael@0: let a12 = aMat[5]; michael@0: michael@0: aMat[1] = aMat[3]; michael@0: aMat[2] = aMat[6]; michael@0: aMat[3] = a01; michael@0: aMat[5] = aMat[7]; michael@0: aMat[6] = a02; michael@0: aMat[7] = a12; michael@0: return aMat; michael@0: } michael@0: michael@0: aDest[0] = aMat[0]; michael@0: aDest[1] = aMat[3]; michael@0: aDest[2] = aMat[6]; michael@0: aDest[3] = aMat[1]; michael@0: aDest[4] = aMat[4]; michael@0: aDest[5] = aMat[7]; michael@0: aDest[6] = aMat[2]; michael@0: aDest[7] = aMat[5]; michael@0: aDest[8] = aMat[8]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Copies the elements of a mat3 into the upper 3x3 elements of a mat4. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat3 containing values to copy michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat3 if specified, first operand otherwise michael@0: */ michael@0: toMat4: function M3_toMat4(aMat, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(16); michael@0: } michael@0: michael@0: aDest[0] = aMat[0]; michael@0: aDest[1] = aMat[1]; michael@0: aDest[2] = aMat[2]; michael@0: aDest[3] = 0; michael@0: aDest[4] = aMat[3]; michael@0: aDest[5] = aMat[4]; michael@0: aDest[6] = aMat[5]; michael@0: aDest[7] = 0; michael@0: aDest[8] = aMat[6]; michael@0: aDest[9] = aMat[7]; michael@0: aDest[10] = aMat[8]; michael@0: aDest[11] = 0; michael@0: aDest[12] = 0; michael@0: aDest[13] = 0; michael@0: aDest[14] = 0; michael@0: aDest[15] = 1; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Returns a string representation of a 3x3 matrix. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat3 to represent as a string michael@0: * michael@0: * @return {String} representation of the matrix michael@0: */ michael@0: str: function M3_str(aMat) michael@0: { michael@0: return "[" + aMat[0] + ", " + aMat[1] + ", " + aMat[2] + michael@0: ", " + aMat[3] + ", " + aMat[4] + ", " + aMat[5] + michael@0: ", " + aMat[6] + ", " + aMat[7] + ", " + aMat[8] + "]"; michael@0: } michael@0: }; michael@0: michael@0: exports.mat3 = mat3; michael@0: michael@0: /** michael@0: * mat4 - 4x4 Matrix. michael@0: */ michael@0: let mat4 = { michael@0: michael@0: /** michael@0: * Creates a new instance of a mat4 using the default Float32Array type. michael@0: * Any array containing at least 16 numeric elements can serve as a mat4. michael@0: * michael@0: * @param {Array} aMat michael@0: * optional, mat4 containing values to initialize with michael@0: * michael@0: * @return {Array} a new instance of a mat4 michael@0: */ michael@0: create: function M4_create(aMat) michael@0: { michael@0: let dest = new Float32Array(16); michael@0: michael@0: if (aMat) { michael@0: mat4.set(aMat, dest); michael@0: } else { michael@0: mat4.identity(dest); michael@0: } michael@0: return dest; michael@0: }, michael@0: michael@0: /** michael@0: * Copies the values of one mat4 to another michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 containing values to copy michael@0: * @param {Array} aDest michael@0: * mat4 receiving copied values michael@0: * michael@0: * @return {Array} the destination mat4 receiving copied values michael@0: */ michael@0: set: function M4_set(aMat, aDest) michael@0: { michael@0: aDest[0] = aMat[0]; michael@0: aDest[1] = aMat[1]; michael@0: aDest[2] = aMat[2]; michael@0: aDest[3] = aMat[3]; michael@0: aDest[4] = aMat[4]; michael@0: aDest[5] = aMat[5]; michael@0: aDest[6] = aMat[6]; michael@0: aDest[7] = aMat[7]; michael@0: aDest[8] = aMat[8]; michael@0: aDest[9] = aMat[9]; michael@0: aDest[10] = aMat[10]; michael@0: aDest[11] = aMat[11]; michael@0: aDest[12] = aMat[12]; michael@0: aDest[13] = aMat[13]; michael@0: aDest[14] = aMat[14]; michael@0: aDest[15] = aMat[15]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Sets a mat4 to an identity matrix. michael@0: * michael@0: * @param {Array} aDest michael@0: * mat4 to set michael@0: * michael@0: * @return {Array} the same matrix michael@0: */ michael@0: identity: function M4_identity(aDest) michael@0: { michael@0: aDest[0] = 1; michael@0: aDest[1] = 0; michael@0: aDest[2] = 0; michael@0: aDest[3] = 0; michael@0: aDest[4] = 0; michael@0: aDest[5] = 1; michael@0: aDest[6] = 0; michael@0: aDest[7] = 0; michael@0: aDest[8] = 0; michael@0: aDest[9] = 0; michael@0: aDest[10] = 1; michael@0: aDest[11] = 0; michael@0: aDest[12] = 0; michael@0: aDest[13] = 0; michael@0: aDest[14] = 0; michael@0: aDest[15] = 1; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Transposes a mat4 (flips the values over the diagonal). michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to transpose michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: transpose: function M4_transpose(aMat, aDest) michael@0: { michael@0: if (!aDest || aMat === aDest) { michael@0: let a01 = aMat[1]; michael@0: let a02 = aMat[2]; michael@0: let a03 = aMat[3]; michael@0: let a12 = aMat[6]; michael@0: let a13 = aMat[7]; michael@0: let a23 = aMat[11]; michael@0: michael@0: aMat[1] = aMat[4]; michael@0: aMat[2] = aMat[8]; michael@0: aMat[3] = aMat[12]; michael@0: aMat[4] = a01; michael@0: aMat[6] = aMat[9]; michael@0: aMat[7] = aMat[13]; michael@0: aMat[8] = a02; michael@0: aMat[9] = a12; michael@0: aMat[11] = aMat[14]; michael@0: aMat[12] = a03; michael@0: aMat[13] = a13; michael@0: aMat[14] = a23; michael@0: return aMat; michael@0: } michael@0: michael@0: aDest[0] = aMat[0]; michael@0: aDest[1] = aMat[4]; michael@0: aDest[2] = aMat[8]; michael@0: aDest[3] = aMat[12]; michael@0: aDest[4] = aMat[1]; michael@0: aDest[5] = aMat[5]; michael@0: aDest[6] = aMat[9]; michael@0: aDest[7] = aMat[13]; michael@0: aDest[8] = aMat[2]; michael@0: aDest[9] = aMat[6]; michael@0: aDest[10] = aMat[10]; michael@0: aDest[11] = aMat[14]; michael@0: aDest[12] = aMat[3]; michael@0: aDest[13] = aMat[7]; michael@0: aDest[14] = aMat[11]; michael@0: aDest[15] = aMat[15]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Calculate the determinant of a mat4. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to calculate determinant of michael@0: * michael@0: * @return {Number} determinant of the matrix michael@0: */ michael@0: determinant: function M4_determinant(mat) michael@0: { michael@0: let a00 = mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3]; michael@0: let a10 = mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7]; michael@0: let a20 = mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11]; michael@0: let a30 = mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15]; michael@0: michael@0: return a30 * a21 * a12 * a03 - a20 * a31 * a12 * a03 - michael@0: a30 * a11 * a22 * a03 + a10 * a31 * a22 * a03 + michael@0: a20 * a11 * a32 * a03 - a10 * a21 * a32 * a03 - michael@0: a30 * a21 * a02 * a13 + a20 * a31 * a02 * a13 + michael@0: a30 * a01 * a22 * a13 - a00 * a31 * a22 * a13 - michael@0: a20 * a01 * a32 * a13 + a00 * a21 * a32 * a13 + michael@0: a30 * a11 * a02 * a23 - a10 * a31 * a02 * a23 - michael@0: a30 * a01 * a12 * a23 + a00 * a31 * a12 * a23 + michael@0: a10 * a01 * a32 * a23 - a00 * a11 * a32 * a23 - michael@0: a20 * a11 * a02 * a33 + a10 * a21 * a02 * a33 + michael@0: a20 * a01 * a12 * a33 - a00 * a21 * a12 * a33 - michael@0: a10 * a01 * a22 * a33 + a00 * a11 * a22 * a33; michael@0: }, michael@0: michael@0: /** michael@0: * Calculate the inverse of a mat4. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to calculate inverse of michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: inverse: function M4_inverse(aMat, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aMat; michael@0: } michael@0: michael@0: let a00 = aMat[0], a01 = aMat[1], a02 = aMat[2], a03 = aMat[3]; michael@0: let a10 = aMat[4], a11 = aMat[5], a12 = aMat[6], a13 = aMat[7]; michael@0: let a20 = aMat[8], a21 = aMat[9], a22 = aMat[10], a23 = aMat[11]; michael@0: let a30 = aMat[12], a31 = aMat[13], a32 = aMat[14], a33 = aMat[15]; michael@0: michael@0: let b00 = a00 * a11 - a01 * a10; michael@0: let b01 = a00 * a12 - a02 * a10; michael@0: let b02 = a00 * a13 - a03 * a10; michael@0: let b03 = a01 * a12 - a02 * a11; michael@0: let b04 = a01 * a13 - a03 * a11; michael@0: let b05 = a02 * a13 - a03 * a12; michael@0: let b06 = a20 * a31 - a21 * a30; michael@0: let b07 = a20 * a32 - a22 * a30; michael@0: let b08 = a20 * a33 - a23 * a30; michael@0: let b09 = a21 * a32 - a22 * a31; michael@0: let b10 = a21 * a33 - a23 * a31; michael@0: let b11 = a22 * a33 - a23 * a32; michael@0: let id = 1 / ((b00 * b11 - b01 * b10 + b02 * b09 + michael@0: b03 * b08 - b04 * b07 + b05 * b06) || EPSILON); michael@0: michael@0: aDest[0] = ( a11 * b11 - a12 * b10 + a13 * b09) * id; michael@0: aDest[1] = (-a01 * b11 + a02 * b10 - a03 * b09) * id; michael@0: aDest[2] = ( a31 * b05 - a32 * b04 + a33 * b03) * id; michael@0: aDest[3] = (-a21 * b05 + a22 * b04 - a23 * b03) * id; michael@0: aDest[4] = (-a10 * b11 + a12 * b08 - a13 * b07) * id; michael@0: aDest[5] = ( a00 * b11 - a02 * b08 + a03 * b07) * id; michael@0: aDest[6] = (-a30 * b05 + a32 * b02 - a33 * b01) * id; michael@0: aDest[7] = ( a20 * b05 - a22 * b02 + a23 * b01) * id; michael@0: aDest[8] = ( a10 * b10 - a11 * b08 + a13 * b06) * id; michael@0: aDest[9] = (-a00 * b10 + a01 * b08 - a03 * b06) * id; michael@0: aDest[10] = ( a30 * b04 - a31 * b02 + a33 * b00) * id; michael@0: aDest[11] = (-a20 * b04 + a21 * b02 - a23 * b00) * id; michael@0: aDest[12] = (-a10 * b09 + a11 * b07 - a12 * b06) * id; michael@0: aDest[13] = ( a00 * b09 - a01 * b07 + a02 * b06) * id; michael@0: aDest[14] = (-a30 * b03 + a31 * b01 - a32 * b00) * id; michael@0: aDest[15] = ( a20 * b03 - a21 * b01 + a22 * b00) * id; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Copies the upper 3x3 elements of a mat4 into another mat4. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 containing values to copy michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: toRotationMat: function M4_toRotationMat(aMat, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(16); michael@0: } michael@0: michael@0: aDest[0] = aMat[0]; michael@0: aDest[1] = aMat[1]; michael@0: aDest[2] = aMat[2]; michael@0: aDest[3] = aMat[3]; michael@0: aDest[4] = aMat[4]; michael@0: aDest[5] = aMat[5]; michael@0: aDest[6] = aMat[6]; michael@0: aDest[7] = aMat[7]; michael@0: aDest[8] = aMat[8]; michael@0: aDest[9] = aMat[9]; michael@0: aDest[10] = aMat[10]; michael@0: aDest[11] = aMat[11]; michael@0: aDest[12] = 0; michael@0: aDest[13] = 0; michael@0: aDest[14] = 0; michael@0: aDest[15] = 1; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Copies the upper 3x3 elements of a mat4 into a mat3. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 containing values to copy michael@0: * @param {Array} aDest michael@0: * optional, mat3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat3 if specified, first operand otherwise michael@0: */ michael@0: toMat3: function M4_toMat3(aMat, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(9); michael@0: } michael@0: michael@0: aDest[0] = aMat[0]; michael@0: aDest[1] = aMat[1]; michael@0: aDest[2] = aMat[2]; michael@0: aDest[3] = aMat[4]; michael@0: aDest[4] = aMat[5]; michael@0: aDest[5] = aMat[6]; michael@0: aDest[6] = aMat[8]; michael@0: aDest[7] = aMat[9]; michael@0: aDest[8] = aMat[10]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Calculate the inverse of the upper 3x3 elements of a mat4 and copies michael@0: * the result into a mat3. The resulting matrix is useful for calculating michael@0: * transformed normals. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 containing values to invert and copy michael@0: * @param {Array} aDest michael@0: * optional, mat3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat3 if specified, first operand otherwise michael@0: */ michael@0: toInverseMat3: function M4_toInverseMat3(aMat, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(9); michael@0: } michael@0: michael@0: let a00 = aMat[0], a01 = aMat[1], a02 = aMat[2]; michael@0: let a10 = aMat[4], a11 = aMat[5], a12 = aMat[6]; michael@0: let a20 = aMat[8], a21 = aMat[9], a22 = aMat[10]; michael@0: michael@0: let b01 = a22 * a11 - a12 * a21; michael@0: let b11 = -a22 * a10 + a12 * a20; michael@0: let b21 = a21 * a10 - a11 * a20; michael@0: let id = 1 / ((a00 * b01 + a01 * b11 + a02 * b21) || EPSILON); michael@0: michael@0: aDest[0] = b01 * id; michael@0: aDest[1] = (-a22 * a01 + a02 * a21) * id; michael@0: aDest[2] = ( a12 * a01 - a02 * a11) * id; michael@0: aDest[3] = b11 * id; michael@0: aDest[4] = ( a22 * a00 - a02 * a20) * id; michael@0: aDest[5] = (-a12 * a00 + a02 * a10) * id; michael@0: aDest[6] = b21 * id; michael@0: aDest[7] = (-a21 * a00 + a01 * a20) * id; michael@0: aDest[8] = ( a11 * a00 - a01 * a10) * id; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Performs a matrix multiplication. michael@0: * michael@0: * @param {Array} aMat michael@0: * first operand michael@0: * @param {Array} aMat2 michael@0: * second operand michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: multiply: function M4_multiply(aMat, aMat2, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aMat; michael@0: } michael@0: michael@0: let a00 = aMat[0], a01 = aMat[1], a02 = aMat[2], a03 = aMat[3]; michael@0: let a10 = aMat[4], a11 = aMat[5], a12 = aMat[6], a13 = aMat[7]; michael@0: let a20 = aMat[8], a21 = aMat[9], a22 = aMat[10], a23 = aMat[11]; michael@0: let a30 = aMat[12], a31 = aMat[13], a32 = aMat[14], a33 = aMat[15]; michael@0: michael@0: let b00 = aMat2[0], b01 = aMat2[1], b02 = aMat2[2], b03 = aMat2[3]; michael@0: let b10 = aMat2[4], b11 = aMat2[5], b12 = aMat2[6], b13 = aMat2[7]; michael@0: let b20 = aMat2[8], b21 = aMat2[9], b22 = aMat2[10], b23 = aMat2[11]; michael@0: let b30 = aMat2[12], b31 = aMat2[13], b32 = aMat2[14], b33 = aMat2[15]; michael@0: michael@0: aDest[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30; michael@0: aDest[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31; michael@0: aDest[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32; michael@0: aDest[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33; michael@0: aDest[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30; michael@0: aDest[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31; michael@0: aDest[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32; michael@0: aDest[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33; michael@0: aDest[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30; michael@0: aDest[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31; michael@0: aDest[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32; michael@0: aDest[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33; michael@0: aDest[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30; michael@0: aDest[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31; michael@0: aDest[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32; michael@0: aDest[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Transforms a vec3 with the given matrix. michael@0: * 4th vector component is implicitly 1. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to transform the vector with michael@0: * @param {Array} aVec michael@0: * vec3 to transform michael@0: * @param {Array} aDest michael@0: * optional, vec3 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec3 if specified, aVec operand otherwise michael@0: */ michael@0: multiplyVec3: function M4_multiplyVec3(aMat, aVec, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: let x = aVec[0]; michael@0: let y = aVec[1]; michael@0: let z = aVec[2]; michael@0: michael@0: aDest[0] = aMat[0] * x + aMat[4] * y + aMat[8] * z + aMat[12]; michael@0: aDest[1] = aMat[1] * x + aMat[5] * y + aMat[9] * z + aMat[13]; michael@0: aDest[2] = aMat[2] * x + aMat[6] * y + aMat[10] * z + aMat[14]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Transforms a vec4 with the given matrix. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to transform the vector with michael@0: * @param {Array} aVec michael@0: * vec4 to transform michael@0: * @param {Array} aDest michael@0: * optional, vec4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec4 if specified, vec4 operand otherwise michael@0: */ michael@0: multiplyVec4: function M4_multiplyVec4(aMat, aVec, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: let x = aVec[0]; michael@0: let y = aVec[1]; michael@0: let z = aVec[2]; michael@0: let w = aVec[3]; michael@0: michael@0: aDest[0] = aMat[0] * x + aMat[4] * y + aMat[8] * z + aMat[12] * w; michael@0: aDest[1] = aMat[1] * x + aMat[5] * y + aMat[9] * z + aMat[13] * w; michael@0: aDest[2] = aMat[2] * x + aMat[6] * y + aMat[10] * z + aMat[14] * w; michael@0: aDest[3] = aMat[3] * x + aMat[7] * y + aMat[11] * z + aMat[15] * w; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Translates a matrix by the given vector. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to translate michael@0: * @param {Array} aVec michael@0: * vec3 specifying the translation michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: translate: function M4_translate(aMat, aVec, aDest) michael@0: { michael@0: let x = aVec[0]; michael@0: let y = aVec[1]; michael@0: let z = aVec[2]; michael@0: michael@0: if (!aDest || aMat === aDest) { michael@0: aMat[12] = aMat[0] * x + aMat[4] * y + aMat[8] * z + aMat[12]; michael@0: aMat[13] = aMat[1] * x + aMat[5] * y + aMat[9] * z + aMat[13]; michael@0: aMat[14] = aMat[2] * x + aMat[6] * y + aMat[10] * z + aMat[14]; michael@0: aMat[15] = aMat[3] * x + aMat[7] * y + aMat[11] * z + aMat[15]; michael@0: return aMat; michael@0: } michael@0: michael@0: let a00 = aMat[0], a01 = aMat[1], a02 = aMat[2], a03 = aMat[3]; michael@0: let a10 = aMat[4], a11 = aMat[5], a12 = aMat[6], a13 = aMat[7]; michael@0: let a20 = aMat[8], a21 = aMat[9], a22 = aMat[10], a23 = aMat[11]; michael@0: michael@0: aDest[0] = a00; michael@0: aDest[1] = a01; michael@0: aDest[2] = a02; michael@0: aDest[3] = a03; michael@0: aDest[4] = a10; michael@0: aDest[5] = a11; michael@0: aDest[6] = a12; michael@0: aDest[7] = a13; michael@0: aDest[8] = a20; michael@0: aDest[9] = a21; michael@0: aDest[10] = a22; michael@0: aDest[11] = a23; michael@0: aDest[12] = a00 * x + a10 * y + a20 * z + aMat[12]; michael@0: aDest[13] = a01 * x + a11 * y + a21 * z + aMat[13]; michael@0: aDest[14] = a02 * x + a12 * y + a22 * z + aMat[14]; michael@0: aDest[15] = a03 * x + a13 * y + a23 * z + aMat[15]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Scales a matrix by the given vector. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to translate michael@0: * @param {Array} aVec michael@0: * vec3 specifying the scale on each axis michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: scale: function M4_scale(aMat, aVec, aDest) michael@0: { michael@0: let x = aVec[0]; michael@0: let y = aVec[1]; michael@0: let z = aVec[2]; michael@0: michael@0: if (!aDest || aMat === aDest) { michael@0: aMat[0] *= x; michael@0: aMat[1] *= x; michael@0: aMat[2] *= x; michael@0: aMat[3] *= x; michael@0: aMat[4] *= y; michael@0: aMat[5] *= y; michael@0: aMat[6] *= y; michael@0: aMat[7] *= y; michael@0: aMat[8] *= z; michael@0: aMat[9] *= z; michael@0: aMat[10] *= z; michael@0: aMat[11] *= z; michael@0: return aMat; michael@0: } michael@0: michael@0: aDest[0] = aMat[0] * x; michael@0: aDest[1] = aMat[1] * x; michael@0: aDest[2] = aMat[2] * x; michael@0: aDest[3] = aMat[3] * x; michael@0: aDest[4] = aMat[4] * y; michael@0: aDest[5] = aMat[5] * y; michael@0: aDest[6] = aMat[6] * y; michael@0: aDest[7] = aMat[7] * y; michael@0: aDest[8] = aMat[8] * z; michael@0: aDest[9] = aMat[9] * z; michael@0: aDest[10] = aMat[10] * z; michael@0: aDest[11] = aMat[11] * z; michael@0: aDest[12] = aMat[12]; michael@0: aDest[13] = aMat[13]; michael@0: aDest[14] = aMat[14]; michael@0: aDest[15] = aMat[15]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Rotates a matrix by the given angle around the specified axis. michael@0: * If rotating around a primary axis (x, y, z) one of the specialized michael@0: * rotation functions should be used instead for performance, michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to rotate michael@0: * @param {Number} aAngle michael@0: * the angle (in radians) to rotate michael@0: * @param {Array} aAxis michael@0: * vec3 representing the axis to rotate around michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: rotate: function M4_rotate(aMat, aAngle, aAxis, aDest) michael@0: { michael@0: let x = aAxis[0]; michael@0: let y = aAxis[1]; michael@0: let z = aAxis[2]; michael@0: let len = 1 / (Math.sqrt(x * x + y * y + z * z) || EPSILON); michael@0: michael@0: x *= len; michael@0: y *= len; michael@0: z *= len; michael@0: michael@0: let s = Math.sin(aAngle); michael@0: let c = Math.cos(aAngle); michael@0: let t = 1 - c; michael@0: michael@0: let a00 = aMat[0], a01 = aMat[1], a02 = aMat[2], a03 = aMat[3]; michael@0: let a10 = aMat[4], a11 = aMat[5], a12 = aMat[6], a13 = aMat[7]; michael@0: let a20 = aMat[8], a21 = aMat[9], a22 = aMat[10], a23 = aMat[11]; michael@0: michael@0: let b00 = x * x * t + c, b01 = y * x * t + z * s, b02 = z * x * t - y * s; michael@0: let b10 = x * y * t - z * s, b11 = y * y * t + c, b12 = z * y * t + x * s; michael@0: let b20 = x * z * t + y * s, b21 = y * z * t - x * s, b22 = z * z * t + c; michael@0: michael@0: if (!aDest) { michael@0: aDest = aMat; michael@0: } else if (aMat !== aDest) { michael@0: aDest[12] = aMat[12]; michael@0: aDest[13] = aMat[13]; michael@0: aDest[14] = aMat[14]; michael@0: aDest[15] = aMat[15]; michael@0: } michael@0: michael@0: aDest[0] = a00 * b00 + a10 * b01 + a20 * b02; michael@0: aDest[1] = a01 * b00 + a11 * b01 + a21 * b02; michael@0: aDest[2] = a02 * b00 + a12 * b01 + a22 * b02; michael@0: aDest[3] = a03 * b00 + a13 * b01 + a23 * b02; michael@0: aDest[4] = a00 * b10 + a10 * b11 + a20 * b12; michael@0: aDest[5] = a01 * b10 + a11 * b11 + a21 * b12; michael@0: aDest[6] = a02 * b10 + a12 * b11 + a22 * b12; michael@0: aDest[7] = a03 * b10 + a13 * b11 + a23 * b12; michael@0: aDest[8] = a00 * b20 + a10 * b21 + a20 * b22; michael@0: aDest[9] = a01 * b20 + a11 * b21 + a21 * b22; michael@0: aDest[10] = a02 * b20 + a12 * b21 + a22 * b22; michael@0: aDest[11] = a03 * b20 + a13 * b21 + a23 * b22; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Rotates a matrix by the given angle around the X axis. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to rotate michael@0: * @param {Number} aAngle michael@0: * the angle (in radians) to rotate michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: rotateX: function M4_rotateX(aMat, aAngle, aDest) michael@0: { michael@0: let s = Math.sin(aAngle); michael@0: let c = Math.cos(aAngle); michael@0: michael@0: let a10 = aMat[4], a11 = aMat[5], a12 = aMat[6], a13 = aMat[7]; michael@0: let a20 = aMat[8], a21 = aMat[9], a22 = aMat[10], a23 = aMat[11]; michael@0: michael@0: if (!aDest) { michael@0: aDest = aMat; michael@0: } else if (aMat !== aDest) { michael@0: aDest[0] = aMat[0]; michael@0: aDest[1] = aMat[1]; michael@0: aDest[2] = aMat[2]; michael@0: aDest[3] = aMat[3]; michael@0: aDest[12] = aMat[12]; michael@0: aDest[13] = aMat[13]; michael@0: aDest[14] = aMat[14]; michael@0: aDest[15] = aMat[15]; michael@0: } michael@0: michael@0: aDest[4] = a10 * c + a20 * s; michael@0: aDest[5] = a11 * c + a21 * s; michael@0: aDest[6] = a12 * c + a22 * s; michael@0: aDest[7] = a13 * c + a23 * s; michael@0: aDest[8] = a10 * -s + a20 * c; michael@0: aDest[9] = a11 * -s + a21 * c; michael@0: aDest[10] = a12 * -s + a22 * c; michael@0: aDest[11] = a13 * -s + a23 * c; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Rotates a matrix by the given angle around the Y axix. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to rotate michael@0: * @param {Number} aAngle michael@0: * the angle (in radians) to rotate michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: rotateY: function M4_rotateY(aMat, aAngle, aDest) michael@0: { michael@0: let s = Math.sin(aAngle); michael@0: let c = Math.cos(aAngle); michael@0: michael@0: let a00 = aMat[0], a01 = aMat[1], a02 = aMat[2], a03 = aMat[3]; michael@0: let a20 = aMat[8], a21 = aMat[9], a22 = aMat[10], a23 = aMat[11]; michael@0: michael@0: if (!aDest) { michael@0: aDest = aMat; michael@0: } else if (aMat !== aDest) { michael@0: aDest[4] = aMat[4]; michael@0: aDest[5] = aMat[5]; michael@0: aDest[6] = aMat[6]; michael@0: aDest[7] = aMat[7]; michael@0: aDest[12] = aMat[12]; michael@0: aDest[13] = aMat[13]; michael@0: aDest[14] = aMat[14]; michael@0: aDest[15] = aMat[15]; michael@0: } michael@0: michael@0: aDest[0] = a00 * c + a20 * -s; michael@0: aDest[1] = a01 * c + a21 * -s; michael@0: aDest[2] = a02 * c + a22 * -s; michael@0: aDest[3] = a03 * c + a23 * -s; michael@0: aDest[8] = a00 * s + a20 * c; michael@0: aDest[9] = a01 * s + a21 * c; michael@0: aDest[10] = a02 * s + a22 * c; michael@0: aDest[11] = a03 * s + a23 * c; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Rotates a matrix by the given angle around the Z axix. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to rotate michael@0: * @param {Number} aAngle michael@0: * the angle (in radians) to rotate michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: rotateZ: function M4_rotateZ(aMat, aAngle, aDest) michael@0: { michael@0: let s = Math.sin(aAngle); michael@0: let c = Math.cos(aAngle); michael@0: michael@0: let a00 = aMat[0], a01 = aMat[1], a02 = aMat[2], a03 = aMat[3]; michael@0: let a10 = aMat[4], a11 = aMat[5], a12 = aMat[6], a13 = aMat[7]; michael@0: michael@0: if (!aDest) { michael@0: aDest = aMat; michael@0: } else if (aMat !== aDest) { michael@0: aDest[8] = aMat[8]; michael@0: aDest[9] = aMat[9]; michael@0: aDest[10] = aMat[10]; michael@0: aDest[11] = aMat[11]; michael@0: aDest[12] = aMat[12]; michael@0: aDest[13] = aMat[13]; michael@0: aDest[14] = aMat[14]; michael@0: aDest[15] = aMat[15]; michael@0: } michael@0: michael@0: aDest[0] = a00 * c + a10 * s; michael@0: aDest[1] = a01 * c + a11 * s; michael@0: aDest[2] = a02 * c + a12 * s; michael@0: aDest[3] = a03 * c + a13 * s; michael@0: aDest[4] = a00 * -s + a10 * c; michael@0: aDest[5] = a01 * -s + a11 * c; michael@0: aDest[6] = a02 * -s + a12 * c; michael@0: aDest[7] = a03 * -s + a13 * c; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Generates a frustum matrix with the given bounds. michael@0: * michael@0: * @param {Number} aLeft michael@0: * scalar, left bound of the frustum michael@0: * @param {Number} aRight michael@0: * scalar, right bound of the frustum michael@0: * @param {Number} aBottom michael@0: * scalar, bottom bound of the frustum michael@0: * @param {Number} aTop michael@0: * scalar, top bound of the frustum michael@0: * @param {Number} aNear michael@0: * scalar, near bound of the frustum michael@0: * @param {Number} aFar michael@0: * scalar, far bound of the frustum michael@0: * @param {Array} aDest michael@0: * optional, mat4 frustum matrix will be written into michael@0: * if not specified result is written to a new mat4 michael@0: * michael@0: * @return {Array} the destination mat4 if specified, a new mat4 otherwise michael@0: */ michael@0: frustum: function M4_frustum( michael@0: aLeft, aRight, aBottom, aTop, aNear, aFar, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(16); michael@0: } michael@0: michael@0: let rl = (aRight - aLeft); michael@0: let tb = (aTop - aBottom); michael@0: let fn = (aFar - aNear); michael@0: michael@0: aDest[0] = (aNear * 2) / rl; michael@0: aDest[1] = 0; michael@0: aDest[2] = 0; michael@0: aDest[3] = 0; michael@0: aDest[4] = 0; michael@0: aDest[5] = (aNear * 2) / tb; michael@0: aDest[6] = 0; michael@0: aDest[7] = 0; michael@0: aDest[8] = (aRight + aLeft) / rl; michael@0: aDest[9] = (aTop + aBottom) / tb; michael@0: aDest[10] = -(aFar + aNear) / fn; michael@0: aDest[11] = -1; michael@0: aDest[12] = 0; michael@0: aDest[13] = 0; michael@0: aDest[14] = -(aFar * aNear * 2) / fn; michael@0: aDest[15] = 0; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Generates a perspective projection matrix with the given bounds. michael@0: * michael@0: * @param {Number} aFovy michael@0: * scalar, vertical field of view (degrees) michael@0: * @param {Number} aAspect michael@0: * scalar, aspect ratio (typically viewport width/height) michael@0: * @param {Number} aNear michael@0: * scalar, near bound of the frustum michael@0: * @param {Number} aFar michael@0: * scalar, far bound of the frustum michael@0: * @param {Array} aDest michael@0: * optional, mat4 frustum matrix will be written into michael@0: * if not specified result is written to a new mat4 michael@0: * michael@0: * @return {Array} the destination mat4 if specified, a new mat4 otherwise michael@0: */ michael@0: perspective: function M4_perspective( michael@0: aFovy, aAspect, aNear, aFar, aDest, aFlip) michael@0: { michael@0: let upper = aNear * Math.tan(aFovy * 0.00872664626); // PI * 180 / 2 michael@0: let right = upper * aAspect; michael@0: let top = upper * (aFlip || 1); michael@0: michael@0: return mat4.frustum(-right, right, -top, top, aNear, aFar, aDest); michael@0: }, michael@0: michael@0: /** michael@0: * Generates a orthogonal projection matrix with the given bounds. michael@0: * michael@0: * @param {Number} aLeft michael@0: * scalar, left bound of the frustum michael@0: * @param {Number} aRight michael@0: * scalar, right bound of the frustum michael@0: * @param {Number} aBottom michael@0: * scalar, bottom bound of the frustum michael@0: * @param {Number} aTop michael@0: * scalar, top bound of the frustum michael@0: * @param {Number} aNear michael@0: * scalar, near bound of the frustum michael@0: * @param {Number} aFar michael@0: * scalar, far bound of the frustum michael@0: * @param {Array} aDest michael@0: * optional, mat4 frustum matrix will be written into michael@0: * if not specified result is written to a new mat4 michael@0: * michael@0: * @return {Array} the destination mat4 if specified, a new mat4 otherwise michael@0: */ michael@0: ortho: function M4_ortho(aLeft, aRight, aBottom, aTop, aNear, aFar, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(16); michael@0: } michael@0: michael@0: let rl = (aRight - aLeft); michael@0: let tb = (aTop - aBottom); michael@0: let fn = (aFar - aNear); michael@0: michael@0: aDest[0] = 2 / rl; michael@0: aDest[1] = 0; michael@0: aDest[2] = 0; michael@0: aDest[3] = 0; michael@0: aDest[4] = 0; michael@0: aDest[5] = 2 / tb; michael@0: aDest[6] = 0; michael@0: aDest[7] = 0; michael@0: aDest[8] = 0; michael@0: aDest[9] = 0; michael@0: aDest[10] = -2 / fn; michael@0: aDest[11] = 0; michael@0: aDest[12] = -(aLeft + aRight) / rl; michael@0: aDest[13] = -(aTop + aBottom) / tb; michael@0: aDest[14] = -(aFar + aNear) / fn; michael@0: aDest[15] = 1; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Generates a look-at matrix with the given eye position, focal point, and michael@0: * up axis. michael@0: * michael@0: * @param {Array} aEye michael@0: * vec3, position of the viewer michael@0: * @param {Array} aCenter michael@0: * vec3, point the viewer is looking at michael@0: * @param {Array} aUp michael@0: * vec3 pointing up michael@0: * @param {Array} aDest michael@0: * optional, mat4 frustum matrix will be written into michael@0: * if not specified result is written to a new mat4 michael@0: * michael@0: * @return {Array} the destination mat4 if specified, a new mat4 otherwise michael@0: */ michael@0: lookAt: function M4_lookAt(aEye, aCenter, aUp, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(16); michael@0: } michael@0: michael@0: let eyex = aEye[0]; michael@0: let eyey = aEye[1]; michael@0: let eyez = aEye[2]; michael@0: let upx = aUp[0]; michael@0: let upy = aUp[1]; michael@0: let upz = aUp[2]; michael@0: let centerx = aCenter[0]; michael@0: let centery = aCenter[1]; michael@0: let centerz = aCenter[2]; michael@0: michael@0: let z0 = eyex - aCenter[0]; michael@0: let z1 = eyey - aCenter[1]; michael@0: let z2 = eyez - aCenter[2]; michael@0: let len = 1 / (Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2) || EPSILON); michael@0: michael@0: z0 *= len; michael@0: z1 *= len; michael@0: z2 *= len; michael@0: michael@0: let x0 = upy * z2 - upz * z1; michael@0: let x1 = upz * z0 - upx * z2; michael@0: let x2 = upx * z1 - upy * z0; michael@0: len = 1 / (Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2) || EPSILON); michael@0: michael@0: x0 *= len; michael@0: x1 *= len; michael@0: x2 *= len; michael@0: michael@0: let y0 = z1 * x2 - z2 * x1; michael@0: let y1 = z2 * x0 - z0 * x2; michael@0: let y2 = z0 * x1 - z1 * x0; michael@0: len = 1 / (Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2) || EPSILON); michael@0: michael@0: y0 *= len; michael@0: y1 *= len; michael@0: y2 *= len; michael@0: michael@0: aDest[0] = x0; michael@0: aDest[1] = y0; michael@0: aDest[2] = z0; michael@0: aDest[3] = 0; michael@0: aDest[4] = x1; michael@0: aDest[5] = y1; michael@0: aDest[6] = z1; michael@0: aDest[7] = 0; michael@0: aDest[8] = x2; michael@0: aDest[9] = y2; michael@0: aDest[10] = z2; michael@0: aDest[11] = 0; michael@0: aDest[12] = -(x0 * eyex + x1 * eyey + x2 * eyez); michael@0: aDest[13] = -(y0 * eyex + y1 * eyey + y2 * eyez); michael@0: aDest[14] = -(z0 * eyex + z1 * eyey + z2 * eyez); michael@0: aDest[15] = 1; michael@0: michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Returns a string representation of a 4x4 matrix. michael@0: * michael@0: * @param {Array} aMat michael@0: * mat4 to represent as a string michael@0: * michael@0: * @return {String} representation of the matrix michael@0: */ michael@0: str: function M4_str(mat) michael@0: { michael@0: return "[" + mat[0] + ", " + mat[1] + ", " + mat[2] + ", " + mat[3] + michael@0: ", "+ mat[4] + ", " + mat[5] + ", " + mat[6] + ", " + mat[7] + michael@0: ", "+ mat[8] + ", " + mat[9] + ", " + mat[10] + ", " + mat[11] + michael@0: ", "+ mat[12] + ", " + mat[13] + ", " + mat[14] + ", " + mat[15] + michael@0: "]"; michael@0: } michael@0: }; michael@0: michael@0: exports.mat4 = mat4; michael@0: michael@0: /** michael@0: * quat4 - Quaternion. michael@0: */ michael@0: let quat4 = { michael@0: michael@0: /** michael@0: * Creates a new instance of a quat4 using the default Float32Array type. michael@0: * Any array containing at least 4 numeric elements can serve as a quat4. michael@0: * michael@0: * @param {Array} aQuat michael@0: * optional, quat4 containing values to initialize with michael@0: * michael@0: * @return {Array} a new instance of a quat4 michael@0: */ michael@0: create: function Q4_create(aQuat) michael@0: { michael@0: let dest = new Float32Array(4); michael@0: michael@0: if (aQuat) { michael@0: quat4.set(aQuat, dest); michael@0: } else { michael@0: quat4.identity(dest); michael@0: } michael@0: return dest; michael@0: }, michael@0: michael@0: /** michael@0: * Copies the values of one quat4 to another. michael@0: * michael@0: * @param {Array} aQuat michael@0: * quat4 containing values to copy michael@0: * @param {Array} aDest michael@0: * quat4 receiving copied values michael@0: * michael@0: * @return {Array} the destination quat4 receiving copied values michael@0: */ michael@0: set: function Q4_set(aQuat, aDest) michael@0: { michael@0: aDest[0] = aQuat[0]; michael@0: aDest[1] = aQuat[1]; michael@0: aDest[2] = aQuat[2]; michael@0: aDest[3] = aQuat[3]; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Sets a quat4 to an identity quaternion. michael@0: * michael@0: * @param {Array} aDest michael@0: * quat4 to set michael@0: * michael@0: * @return {Array} the same quaternion michael@0: */ michael@0: identity: function Q4_identity(aDest) michael@0: { michael@0: aDest[0] = 0; michael@0: aDest[1] = 0; michael@0: aDest[2] = 0; michael@0: aDest[3] = 1; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Calculate the W component of a quat4 from the X, Y, and Z components. michael@0: * Assumes that quaternion is 1 unit in length. michael@0: * Any existing W component will be ignored. michael@0: * michael@0: * @param {Array} aQuat michael@0: * quat4 to calculate W component of michael@0: * @param {Array} aDest michael@0: * optional, quat4 receiving calculated values michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination quat if specified, first operand otherwise michael@0: */ michael@0: calculateW: function Q4_calculateW(aQuat, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aQuat; michael@0: } michael@0: michael@0: let x = aQuat[0]; michael@0: let y = aQuat[1]; michael@0: let z = aQuat[2]; michael@0: michael@0: aDest[0] = x; michael@0: aDest[1] = y; michael@0: aDest[2] = z; michael@0: aDest[3] = -Math.sqrt(Math.abs(1 - x * x - y * y - z * z)); michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Calculate the inverse of a quat4. michael@0: * michael@0: * @param {Array} aQuat michael@0: * quat4 to calculate the inverse of michael@0: * @param {Array} aDest michael@0: * optional, quat4 receiving the inverse values michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination quat if specified, first operand otherwise michael@0: */ michael@0: inverse: function Q4_inverse(aQuat, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aQuat; michael@0: } michael@0: michael@0: aQuat[0] = -aQuat[0]; michael@0: aQuat[1] = -aQuat[1]; michael@0: aQuat[2] = -aQuat[2]; michael@0: return aQuat; michael@0: }, michael@0: michael@0: /** michael@0: * Generates a unit quaternion of the same direction as the provided quat4. michael@0: * If quaternion length is 0, returns [0, 0, 0, 0]. michael@0: * michael@0: * @param {Array} aQuat michael@0: * quat4 to normalize michael@0: * @param {Array} aDest michael@0: * optional, quat4 receiving the operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination quat if specified, first operand otherwise michael@0: */ michael@0: normalize: function Q4_normalize(aQuat, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aQuat; michael@0: } michael@0: michael@0: let x = aQuat[0]; michael@0: let y = aQuat[1]; michael@0: let z = aQuat[2]; michael@0: let w = aQuat[3]; michael@0: let len = Math.sqrt(x * x + y * y + z * z + w * w); michael@0: michael@0: if (Math.abs(len) < EPSILON) { michael@0: aDest[0] = 0; michael@0: aDest[1] = 0; michael@0: aDest[2] = 0; michael@0: aDest[3] = 0; michael@0: return aDest; michael@0: } michael@0: michael@0: len = 1 / len; michael@0: aDest[0] = x * len; michael@0: aDest[1] = y * len; michael@0: aDest[2] = z * len; michael@0: aDest[3] = w * len; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Calculate the length of a quat4. michael@0: * michael@0: * @param {Array} aQuat michael@0: * quat4 to calculate the length of michael@0: * michael@0: * @return {Number} length of the quaternion michael@0: */ michael@0: length: function Q4_length(aQuat) michael@0: { michael@0: let x = aQuat[0]; michael@0: let y = aQuat[1]; michael@0: let z = aQuat[2]; michael@0: let w = aQuat[3]; michael@0: michael@0: return Math.sqrt(x * x + y * y + z * z + w * w); michael@0: }, michael@0: michael@0: /** michael@0: * Performs a quaternion multiplication. michael@0: * michael@0: * @param {Array} aQuat michael@0: * first operand michael@0: * @param {Array} aQuat2 michael@0: * second operand michael@0: * @param {Array} aDest michael@0: * optional, quat4 receiving the operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination quat if specified, first operand otherwise michael@0: */ michael@0: multiply: function Q4_multiply(aQuat, aQuat2, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aQuat; michael@0: } michael@0: michael@0: let qax = aQuat[0]; michael@0: let qay = aQuat[1]; michael@0: let qaz = aQuat[2]; michael@0: let qaw = aQuat[3]; michael@0: let qbx = aQuat2[0]; michael@0: let qby = aQuat2[1]; michael@0: let qbz = aQuat2[2]; michael@0: let qbw = aQuat2[3]; michael@0: michael@0: aDest[0] = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; michael@0: aDest[1] = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; michael@0: aDest[2] = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; michael@0: aDest[3] = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Transforms a vec3 with the given quaternion. michael@0: * michael@0: * @param {Array} aQuat michael@0: * quat4 to transform the vector with michael@0: * @param {Array} aVec michael@0: * vec3 to transform michael@0: * @param {Array} aDest michael@0: * optional, vec3 receiving the operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination vec3 if specified, aVec operand otherwise michael@0: */ michael@0: multiplyVec3: function Q4_multiplyVec3(aQuat, aVec, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aVec; michael@0: } michael@0: michael@0: let x = aVec[0]; michael@0: let y = aVec[1]; michael@0: let z = aVec[2]; michael@0: michael@0: let qx = aQuat[0]; michael@0: let qy = aQuat[1]; michael@0: let qz = aQuat[2]; michael@0: let qw = aQuat[3]; michael@0: michael@0: let ix = qw * x + qy * z - qz * y; michael@0: let iy = qw * y + qz * x - qx * z; michael@0: let iz = qw * z + qx * y - qy * x; michael@0: let iw = -qx * x - qy * y - qz * z; michael@0: michael@0: aDest[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy; michael@0: aDest[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz; michael@0: aDest[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Performs a spherical linear interpolation between two quat4. michael@0: * michael@0: * @param {Array} aQuat michael@0: * first quaternion michael@0: * @param {Array} aQuat2 michael@0: * second quaternion michael@0: * @param {Number} aSlerp michael@0: * interpolation amount between the two inputs michael@0: * @param {Array} aDest michael@0: * optional, quat4 receiving the operation result michael@0: * if not specified result is written to the first operand michael@0: * michael@0: * @return {Array} the destination quat if specified, first operand otherwise michael@0: */ michael@0: slerp: function Q4_slerp(aQuat, aQuat2, aSlerp, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = aQuat; michael@0: } michael@0: michael@0: let cosHalfTheta = aQuat[0] * aQuat2[0] + michael@0: aQuat[1] * aQuat2[1] + michael@0: aQuat[2] * aQuat2[2] + michael@0: aQuat[3] * aQuat2[3]; michael@0: michael@0: if (Math.abs(cosHalfTheta) >= 1) { michael@0: aDest[0] = aQuat[0]; michael@0: aDest[1] = aQuat[1]; michael@0: aDest[2] = aQuat[2]; michael@0: aDest[3] = aQuat[3]; michael@0: return aDest; michael@0: } michael@0: michael@0: let halfTheta = Math.acos(cosHalfTheta); michael@0: let sinHalfTheta = Math.sqrt(1 - cosHalfTheta * cosHalfTheta); michael@0: michael@0: if (Math.abs(sinHalfTheta) < EPSILON) { michael@0: aDest[0] = (aQuat[0] * 0.5 + aQuat2[0] * 0.5); michael@0: aDest[1] = (aQuat[1] * 0.5 + aQuat2[1] * 0.5); michael@0: aDest[2] = (aQuat[2] * 0.5 + aQuat2[2] * 0.5); michael@0: aDest[3] = (aQuat[3] * 0.5 + aQuat2[3] * 0.5); michael@0: return aDest; michael@0: } michael@0: michael@0: let ratioA = Math.sin((1 - aSlerp) * halfTheta) / sinHalfTheta; michael@0: let ratioB = Math.sin(aSlerp * halfTheta) / sinHalfTheta; michael@0: michael@0: aDest[0] = (aQuat[0] * ratioA + aQuat2[0] * ratioB); michael@0: aDest[1] = (aQuat[1] * ratioA + aQuat2[1] * ratioB); michael@0: aDest[2] = (aQuat[2] * ratioA + aQuat2[2] * ratioB); michael@0: aDest[3] = (aQuat[3] * ratioA + aQuat2[3] * ratioB); michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Calculates a 3x3 matrix from the given quat4. michael@0: * michael@0: * @param {Array} aQuat michael@0: * quat4 to create matrix from michael@0: * @param {Array} aDest michael@0: * optional, mat3 receiving the initialization result michael@0: * if not specified, a new matrix is created michael@0: * michael@0: * @return {Array} the destination mat3 if specified, first operand otherwise michael@0: */ michael@0: toMat3: function Q4_toMat3(aQuat, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(9); michael@0: } michael@0: michael@0: let x = aQuat[0]; michael@0: let y = aQuat[1]; michael@0: let z = aQuat[2]; michael@0: let w = aQuat[3]; michael@0: michael@0: let x2 = x + x; michael@0: let y2 = y + y; michael@0: let z2 = z + z; michael@0: let xx = x * x2; michael@0: let xy = x * y2; michael@0: let xz = x * z2; michael@0: let yy = y * y2; michael@0: let yz = y * z2; michael@0: let zz = z * z2; michael@0: let wx = w * x2; michael@0: let wy = w * y2; michael@0: let wz = w * z2; michael@0: michael@0: aDest[0] = 1 - (yy + zz); michael@0: aDest[1] = xy - wz; michael@0: aDest[2] = xz + wy; michael@0: aDest[3] = xy + wz; michael@0: aDest[4] = 1 - (xx + zz); michael@0: aDest[5] = yz - wx; michael@0: aDest[6] = xz - wy; michael@0: aDest[7] = yz + wx; michael@0: aDest[8] = 1 - (xx + yy); michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Calculates a 4x4 matrix from the given quat4. michael@0: * michael@0: * @param {Array} aQuat michael@0: * quat4 to create matrix from michael@0: * @param {Array} aDest michael@0: * optional, mat4 receiving the initialization result michael@0: * if not specified, a new matrix is created michael@0: * michael@0: * @return {Array} the destination mat4 if specified, first operand otherwise michael@0: */ michael@0: toMat4: function Q4_toMat4(aQuat, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(16); michael@0: } michael@0: michael@0: let x = aQuat[0]; michael@0: let y = aQuat[1]; michael@0: let z = aQuat[2]; michael@0: let w = aQuat[3]; michael@0: michael@0: let x2 = x + x; michael@0: let y2 = y + y; michael@0: let z2 = z + z; michael@0: let xx = x * x2; michael@0: let xy = x * y2; michael@0: let xz = x * z2; michael@0: let yy = y * y2; michael@0: let yz = y * z2; michael@0: let zz = z * z2; michael@0: let wx = w * x2; michael@0: let wy = w * y2; michael@0: let wz = w * z2; michael@0: michael@0: aDest[0] = 1 - (yy + zz); michael@0: aDest[1] = xy - wz; michael@0: aDest[2] = xz + wy; michael@0: aDest[3] = 0; michael@0: aDest[4] = xy + wz; michael@0: aDest[5] = 1 - (xx + zz); michael@0: aDest[6] = yz - wx; michael@0: aDest[7] = 0; michael@0: aDest[8] = xz - wy; michael@0: aDest[9] = yz + wx; michael@0: aDest[10] = 1 - (xx + yy); michael@0: aDest[11] = 0; michael@0: aDest[12] = 0; michael@0: aDest[13] = 0; michael@0: aDest[14] = 0; michael@0: aDest[15] = 1; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Creates a rotation quaternion from axis-angle. michael@0: * This function expects that the axis is a normalized vector. michael@0: * michael@0: * @param {Array} aAxis michael@0: * an array of elements representing the [x, y, z] axis michael@0: * @param {Number} aAngle michael@0: * the angle of rotation michael@0: * @param {Array} aDest michael@0: * optional, quat4 receiving the initialization result michael@0: * if not specified, a new quaternion is created michael@0: * michael@0: * @return {Array} the quaternion as [x, y, z, w] michael@0: */ michael@0: fromAxis: function Q4_fromAxis(aAxis, aAngle, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(4); michael@0: } michael@0: michael@0: let ang = aAngle * 0.5; michael@0: let sin = Math.sin(ang); michael@0: let cos = Math.cos(ang); michael@0: michael@0: aDest[0] = aAxis[0] * sin; michael@0: aDest[1] = aAxis[1] * sin; michael@0: aDest[2] = aAxis[2] * sin; michael@0: aDest[3] = cos; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Creates a rotation quaternion from Euler angles. michael@0: * michael@0: * @param {Number} aYaw michael@0: * the yaw angle of rotation michael@0: * @param {Number} aPitch michael@0: * the pitch angle of rotation michael@0: * @param {Number} aRoll michael@0: * the roll angle of rotation michael@0: * @param {Array} aDest michael@0: * optional, quat4 receiving the initialization result michael@0: * if not specified, a new quaternion is created michael@0: * michael@0: * @return {Array} the quaternion as [x, y, z, w] michael@0: */ michael@0: fromEuler: function Q4_fromEuler(aYaw, aPitch, aRoll, aDest) michael@0: { michael@0: if (!aDest) { michael@0: aDest = new Float32Array(4); michael@0: } michael@0: michael@0: let x = aPitch * 0.5; michael@0: let y = aYaw * 0.5; michael@0: let z = aRoll * 0.5; michael@0: michael@0: let sinr = Math.sin(x); michael@0: let sinp = Math.sin(y); michael@0: let siny = Math.sin(z); michael@0: let cosr = Math.cos(x); michael@0: let cosp = Math.cos(y); michael@0: let cosy = Math.cos(z); michael@0: michael@0: aDest[0] = sinr * cosp * cosy - cosr * sinp * siny; michael@0: aDest[1] = cosr * sinp * cosy + sinr * cosp * siny; michael@0: aDest[2] = cosr * cosp * siny - sinr * sinp * cosy; michael@0: aDest[3] = cosr * cosp * cosy + sinr * sinp * siny; michael@0: return aDest; michael@0: }, michael@0: michael@0: /** michael@0: * Returns a string representation of a quaternion. michael@0: * michael@0: * @param {Array} aQuat michael@0: * quat4 to represent as a string michael@0: * michael@0: * @return {String} representation of the quaternion michael@0: */ michael@0: str: function Q4_str(aQuat) { michael@0: return "[" + aQuat[0] + ", " + michael@0: aQuat[1] + ", " + michael@0: aQuat[2] + ", " + michael@0: aQuat[3] + "]"; michael@0: } michael@0: }; michael@0: michael@0: exports.quat4 = quat4; michael@0: michael@0: /** michael@0: * Various algebraic math functions required by the engine. michael@0: */ michael@0: let TiltMath = { michael@0: michael@0: /** michael@0: * Helper function, converts degrees to radians. michael@0: * michael@0: * @param {Number} aDegrees michael@0: * the degrees to be converted to radians michael@0: * michael@0: * @return {Number} the degrees converted to radians michael@0: */ michael@0: radians: function TM_radians(aDegrees) michael@0: { michael@0: return aDegrees * PI_OVER_180; michael@0: }, michael@0: michael@0: /** michael@0: * Helper function, converts radians to degrees. michael@0: * michael@0: * @param {Number} aRadians michael@0: * the radians to be converted to degrees michael@0: * michael@0: * @return {Number} the radians converted to degrees michael@0: */ michael@0: degrees: function TM_degrees(aRadians) michael@0: { michael@0: return aRadians * INV_PI_OVER_180; michael@0: }, michael@0: michael@0: /** michael@0: * Re-maps a number from one range to another. michael@0: * michael@0: * @param {Number} aValue michael@0: * the number to map michael@0: * @param {Number} aLow1 michael@0: * the normal lower bound of the number michael@0: * @param {Number} aHigh1 michael@0: * the normal upper bound of the number michael@0: * @param {Number} aLow2 michael@0: * the new lower bound of the number michael@0: * @param {Number} aHigh2 michael@0: * the new upper bound of the number michael@0: * michael@0: * @return {Number} the remapped number michael@0: */ michael@0: map: function TM_map(aValue, aLow1, aHigh1, aLow2, aHigh2) michael@0: { michael@0: return aLow2 + (aHigh2 - aLow2) * ((aValue - aLow1) / (aHigh1 - aLow1)); michael@0: }, michael@0: michael@0: /** michael@0: * Returns if number is power of two. michael@0: * michael@0: * @param {Number} aNumber michael@0: * the number to be verified michael@0: * michael@0: * @return {Boolean} true if x is power of two michael@0: */ michael@0: isPowerOfTwo: function TM_isPowerOfTwo(aNumber) michael@0: { michael@0: return !(aNumber & (aNumber - 1)); michael@0: }, michael@0: michael@0: /** michael@0: * Returns the next closest power of two greater than a number. michael@0: * michael@0: * @param {Number} aNumber michael@0: * the number to be converted michael@0: * michael@0: * @return {Number} the next closest power of two for x michael@0: */ michael@0: nextPowerOfTwo: function TM_nextPowerOfTwo(aNumber) michael@0: { michael@0: --aNumber; michael@0: michael@0: for (let i = 1; i < 32; i <<= 1) { michael@0: aNumber = aNumber | aNumber >> i; michael@0: } michael@0: return aNumber + 1; michael@0: }, michael@0: michael@0: /** michael@0: * A convenient way of limiting values to a set boundary. michael@0: * michael@0: * @param {Number} aValue michael@0: * the number to be limited michael@0: * @param {Number} aMin michael@0: * the minimum allowed value for the number michael@0: * @param {Number} aMax michael@0: * the maximum allowed value for the number michael@0: */ michael@0: clamp: function TM_clamp(aValue, aMin, aMax) michael@0: { michael@0: return Math.max(aMin, Math.min(aMax, aValue)); michael@0: }, michael@0: michael@0: /** michael@0: * Convenient way to clamp a value to 0..1 michael@0: * michael@0: * @param {Number} aValue michael@0: * the number to be limited michael@0: */ michael@0: saturate: function TM_saturate(aValue) michael@0: { michael@0: return Math.max(0, Math.min(1, aValue)); michael@0: }, michael@0: michael@0: /** michael@0: * Converts a hex color to rgba. michael@0: * If the passed param is invalid, it will be converted to [0, 0, 0, 1]; michael@0: * michael@0: * @param {String} aColor michael@0: * color expressed in hex, or using rgb() or rgba() michael@0: * michael@0: * @return {Array} with 4 color 0..1 components: [red, green, blue, alpha] michael@0: */ michael@0: hex2rgba: (function() michael@0: { michael@0: let cache = {}; michael@0: michael@0: return function TM_hex2rgba(aColor) { michael@0: let hex = aColor.charAt(0) === "#" ? aColor.substring(1) : aColor; michael@0: michael@0: // check the cache to see if this color wasn't converted already michael@0: if (cache[hex] !== undefined) { michael@0: return cache[hex]; michael@0: } michael@0: michael@0: // e.g. "f00" michael@0: if (hex.length === 3) { michael@0: let r = parseInt(hex.substring(0, 1), 16) * FIFTEEN_OVER_225; michael@0: let g = parseInt(hex.substring(1, 2), 16) * FIFTEEN_OVER_225; michael@0: let b = parseInt(hex.substring(2, 3), 16) * FIFTEEN_OVER_225; michael@0: michael@0: return (cache[hex] = [r, g, b, 1]); michael@0: } michael@0: // e.g. "f008" michael@0: if (hex.length === 4) { michael@0: let r = parseInt(hex.substring(0, 1), 16) * FIFTEEN_OVER_225; michael@0: let g = parseInt(hex.substring(1, 2), 16) * FIFTEEN_OVER_225; michael@0: let b = parseInt(hex.substring(2, 3), 16) * FIFTEEN_OVER_225; michael@0: let a = parseInt(hex.substring(3, 4), 16) * FIFTEEN_OVER_225; michael@0: michael@0: return (cache[hex] = [r, g, b, a]); michael@0: } michael@0: // e.g. "ff0000" michael@0: if (hex.length === 6) { michael@0: let r = parseInt(hex.substring(0, 2), 16) * ONE_OVER_255; michael@0: let g = parseInt(hex.substring(2, 4), 16) * ONE_OVER_255; michael@0: let b = parseInt(hex.substring(4, 6), 16) * ONE_OVER_255; michael@0: let a = 1; michael@0: michael@0: return (cache[hex] = [r, g, b, a]); michael@0: } michael@0: // e.g "ff0000aa" michael@0: if (hex.length === 8) { michael@0: let r = parseInt(hex.substring(0, 2), 16) * ONE_OVER_255; michael@0: let g = parseInt(hex.substring(2, 4), 16) * ONE_OVER_255; michael@0: let b = parseInt(hex.substring(4, 6), 16) * ONE_OVER_255; michael@0: let a = parseInt(hex.substring(6, 8), 16) * ONE_OVER_255; michael@0: michael@0: return (cache[hex] = [r, g, b, a]); michael@0: } michael@0: // e.g. "rgba(255, 0, 0, 0.5)" michael@0: if (hex.match("^rgba")) { michael@0: let rgba = hex.substring(5, hex.length - 1).split(","); michael@0: rgba[0] *= ONE_OVER_255; michael@0: rgba[1] *= ONE_OVER_255; michael@0: rgba[2] *= ONE_OVER_255; michael@0: // in CSS, the alpha component of rgba() is already in the range 0..1 michael@0: michael@0: return (cache[hex] = rgba); michael@0: } michael@0: // e.g. "rgb(255, 0, 0)" michael@0: if (hex.match("^rgb")) { michael@0: let rgba = hex.substring(4, hex.length - 1).split(","); michael@0: rgba[0] *= ONE_OVER_255; michael@0: rgba[1] *= ONE_OVER_255; michael@0: rgba[2] *= ONE_OVER_255; michael@0: rgba[3] = 1; michael@0: michael@0: return (cache[hex] = rgba); michael@0: } michael@0: michael@0: // your argument is invalid michael@0: return (cache[hex] = [0, 0, 0, 1]); michael@0: }; michael@0: }()) michael@0: }; michael@0: michael@0: exports.TiltMath = TiltMath; michael@0: michael@0: // bind the owner object to the necessary functions michael@0: TiltUtils.bindObjectFunc(vec3); michael@0: TiltUtils.bindObjectFunc(mat3); michael@0: TiltUtils.bindObjectFunc(mat4); michael@0: TiltUtils.bindObjectFunc(quat4); michael@0: TiltUtils.bindObjectFunc(TiltMath);