From d57eb2925820c2079702c80e690c44370d4fa473 Mon Sep 17 00:00:00 2001 From: Tomycj Date: Mon, 8 Jan 2024 04:18:38 -0300 Subject: [PATCH] Introducing Cells3D indev --- .gitignore | 2 + WebGPU/Cells_3D.html | 342 + WebGPU/modules/aleaRNG.js | 67 + WebGPU/modules/gl-matrix.js | 7711 +++++++++++++++++++ WebGPU/modules/misClases.js | 0 WebGPU/modules/misFunciones.js | 279 + WebGPU/modules/utilities.js | 0 WebGPU/scripts/cells3D.js | 2619 +++++++ WebGPU/scripts/cellsGPU.js | 81 +- WebGPU/shaders/shadersCellsGPU.js | 340 +- data/Cells GPU setup - Test posiciones.json | 31 + index.html | 3 + sounds/crystal_click.wav | Bin 0 -> 26804 bytes sounds/glass_click.wav | Bin 0 -> 71874 bytes 14 files changed, 11431 insertions(+), 44 deletions(-) create mode 100644 WebGPU/Cells_3D.html create mode 100644 WebGPU/modules/aleaRNG.js create mode 100644 WebGPU/modules/gl-matrix.js create mode 100644 WebGPU/modules/misClases.js create mode 100644 WebGPU/modules/utilities.js create mode 100644 WebGPU/scripts/cells3D.js create mode 100644 data/Cells GPU setup - Test posiciones.json create mode 100644 sounds/crystal_click.wav create mode 100644 sounds/glass_click.wav diff --git a/.gitignore b/.gitignore index 08c23ec..2aebca8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ WebGPU/scripts/cellsGPU - copia.js WebGPU/shaders/syntax_check.wgsl WebGPU/.vscode/launch.json +Test samples +PWA \ No newline at end of file diff --git a/WebGPU/Cells_3D.html b/WebGPU/Cells_3D.html new file mode 100644 index 0000000..1555856 --- /dev/null +++ b/WebGPU/Cells_3D.html @@ -0,0 +1,342 @@ + + + + Cells 3D - v0.5 + + + + + + + + + + + + +

Iniciando...

+ +
+ +
+ + + + + + + +
+ +
+
+ +
+
+ +
+ + + +
+ + Panel de control + + + +
+ +
+ +
+ Edad:  - +
+ FPS:  - +
+ + +
+
+
+ +
+ +
+ +
+ + + +
+ + + + + + \ No newline at end of file diff --git a/WebGPU/modules/aleaRNG.js b/WebGPU/modules/aleaRNG.js new file mode 100644 index 0000000..1a48a2d --- /dev/null +++ b/WebGPU/modules/aleaRNG.js @@ -0,0 +1,67 @@ +!(function (n, t, e) { + function u(n) { + var t = this, + e = (function () { + var s = 4022871197; + return function (n) { + n = String(n); + for (var t = 0; t < n.length; t++) { + var e = 0.02519603282416938 * (s += n.charCodeAt(t)); + (e -= s = e >>> 0), + (s = (e *= s) >>> 0), + (s += 4294967296 * (e -= s)); + } + return 2.3283064365386963e-10 * (s >>> 0); + }; + })(); + (t.next = function () { + var n = 2091639 * t.s0 + 2.3283064365386963e-10 * t.c; + return (t.s0 = t.s1), (t.s1 = t.s2), (t.s2 = n - (t.c = 0 | n)); + }), + (t.c = 1), + (t.s0 = e(" ")), + (t.s1 = e(" ")), + (t.s2 = e(" ")), + (t.s0 -= e(n)), + t.s0 < 0 && (t.s0 += 1), + (t.s1 -= e(n)), + t.s1 < 0 && (t.s1 += 1), + (t.s2 -= e(n)), + t.s2 < 0 && (t.s2 += 1), + (e = null); + } + function o(n, t) { + return (t.c = n.c), (t.s0 = n.s0), (t.s1 = n.s1), (t.s2 = n.s2), t; + } + function s(n, t) { + var e = new u(n), + s = t && t.state, + r = e.next; + return ( + (r.int32 = function () { + return (4294967296 * e.next()) | 0; + }), + (r.double = function () { + return r() + 11102230246251565e-32 * ((2097152 * r()) | 0); + }), + (r.quick = r), + s && + ("object" == typeof s && o(s, e), + (r.state = function () { + return o(e, {}); + })), + r + ); + } + t && t.exports + ? (t.exports = s) + : e && e.amd + ? e(function () { + return s; + }) + : (this.alea = s); +})( + 0, + "object" == typeof module && module, + "function" == typeof define && define +); diff --git a/WebGPU/modules/gl-matrix.js b/WebGPU/modules/gl-matrix.js new file mode 100644 index 0000000..cb64198 --- /dev/null +++ b/WebGPU/modules/gl-matrix.js @@ -0,0 +1,7711 @@ + +/*! +@fileoverview gl-matrix - High performance matrix and vector operations +@author Brandon Jones +@author Colin MacKenzie IV +@version 3.4.3 + +Copyright (c) 2015-2021, Brandon Jones, Colin MacKenzie IV. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = global || self, factory(global.glMatrix = {})); +}(this, (function (exports) { 'use strict'; + // functions before mat4 + /** + * Common utilities + * @module glMatrix + */ + // Configuration Constants + var EPSILON = 0.000001; + var ARRAY_TYPE = typeof Float32Array !== 'undefined' ? Float32Array : Array; + var RANDOM = Math.random; + /** + * Sets the type of array used when creating new vectors and matrices + * + * @param {Float32ArrayConstructor | ArrayConstructor} type Array type, such as Float32Array or Array + */ + + function setMatrixArrayType(type) { + ARRAY_TYPE = type; + } + var degree = Math.PI / 180; + /** + * Convert Degree To Radian + * + * @param {Number} a Angle in Degrees + */ + + function toRadian(a) { + return a * degree; + } + /** + * Tests whether or not the arguments have approximately the same value, within an absolute + * or relative tolerance of glMatrix.EPSILON (an absolute tolerance is used for values less + * than or equal to 1.0, and a relative tolerance is used for larger values) + * + * @param {Number} a The first number to test. + * @param {Number} b The second number to test. + * @returns {Boolean} True if the numbers are approximately equal, false otherwise. + */ + + function equals(a, b) { + return Math.abs(a - b) <= EPSILON * Math.max(1.0, Math.abs(a), Math.abs(b)); + } + if (!Math.hypot) Math.hypot = function () { + var y = 0, + i = arguments.length; + + while (i--) { + y += arguments[i] * arguments[i]; + } + + return Math.sqrt(y); + }; + + var common = /*#__PURE__*/Object.freeze({ + __proto__: null, + EPSILON: EPSILON, + get ARRAY_TYPE () { return ARRAY_TYPE; }, + RANDOM: RANDOM, + setMatrixArrayType: setMatrixArrayType, + toRadian: toRadian, + equals: equals + }); + + /** + * 2x2 Matrix + * @module mat2 + */ + + /** + * Creates a new identity mat2 + * + * @returns {mat2} a new 2x2 matrix + */ + + function create() { + var out = new ARRAY_TYPE(4); + + if (ARRAY_TYPE != Float32Array) { + out[1] = 0; + out[2] = 0; + } + + out[0] = 1; + out[3] = 1; + return out; + } + /** + * Creates a new mat2 initialized with values from an existing matrix + * + * @param {ReadonlyMat2} a matrix to clone + * @returns {mat2} a new 2x2 matrix + */ + + function clone(a) { + var out = new ARRAY_TYPE(4); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; + } + /** + * Copy the values from one mat2 to another + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the source matrix + * @returns {mat2} out + */ + + function copy(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; + } + /** + * Set a mat2 to the identity matrix + * + * @param {mat2} out the receiving matrix + * @returns {mat2} out + */ + + function identity(out) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 1; + return out; + } + /** + * Create a new mat2 with the given values + * + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m10 Component in column 1, row 0 position (index 2) + * @param {Number} m11 Component in column 1, row 1 position (index 3) + * @returns {mat2} out A new 2x2 matrix + */ + + function fromValues(m00, m01, m10, m11) { + var out = new ARRAY_TYPE(4); + out[0] = m00; + out[1] = m01; + out[2] = m10; + out[3] = m11; + return out; + } + /** + * Set the components of a mat2 to the given values + * + * @param {mat2} out the receiving matrix + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m10 Component in column 1, row 0 position (index 2) + * @param {Number} m11 Component in column 1, row 1 position (index 3) + * @returns {mat2} out + */ + + function set(out, m00, m01, m10, m11) { + out[0] = m00; + out[1] = m01; + out[2] = m10; + out[3] = m11; + return out; + } + /** + * Transpose the values of a mat2 + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the source matrix + * @returns {mat2} out + */ + + function transpose(out, a) { + // If we are transposing ourselves we can skip a few steps but have to cache + // some values + if (out === a) { + var a1 = a[1]; + out[1] = a[2]; + out[2] = a1; + } else { + out[0] = a[0]; + out[1] = a[2]; + out[2] = a[1]; + out[3] = a[3]; + } + + return out; + } + /** + * Inverts a mat2 + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the source matrix + * @returns {mat2} out + */ + + function invert(out, a) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; // Calculate the determinant + + var det = a0 * a3 - a2 * a1; + + if (!det) { + return null; + } + + det = 1.0 / det; + out[0] = a3 * det; + out[1] = -a1 * det; + out[2] = -a2 * det; + out[3] = a0 * det; + return out; + } + /** + * Calculates the adjugate of a mat2 + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the source matrix + * @returns {mat2} out + */ + + function adjoint(out, a) { + // Caching this value is nessecary if out == a + var a0 = a[0]; + out[0] = a[3]; + out[1] = -a[1]; + out[2] = -a[2]; + out[3] = a0; + return out; + } + /** + * Calculates the determinant of a mat2 + * + * @param {ReadonlyMat2} a the source matrix + * @returns {Number} determinant of a + */ + + function determinant(a) { + return a[0] * a[3] - a[2] * a[1]; + } + /** + * Multiplies two mat2's + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the first operand + * @param {ReadonlyMat2} b the second operand + * @returns {mat2} out + */ + + function multiply(out, a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + out[0] = a0 * b0 + a2 * b1; + out[1] = a1 * b0 + a3 * b1; + out[2] = a0 * b2 + a2 * b3; + out[3] = a1 * b2 + a3 * b3; + return out; + } + /** + * Rotates a mat2 by the given angle + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat2} out + */ + + function rotate(out, a, rad) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var s = Math.sin(rad); + var c = Math.cos(rad); + out[0] = a0 * c + a2 * s; + out[1] = a1 * c + a3 * s; + out[2] = a0 * -s + a2 * c; + out[3] = a1 * -s + a3 * c; + return out; + } + /** + * Scales the mat2 by the dimensions in the given vec2 + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the matrix to rotate + * @param {ReadonlyVec2} v the vec2 to scale the matrix by + * @returns {mat2} out + **/ + + function scale(out, a, v) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var v0 = v[0], + v1 = v[1]; + out[0] = a0 * v0; + out[1] = a1 * v0; + out[2] = a2 * v1; + out[3] = a3 * v1; + return out; + } + /** + * Creates a matrix from a given angle + * This is equivalent to (but much faster than): + * + * mat2.identity(dest); + * mat2.rotate(dest, dest, rad); + * + * @param {mat2} out mat2 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat2} out + */ + + function fromRotation(out, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); + out[0] = c; + out[1] = s; + out[2] = -s; + out[3] = c; + return out; + } + /** + * Creates a matrix from a vector scaling + * This is equivalent to (but much faster than): + * + * mat2.identity(dest); + * mat2.scale(dest, dest, vec); + * + * @param {mat2} out mat2 receiving operation result + * @param {ReadonlyVec2} v Scaling vector + * @returns {mat2} out + */ + + function fromScaling(out, v) { + out[0] = v[0]; + out[1] = 0; + out[2] = 0; + out[3] = v[1]; + return out; + } + /** + * Returns a string representation of a mat2 + * + * @param {ReadonlyMat2} a matrix to represent as a string + * @returns {String} string representation of the matrix + */ + + function str(a) { + return "mat2(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")"; + } + /** + * Returns Frobenius norm of a mat2 + * + * @param {ReadonlyMat2} a the matrix to calculate Frobenius norm of + * @returns {Number} Frobenius norm + */ + + function frob(a) { + return Math.hypot(a[0], a[1], a[2], a[3]); + } + /** + * Returns L, D and U matrices (Lower triangular, Diagonal and Upper triangular) by factorizing the input matrix + * @param {ReadonlyMat2} L the lower triangular matrix + * @param {ReadonlyMat2} D the diagonal matrix + * @param {ReadonlyMat2} U the upper triangular matrix + * @param {ReadonlyMat2} a the input matrix to factorize + */ + + function LDU(L, D, U, a) { + L[2] = a[2] / a[0]; + U[0] = a[0]; + U[1] = a[1]; + U[3] = a[3] - L[2] * U[1]; + return [L, D, U]; + } + /** + * Adds two mat2's + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the first operand + * @param {ReadonlyMat2} b the second operand + * @returns {mat2} out + */ + + function add(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + return out; + } + /** + * Subtracts matrix b from matrix a + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the first operand + * @param {ReadonlyMat2} b the second operand + * @returns {mat2} out + */ + + function subtract(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + out[3] = a[3] - b[3]; + return out; + } + /** + * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyMat2} a The first matrix. + * @param {ReadonlyMat2} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + + function exactEquals(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]; + } + /** + * Returns whether or not the matrices have approximately the same elements in the same position. + * + * @param {ReadonlyMat2} a The first matrix. + * @param {ReadonlyMat2} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + + function equals$1(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)); + } + /** + * Multiply each element of the matrix by a scalar. + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the matrix to scale + * @param {Number} b amount to scale the matrix's elements by + * @returns {mat2} out + */ + + function multiplyScalar(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + return out; + } + /** + * Adds two mat2's after multiplying each element of the second operand by a scalar value. + * + * @param {mat2} out the receiving vector + * @param {ReadonlyMat2} a the first operand + * @param {ReadonlyMat2} b the second operand + * @param {Number} scale the amount to scale b's elements by before adding + * @returns {mat2} out + */ + + function multiplyScalarAndAdd(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + out[3] = a[3] + b[3] * scale; + return out; + } + /** + * Alias for {@link mat2.multiply} + * @function + */ + + var mul = multiply; + /** + * Alias for {@link mat2.subtract} + * @function + */ + + var sub = subtract; + + var mat2 = /*#__PURE__*/Object.freeze({ + __proto__: null, + create: create, + clone: clone, + copy: copy, + identity: identity, + fromValues: fromValues, + set: set, + transpose: transpose, + invert: invert, + adjoint: adjoint, + determinant: determinant, + multiply: multiply, + rotate: rotate, + scale: scale, + fromRotation: fromRotation, + fromScaling: fromScaling, + str: str, + frob: frob, + LDU: LDU, + add: add, + subtract: subtract, + exactEquals: exactEquals, + equals: equals$1, + multiplyScalar: multiplyScalar, + multiplyScalarAndAdd: multiplyScalarAndAdd, + mul: mul, + sub: sub + }); + + /** + * 2x3 Matrix + * @module mat2d + * @description + * A mat2d contains six elements defined as: + *
+     * [a, b,
+     *  c, d,
+     *  tx, ty]
+     * 
+ * This is a short form for the 3x3 matrix: + *
+     * [a, b, 0,
+     *  c, d, 0,
+     *  tx, ty, 1]
+     * 
+ * The last column is ignored so the array is shorter and operations are faster. + */ + + /** + * Creates a new identity mat2d + * + * @returns {mat2d} a new 2x3 matrix + */ + + function create$1() { + var out = new ARRAY_TYPE(6); + + if (ARRAY_TYPE != Float32Array) { + out[1] = 0; + out[2] = 0; + out[4] = 0; + out[5] = 0; + } + + out[0] = 1; + out[3] = 1; + return out; + } + /** + * Creates a new mat2d initialized with values from an existing matrix + * + * @param {ReadonlyMat2d} a matrix to clone + * @returns {mat2d} a new 2x3 matrix + */ + + function clone$1(a) { + var out = new ARRAY_TYPE(6); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + return out; + } + /** + * Copy the values from one mat2d to another + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the source matrix + * @returns {mat2d} out + */ + + function copy$1(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + return out; + } + /** + * Set a mat2d to the identity matrix + * + * @param {mat2d} out the receiving matrix + * @returns {mat2d} out + */ + + function identity$1(out) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 1; + out[4] = 0; + out[5] = 0; + return out; + } + /** + * Create a new mat2d with the given values + * + * @param {Number} a Component A (index 0) + * @param {Number} b Component B (index 1) + * @param {Number} c Component C (index 2) + * @param {Number} d Component D (index 3) + * @param {Number} tx Component TX (index 4) + * @param {Number} ty Component TY (index 5) + * @returns {mat2d} A new mat2d + */ + + function fromValues$1(a, b, c, d, tx, ty) { + var out = new ARRAY_TYPE(6); + out[0] = a; + out[1] = b; + out[2] = c; + out[3] = d; + out[4] = tx; + out[5] = ty; + return out; + } + /** + * Set the components of a mat2d to the given values + * + * @param {mat2d} out the receiving matrix + * @param {Number} a Component A (index 0) + * @param {Number} b Component B (index 1) + * @param {Number} c Component C (index 2) + * @param {Number} d Component D (index 3) + * @param {Number} tx Component TX (index 4) + * @param {Number} ty Component TY (index 5) + * @returns {mat2d} out + */ + + function set$1(out, a, b, c, d, tx, ty) { + out[0] = a; + out[1] = b; + out[2] = c; + out[3] = d; + out[4] = tx; + out[5] = ty; + return out; + } + /** + * Inverts a mat2d + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the source matrix + * @returns {mat2d} out + */ + + function invert$1(out, a) { + var aa = a[0], + ab = a[1], + ac = a[2], + ad = a[3]; + var atx = a[4], + aty = a[5]; + var det = aa * ad - ab * ac; + + if (!det) { + return null; + } + + det = 1.0 / det; + out[0] = ad * det; + out[1] = -ab * det; + out[2] = -ac * det; + out[3] = aa * det; + out[4] = (ac * aty - ad * atx) * det; + out[5] = (ab * atx - aa * aty) * det; + return out; + } + /** + * Calculates the determinant of a mat2d + * + * @param {ReadonlyMat2d} a the source matrix + * @returns {Number} determinant of a + */ + + function determinant$1(a) { + return a[0] * a[3] - a[1] * a[2]; + } + /** + * Multiplies two mat2d's + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the first operand + * @param {ReadonlyMat2d} b the second operand + * @returns {mat2d} out + */ + + function multiply$1(out, a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5]; + out[0] = a0 * b0 + a2 * b1; + out[1] = a1 * b0 + a3 * b1; + out[2] = a0 * b2 + a2 * b3; + out[3] = a1 * b2 + a3 * b3; + out[4] = a0 * b4 + a2 * b5 + a4; + out[5] = a1 * b4 + a3 * b5 + a5; + return out; + } + /** + * Rotates a mat2d by the given angle + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat2d} out + */ + + function rotate$1(out, a, rad) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5]; + var s = Math.sin(rad); + var c = Math.cos(rad); + out[0] = a0 * c + a2 * s; + out[1] = a1 * c + a3 * s; + out[2] = a0 * -s + a2 * c; + out[3] = a1 * -s + a3 * c; + out[4] = a4; + out[5] = a5; + return out; + } + /** + * Scales the mat2d by the dimensions in the given vec2 + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the matrix to translate + * @param {ReadonlyVec2} v the vec2 to scale the matrix by + * @returns {mat2d} out + **/ + + function scale$1(out, a, v) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5]; + var v0 = v[0], + v1 = v[1]; + out[0] = a0 * v0; + out[1] = a1 * v0; + out[2] = a2 * v1; + out[3] = a3 * v1; + out[4] = a4; + out[5] = a5; + return out; + } + /** + * Translates the mat2d by the dimensions in the given vec2 + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the matrix to translate + * @param {ReadonlyVec2} v the vec2 to translate the matrix by + * @returns {mat2d} out + **/ + + function translate(out, a, v) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5]; + var v0 = v[0], + v1 = v[1]; + out[0] = a0; + out[1] = a1; + out[2] = a2; + out[3] = a3; + out[4] = a0 * v0 + a2 * v1 + a4; + out[5] = a1 * v0 + a3 * v1 + a5; + return out; + } + /** + * Creates a matrix from a given angle + * This is equivalent to (but much faster than): + * + * mat2d.identity(dest); + * mat2d.rotate(dest, dest, rad); + * + * @param {mat2d} out mat2d receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat2d} out + */ + + function fromRotation$1(out, rad) { + var s = Math.sin(rad), + c = Math.cos(rad); + out[0] = c; + out[1] = s; + out[2] = -s; + out[3] = c; + out[4] = 0; + out[5] = 0; + return out; + } + /** + * Creates a matrix from a vector scaling + * This is equivalent to (but much faster than): + * + * mat2d.identity(dest); + * mat2d.scale(dest, dest, vec); + * + * @param {mat2d} out mat2d receiving operation result + * @param {ReadonlyVec2} v Scaling vector + * @returns {mat2d} out + */ + + function fromScaling$1(out, v) { + out[0] = v[0]; + out[1] = 0; + out[2] = 0; + out[3] = v[1]; + out[4] = 0; + out[5] = 0; + return out; + } + /** + * Creates a matrix from a vector translation + * This is equivalent to (but much faster than): + * + * mat2d.identity(dest); + * mat2d.translate(dest, dest, vec); + * + * @param {mat2d} out mat2d receiving operation result + * @param {ReadonlyVec2} v Translation vector + * @returns {mat2d} out + */ + + function fromTranslation(out, v) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 1; + out[4] = v[0]; + out[5] = v[1]; + return out; + } + /** + * Returns a string representation of a mat2d + * + * @param {ReadonlyMat2d} a matrix to represent as a string + * @returns {String} string representation of the matrix + */ + + function str$1(a) { + return "mat2d(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ")"; + } + /** + * Returns Frobenius norm of a mat2d + * + * @param {ReadonlyMat2d} a the matrix to calculate Frobenius norm of + * @returns {Number} Frobenius norm + */ + + function frob$1(a) { + return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], 1); + } + /** + * Adds two mat2d's + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the first operand + * @param {ReadonlyMat2d} b the second operand + * @returns {mat2d} out + */ + + function add$1(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + out[4] = a[4] + b[4]; + out[5] = a[5] + b[5]; + return out; + } + /** + * Subtracts matrix b from matrix a + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the first operand + * @param {ReadonlyMat2d} b the second operand + * @returns {mat2d} out + */ + + function subtract$1(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + out[3] = a[3] - b[3]; + out[4] = a[4] - b[4]; + out[5] = a[5] - b[5]; + return out; + } + /** + * Multiply each element of the matrix by a scalar. + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the matrix to scale + * @param {Number} b amount to scale the matrix's elements by + * @returns {mat2d} out + */ + + function multiplyScalar$1(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + out[4] = a[4] * b; + out[5] = a[5] * b; + return out; + } + /** + * Adds two mat2d's after multiplying each element of the second operand by a scalar value. + * + * @param {mat2d} out the receiving vector + * @param {ReadonlyMat2d} a the first operand + * @param {ReadonlyMat2d} b the second operand + * @param {Number} scale the amount to scale b's elements by before adding + * @returns {mat2d} out + */ + + function multiplyScalarAndAdd$1(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + out[3] = a[3] + b[3] * scale; + out[4] = a[4] + b[4] * scale; + out[5] = a[5] + b[5] * scale; + return out; + } + /** + * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyMat2d} a The first matrix. + * @param {ReadonlyMat2d} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + + function exactEquals$1(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5]; + } + /** + * Returns whether or not the matrices have approximately the same elements in the same position. + * + * @param {ReadonlyMat2d} a The first matrix. + * @param {ReadonlyMat2d} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + + function equals$2(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5]; + return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)); + } + /** + * Alias for {@link mat2d.multiply} + * @function + */ + + var mul$1 = multiply$1; + /** + * Alias for {@link mat2d.subtract} + * @function + */ + + var sub$1 = subtract$1; + + var mat2d = /*#__PURE__*/Object.freeze({ + __proto__: null, + create: create$1, + clone: clone$1, + copy: copy$1, + identity: identity$1, + fromValues: fromValues$1, + set: set$1, + invert: invert$1, + determinant: determinant$1, + multiply: multiply$1, + rotate: rotate$1, + scale: scale$1, + translate: translate, + fromRotation: fromRotation$1, + fromScaling: fromScaling$1, + fromTranslation: fromTranslation, + str: str$1, + frob: frob$1, + add: add$1, + subtract: subtract$1, + multiplyScalar: multiplyScalar$1, + multiplyScalarAndAdd: multiplyScalarAndAdd$1, + exactEquals: exactEquals$1, + equals: equals$2, + mul: mul$1, + sub: sub$1 + }); + + /** + * 3x3 Matrix + * @module mat3 + */ + + /** + * Creates a new identity mat3 + * + * @returns {mat3} a new 3x3 matrix + */ + + function create$2() { + var out = new ARRAY_TYPE(9); + + if (ARRAY_TYPE != Float32Array) { + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[5] = 0; + out[6] = 0; + out[7] = 0; + } + + out[0] = 1; + out[4] = 1; + out[8] = 1; + return out; + } + /** + * Copies the upper-left 3x3 values into the given mat3. + * + * @param {mat3} out the receiving 3x3 matrix + * @param {ReadonlyMat4} a the source 4x4 matrix + * @returns {mat3} out + */ + + function fromMat4(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[4]; + out[4] = a[5]; + out[5] = a[6]; + out[6] = a[8]; + out[7] = a[9]; + out[8] = a[10]; + return out; + } + /** + * Creates a new mat3 initialized with values from an existing matrix + * + * @param {ReadonlyMat3} a matrix to clone + * @returns {mat3} a new 3x3 matrix + */ + + function clone$2(a) { + var out = new ARRAY_TYPE(9); + 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]; + return out; + } + /** + * Copy the values from one mat3 to another + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the source matrix + * @returns {mat3} out + */ + + function copy$2(out, a) { + 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]; + return out; + } + /** + * Create a new mat3 with the given values + * + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m02 Component in column 0, row 2 position (index 2) + * @param {Number} m10 Component in column 1, row 0 position (index 3) + * @param {Number} m11 Component in column 1, row 1 position (index 4) + * @param {Number} m12 Component in column 1, row 2 position (index 5) + * @param {Number} m20 Component in column 2, row 0 position (index 6) + * @param {Number} m21 Component in column 2, row 1 position (index 7) + * @param {Number} m22 Component in column 2, row 2 position (index 8) + * @returns {mat3} A new mat3 + */ + + function fromValues$2(m00, m01, m02, m10, m11, m12, m20, m21, m22) { + var out = new ARRAY_TYPE(9); + out[0] = m00; + out[1] = m01; + out[2] = m02; + out[3] = m10; + out[4] = m11; + out[5] = m12; + out[6] = m20; + out[7] = m21; + out[8] = m22; + return out; + } + /** + * Set the components of a mat3 to the given values + * + * @param {mat3} out the receiving matrix + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m02 Component in column 0, row 2 position (index 2) + * @param {Number} m10 Component in column 1, row 0 position (index 3) + * @param {Number} m11 Component in column 1, row 1 position (index 4) + * @param {Number} m12 Component in column 1, row 2 position (index 5) + * @param {Number} m20 Component in column 2, row 0 position (index 6) + * @param {Number} m21 Component in column 2, row 1 position (index 7) + * @param {Number} m22 Component in column 2, row 2 position (index 8) + * @returns {mat3} out + */ + + function set$2(out, m00, m01, m02, m10, m11, m12, m20, m21, m22) { + out[0] = m00; + out[1] = m01; + out[2] = m02; + out[3] = m10; + out[4] = m11; + out[5] = m12; + out[6] = m20; + out[7] = m21; + out[8] = m22; + return out; + } + /** + * Set a mat3 to the identity matrix + * + * @param {mat3} out the receiving matrix + * @returns {mat3} out + */ + + function identity$2(out) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 1; + out[5] = 0; + out[6] = 0; + out[7] = 0; + out[8] = 1; + return out; + } + /** + * Transpose the values of a mat3 + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the source matrix + * @returns {mat3} out + */ + + function transpose$1(out, a) { + // If we are transposing ourselves we can skip a few steps but have to cache some values + if (out === a) { + var a01 = a[1], + a02 = a[2], + a12 = a[5]; + out[1] = a[3]; + out[2] = a[6]; + out[3] = a01; + out[5] = a[7]; + out[6] = a02; + out[7] = a12; + } else { + out[0] = a[0]; + out[1] = a[3]; + out[2] = a[6]; + out[3] = a[1]; + out[4] = a[4]; + out[5] = a[7]; + out[6] = a[2]; + out[7] = a[5]; + out[8] = a[8]; + } + + return out; + } + /** + * Inverts a mat3 + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the source matrix + * @returns {mat3} out + */ + + function invert$2(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2]; + var a10 = a[3], + a11 = a[4], + a12 = a[5]; + var a20 = a[6], + a21 = a[7], + a22 = a[8]; + var b01 = a22 * a11 - a12 * a21; + var b11 = -a22 * a10 + a12 * a20; + var b21 = a21 * a10 - a11 * a20; // Calculate the determinant + + var det = a00 * b01 + a01 * b11 + a02 * b21; + + if (!det) { + return null; + } + + det = 1.0 / det; + out[0] = b01 * det; + out[1] = (-a22 * a01 + a02 * a21) * det; + out[2] = (a12 * a01 - a02 * a11) * det; + out[3] = b11 * det; + out[4] = (a22 * a00 - a02 * a20) * det; + out[5] = (-a12 * a00 + a02 * a10) * det; + out[6] = b21 * det; + out[7] = (-a21 * a00 + a01 * a20) * det; + out[8] = (a11 * a00 - a01 * a10) * det; + return out; + } + /** + * Calculates the adjugate of a mat3 + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the source matrix + * @returns {mat3} out + */ + + function adjoint$1(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2]; + var a10 = a[3], + a11 = a[4], + a12 = a[5]; + var a20 = a[6], + a21 = a[7], + a22 = a[8]; + out[0] = a11 * a22 - a12 * a21; + out[1] = a02 * a21 - a01 * a22; + out[2] = a01 * a12 - a02 * a11; + out[3] = a12 * a20 - a10 * a22; + out[4] = a00 * a22 - a02 * a20; + out[5] = a02 * a10 - a00 * a12; + out[6] = a10 * a21 - a11 * a20; + out[7] = a01 * a20 - a00 * a21; + out[8] = a00 * a11 - a01 * a10; + return out; + } + /** + * Calculates the determinant of a mat3 + * + * @param {ReadonlyMat3} a the source matrix + * @returns {Number} determinant of a + */ + + function determinant$2(a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2]; + var a10 = a[3], + a11 = a[4], + a12 = a[5]; + var a20 = a[6], + a21 = a[7], + a22 = a[8]; + return a00 * (a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * (a21 * a10 - a11 * a20); + } + /** + * Multiplies two mat3's + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the first operand + * @param {ReadonlyMat3} b the second operand + * @returns {mat3} out + */ + + function multiply$2(out, a, b) { + var a00 = a[0], + a01 = a[1], + a02 = a[2]; + var a10 = a[3], + a11 = a[4], + a12 = a[5]; + var a20 = a[6], + a21 = a[7], + a22 = a[8]; + var b00 = b[0], + b01 = b[1], + b02 = b[2]; + var b10 = b[3], + b11 = b[4], + b12 = b[5]; + var b20 = b[6], + b21 = b[7], + b22 = b[8]; + out[0] = b00 * a00 + b01 * a10 + b02 * a20; + out[1] = b00 * a01 + b01 * a11 + b02 * a21; + out[2] = b00 * a02 + b01 * a12 + b02 * a22; + out[3] = b10 * a00 + b11 * a10 + b12 * a20; + out[4] = b10 * a01 + b11 * a11 + b12 * a21; + out[5] = b10 * a02 + b11 * a12 + b12 * a22; + out[6] = b20 * a00 + b21 * a10 + b22 * a20; + out[7] = b20 * a01 + b21 * a11 + b22 * a21; + out[8] = b20 * a02 + b21 * a12 + b22 * a22; + return out; + } + /** + * Translate a mat3 by the given vector + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the matrix to translate + * @param {ReadonlyVec2} v vector to translate by + * @returns {mat3} out + */ + + function translate$1(out, a, v) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a10 = a[3], + a11 = a[4], + a12 = a[5], + a20 = a[6], + a21 = a[7], + a22 = a[8], + x = v[0], + y = v[1]; + out[0] = a00; + out[1] = a01; + out[2] = a02; + out[3] = a10; + out[4] = a11; + out[5] = a12; + out[6] = x * a00 + y * a10 + a20; + out[7] = x * a01 + y * a11 + a21; + out[8] = x * a02 + y * a12 + a22; + return out; + } + /** + * Rotates a mat3 by the given angle + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat3} out + */ + + function rotate$2(out, a, rad) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a10 = a[3], + a11 = a[4], + a12 = a[5], + a20 = a[6], + a21 = a[7], + a22 = a[8], + s = Math.sin(rad), + c = Math.cos(rad); + out[0] = c * a00 + s * a10; + out[1] = c * a01 + s * a11; + out[2] = c * a02 + s * a12; + out[3] = c * a10 - s * a00; + out[4] = c * a11 - s * a01; + out[5] = c * a12 - s * a02; + out[6] = a20; + out[7] = a21; + out[8] = a22; + return out; + } + /** + * Scales the mat3 by the dimensions in the given vec2 + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the matrix to rotate + * @param {ReadonlyVec2} v the vec2 to scale the matrix by + * @returns {mat3} out + **/ + + function scale$2(out, a, v) { + var x = v[0], + y = v[1]; + out[0] = x * a[0]; + out[1] = x * a[1]; + out[2] = x * a[2]; + out[3] = y * a[3]; + out[4] = y * a[4]; + out[5] = y * a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + return out; + } + /** + * Creates a matrix from a vector translation + * This is equivalent to (but much faster than): + * + * mat3.identity(dest); + * mat3.translate(dest, dest, vec); + * + * @param {mat3} out mat3 receiving operation result + * @param {ReadonlyVec2} v Translation vector + * @returns {mat3} out + */ + + function fromTranslation$1(out, v) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 1; + out[5] = 0; + out[6] = v[0]; + out[7] = v[1]; + out[8] = 1; + return out; + } + /** + * Creates a matrix from a given angle + * This is equivalent to (but much faster than): + * + * mat3.identity(dest); + * mat3.rotate(dest, dest, rad); + * + * @param {mat3} out mat3 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat3} out + */ + + function fromRotation$2(out, rad) { + var s = Math.sin(rad), + c = Math.cos(rad); + out[0] = c; + out[1] = s; + out[2] = 0; + out[3] = -s; + out[4] = c; + out[5] = 0; + out[6] = 0; + out[7] = 0; + out[8] = 1; + return out; + } + /** + * Creates a matrix from a vector scaling + * This is equivalent to (but much faster than): + * + * mat3.identity(dest); + * mat3.scale(dest, dest, vec); + * + * @param {mat3} out mat3 receiving operation result + * @param {ReadonlyVec2} v Scaling vector + * @returns {mat3} out + */ + + function fromScaling$2(out, v) { + out[0] = v[0]; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = v[1]; + out[5] = 0; + out[6] = 0; + out[7] = 0; + out[8] = 1; + return out; + } + /** + * Copies the values from a mat2d into a mat3 + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat2d} a the matrix to copy + * @returns {mat3} out + **/ + + function fromMat2d(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = 0; + out[3] = a[2]; + out[4] = a[3]; + out[5] = 0; + out[6] = a[4]; + out[7] = a[5]; + out[8] = 1; + return out; + } + /** + * Calculates a 3x3 matrix from the given quaternion + * + * @param {mat3} out mat3 receiving operation result + * @param {ReadonlyQuat} q Quaternion to create matrix from + * + * @returns {mat3} out + */ + + function fromQuat(out, q) { + var x = q[0], + y = q[1], + z = q[2], + w = q[3]; + var x2 = x + x; + var y2 = y + y; + var z2 = z + z; + var xx = x * x2; + var yx = y * x2; + var yy = y * y2; + var zx = z * x2; + var zy = z * y2; + var zz = z * z2; + var wx = w * x2; + var wy = w * y2; + var wz = w * z2; + out[0] = 1 - yy - zz; + out[3] = yx - wz; + out[6] = zx + wy; + out[1] = yx + wz; + out[4] = 1 - xx - zz; + out[7] = zy - wx; + out[2] = zx - wy; + out[5] = zy + wx; + out[8] = 1 - xx - yy; + return out; + } + /** + * Calculates a 3x3 normal matrix (transpose inverse) from the 4x4 matrix + * + * @param {mat3} out mat3 receiving operation result + * @param {ReadonlyMat4} a Mat4 to derive the normal matrix from + * + * @returns {mat3} out + */ + + function normalFromMat4(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + var a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + var a30 = a[12], + a31 = a[13], + a32 = a[14], + 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.0 / det; + out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + return out; + } + /** + * Generates a 2D projection matrix with the given bounds + * + * @param {mat3} out mat3 frustum matrix will be written into + * @param {number} width Width of your gl context + * @param {number} height Height of gl context + * @returns {mat3} out + */ + + function projection(out, width, height) { + out[0] = 2 / width; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = -2 / height; + out[5] = 0; + out[6] = -1; + out[7] = 1; + out[8] = 1; + return out; + } + /** + * Returns a string representation of a mat3 + * + * @param {ReadonlyMat3} a matrix to represent as a string + * @returns {String} string representation of the matrix + */ + + function str$2(a) { + return "mat3(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ", " + a[8] + ")"; + } + /** + * Returns Frobenius norm of a mat3 + * + * @param {ReadonlyMat3} a the matrix to calculate Frobenius norm of + * @returns {Number} Frobenius norm + */ + + function frob$2(a) { + return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]); + } + /** + * Adds two mat3's + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the first operand + * @param {ReadonlyMat3} b the second operand + * @returns {mat3} out + */ + + function add$2(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + out[4] = a[4] + b[4]; + out[5] = a[5] + b[5]; + out[6] = a[6] + b[6]; + out[7] = a[7] + b[7]; + out[8] = a[8] + b[8]; + return out; + } + /** + * Subtracts matrix b from matrix a + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the first operand + * @param {ReadonlyMat3} b the second operand + * @returns {mat3} out + */ + + function subtract$2(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + out[3] = a[3] - b[3]; + out[4] = a[4] - b[4]; + out[5] = a[5] - b[5]; + out[6] = a[6] - b[6]; + out[7] = a[7] - b[7]; + out[8] = a[8] - b[8]; + return out; + } + /** + * Multiply each element of the matrix by a scalar. + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the matrix to scale + * @param {Number} b amount to scale the matrix's elements by + * @returns {mat3} out + */ + + function multiplyScalar$2(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + out[4] = a[4] * b; + out[5] = a[5] * b; + out[6] = a[6] * b; + out[7] = a[7] * b; + out[8] = a[8] * b; + return out; + } + /** + * Adds two mat3's after multiplying each element of the second operand by a scalar value. + * + * @param {mat3} out the receiving vector + * @param {ReadonlyMat3} a the first operand + * @param {ReadonlyMat3} b the second operand + * @param {Number} scale the amount to scale b's elements by before adding + * @returns {mat3} out + */ + + function multiplyScalarAndAdd$2(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + out[3] = a[3] + b[3] * scale; + out[4] = a[4] + b[4] * scale; + out[5] = a[5] + b[5] * scale; + out[6] = a[6] + b[6] * scale; + out[7] = a[7] + b[7] * scale; + out[8] = a[8] + b[8] * scale; + return out; + } + /** + * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyMat3} a The first matrix. + * @param {ReadonlyMat3} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + + function exactEquals$2(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8]; + } + /** + * Returns whether or not the matrices have approximately the same elements in the same position. + * + * @param {ReadonlyMat3} a The first matrix. + * @param {ReadonlyMat3} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + + function equals$3(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5], + a6 = a[6], + a7 = a[7], + a8 = a[8]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5], + b6 = b[6], + b7 = b[7], + b8 = b[8]; + return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)) && Math.abs(a8 - b8) <= EPSILON * Math.max(1.0, Math.abs(a8), Math.abs(b8)); + } + /** + * Alias for {@link mat3.multiply} + * @function + */ + + var mul$2 = multiply$2; + /** + * Alias for {@link mat3.subtract} + * @function + */ + + var sub$2 = subtract$2; + + var mat3 = /*#__PURE__*/Object.freeze({ + __proto__: null, + create: create$2, + fromMat4: fromMat4, + clone: clone$2, + copy: copy$2, + fromValues: fromValues$2, + set: set$2, + identity: identity$2, + transpose: transpose$1, + invert: invert$2, + adjoint: adjoint$1, + determinant: determinant$2, + multiply: multiply$2, + translate: translate$1, + rotate: rotate$2, + scale: scale$2, + fromTranslation: fromTranslation$1, + fromRotation: fromRotation$2, + fromScaling: fromScaling$2, + fromMat2d: fromMat2d, + fromQuat: fromQuat, + normalFromMat4: normalFromMat4, + projection: projection, + str: str$2, + frob: frob$2, + add: add$2, + subtract: subtract$2, + multiplyScalar: multiplyScalar$2, + multiplyScalarAndAdd: multiplyScalarAndAdd$2, + exactEquals: exactEquals$2, + equals: equals$3, + mul: mul$2, + sub: sub$2 + }); + // mat4 + /** + * 4x4 Matrix
Format: olumn-major, when typed out it looks like row-major
The matrices are being post multiplied. + * @module mat4 + */ + + /** + * Creates a new identity mat4 + * + * @returns {mat4} a new 4x4 matrix + */ + + function create$3() { + var out = new ARRAY_TYPE(16); + + if (ARRAY_TYPE != Float32Array) { + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + } + + out[0] = 1; + out[5] = 1; + out[10] = 1; + out[15] = 1; + return out; + } + /** + * Creates a new mat4 initialized with values from an existing matrix + * + * @param {ReadonlyMat4} a matrix to clone + * @returns {mat4} a new 4x4 matrix + */ + + function clone$3(a) { + var out = new ARRAY_TYPE(16); + 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 out; + } + /** + * Copy the values from one mat4 to another + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the source matrix + * @returns {mat4} out + */ + + function copy$3(out, a) { + 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 out; + } + /** + * Create a new mat4 with the given values + * + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m02 Component in column 0, row 2 position (index 2) + * @param {Number} m03 Component in column 0, row 3 position (index 3) + * @param {Number} m10 Component in column 1, row 0 position (index 4) + * @param {Number} m11 Component in column 1, row 1 position (index 5) + * @param {Number} m12 Component in column 1, row 2 position (index 6) + * @param {Number} m13 Component in column 1, row 3 position (index 7) + * @param {Number} m20 Component in column 2, row 0 position (index 8) + * @param {Number} m21 Component in column 2, row 1 position (index 9) + * @param {Number} m22 Component in column 2, row 2 position (index 10) + * @param {Number} m23 Component in column 2, row 3 position (index 11) + * @param {Number} m30 Component in column 3, row 0 position (index 12) + * @param {Number} m31 Component in column 3, row 1 position (index 13) + * @param {Number} m32 Component in column 3, row 2 position (index 14) + * @param {Number} m33 Component in column 3, row 3 position (index 15) + * @returns {mat4} A new mat4 + */ + + function fromValues$3(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) { + var out = new ARRAY_TYPE(16); + out[0] = m00; + out[1] = m01; + out[2] = m02; + out[3] = m03; + out[4] = m10; + out[5] = m11; + out[6] = m12; + out[7] = m13; + out[8] = m20; + out[9] = m21; + out[10] = m22; + out[11] = m23; + out[12] = m30; + out[13] = m31; + out[14] = m32; + out[15] = m33; + return out; + } + /** + * Set the components of a mat4 to the given values + * + * @param {mat4} out the receiving matrix + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m02 Component in column 0, row 2 position (index 2) + * @param {Number} m03 Component in column 0, row 3 position (index 3) + * @param {Number} m10 Component in column 1, row 0 position (index 4) + * @param {Number} m11 Component in column 1, row 1 position (index 5) + * @param {Number} m12 Component in column 1, row 2 position (index 6) + * @param {Number} m13 Component in column 1, row 3 position (index 7) + * @param {Number} m20 Component in column 2, row 0 position (index 8) + * @param {Number} m21 Component in column 2, row 1 position (index 9) + * @param {Number} m22 Component in column 2, row 2 position (index 10) + * @param {Number} m23 Component in column 2, row 3 position (index 11) + * @param {Number} m30 Component in column 3, row 0 position (index 12) + * @param {Number} m31 Component in column 3, row 1 position (index 13) + * @param {Number} m32 Component in column 3, row 2 position (index 14) + * @param {Number} m33 Component in column 3, row 3 position (index 15) + * @returns {mat4} out + */ + + function set$3(out, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) { + out[0] = m00; + out[1] = m01; + out[2] = m02; + out[3] = m03; + out[4] = m10; + out[5] = m11; + out[6] = m12; + out[7] = m13; + out[8] = m20; + out[9] = m21; + out[10] = m22; + out[11] = m23; + out[12] = m30; + out[13] = m31; + out[14] = m32; + out[15] = m33; + return out; + } + /** + * Set a mat4 to the identity matrix + * + * @param {mat4} out the receiving matrix + * @returns {mat4} out + */ + + function identity$3(out) { + 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 out; + } + /** + * Transpose the values of a mat4 + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the source matrix + * @returns {mat4} out + */ + + function transpose$2(out, a) { + // If we are transposing ourselves we can skip a few steps but have to cache some values + if (out === a) { + var a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a12 = a[6], + a13 = a[7]; + var a23 = a[11]; + out[1] = a[4]; + out[2] = a[8]; + out[3] = a[12]; + out[4] = a01; + out[6] = a[9]; + out[7] = a[13]; + out[8] = a02; + out[9] = a12; + out[11] = a[14]; + out[12] = a03; + out[13] = a13; + out[14] = a23; + } else { + out[0] = a[0]; + out[1] = a[4]; + out[2] = a[8]; + out[3] = a[12]; + out[4] = a[1]; + out[5] = a[5]; + out[6] = a[9]; + out[7] = a[13]; + out[8] = a[2]; + out[9] = a[6]; + out[10] = a[10]; + out[11] = a[14]; + out[12] = a[3]; + out[13] = a[7]; + out[14] = a[11]; + out[15] = a[15]; + } + + return out; + } + /** + * Inverts a mat4 + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the source matrix + * @returns {mat4} out + */ + + function invert$3(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + var a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + var a30 = a[12], + a31 = a[13], + a32 = a[14], + 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.0 / det; + out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; + out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; + out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; + out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; + out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; + out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; + out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; + return out; + } + /** + * Calculates the adjugate of a mat4 + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the source matrix + * @returns {mat4} out + */ + + function adjoint$2(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + var a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + var a30 = a[12], + a31 = a[13], + a32 = a[14], + a33 = a[15]; + out[0] = a11 * (a22 * a33 - a23 * a32) - a21 * (a12 * a33 - a13 * a32) + a31 * (a12 * a23 - a13 * a22); + out[1] = -(a01 * (a22 * a33 - a23 * a32) - a21 * (a02 * a33 - a03 * a32) + a31 * (a02 * a23 - a03 * a22)); + out[2] = a01 * (a12 * a33 - a13 * a32) - a11 * (a02 * a33 - a03 * a32) + a31 * (a02 * a13 - a03 * a12); + out[3] = -(a01 * (a12 * a23 - a13 * a22) - a11 * (a02 * a23 - a03 * a22) + a21 * (a02 * a13 - a03 * a12)); + out[4] = -(a10 * (a22 * a33 - a23 * a32) - a20 * (a12 * a33 - a13 * a32) + a30 * (a12 * a23 - a13 * a22)); + out[5] = a00 * (a22 * a33 - a23 * a32) - a20 * (a02 * a33 - a03 * a32) + a30 * (a02 * a23 - a03 * a22); + out[6] = -(a00 * (a12 * a33 - a13 * a32) - a10 * (a02 * a33 - a03 * a32) + a30 * (a02 * a13 - a03 * a12)); + out[7] = a00 * (a12 * a23 - a13 * a22) - a10 * (a02 * a23 - a03 * a22) + a20 * (a02 * a13 - a03 * a12); + out[8] = a10 * (a21 * a33 - a23 * a31) - a20 * (a11 * a33 - a13 * a31) + a30 * (a11 * a23 - a13 * a21); + out[9] = -(a00 * (a21 * a33 - a23 * a31) - a20 * (a01 * a33 - a03 * a31) + a30 * (a01 * a23 - a03 * a21)); + out[10] = a00 * (a11 * a33 - a13 * a31) - a10 * (a01 * a33 - a03 * a31) + a30 * (a01 * a13 - a03 * a11); + out[11] = -(a00 * (a11 * a23 - a13 * a21) - a10 * (a01 * a23 - a03 * a21) + a20 * (a01 * a13 - a03 * a11)); + out[12] = -(a10 * (a21 * a32 - a22 * a31) - a20 * (a11 * a32 - a12 * a31) + a30 * (a11 * a22 - a12 * a21)); + out[13] = a00 * (a21 * a32 - a22 * a31) - a20 * (a01 * a32 - a02 * a31) + a30 * (a01 * a22 - a02 * a21); + out[14] = -(a00 * (a11 * a32 - a12 * a31) - a10 * (a01 * a32 - a02 * a31) + a30 * (a01 * a12 - a02 * a11)); + out[15] = a00 * (a11 * a22 - a12 * a21) - a10 * (a01 * a22 - a02 * a21) + a20 * (a01 * a12 - a02 * a11); + return out; + } + /** + * Calculates the determinant of a mat4 + * + * @param {ReadonlyMat4} a the source matrix + * @returns {Number} determinant of a + */ + + function determinant$3(a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + var a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + var a30 = a[12], + a31 = a[13], + a32 = a[14], + 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; + } + /** + * Multiplies two mat4s + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the first operand + * @param {ReadonlyMat4} b the second operand + * @returns {mat4} out + */ + + function multiply$3(out, a, b) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + var a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + var a30 = a[12], + a31 = a[13], + a32 = a[14], + a33 = a[15]; // Cache only the current line of the second matrix + + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + b0 = b[4]; + b1 = b[5]; + b2 = b[6]; + b3 = b[7]; + out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + b0 = b[8]; + b1 = b[9]; + b2 = b[10]; + b3 = b[11]; + out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + b0 = b[12]; + b1 = b[13]; + b2 = b[14]; + b3 = b[15]; + out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + return out; + } + /** + * Translate a mat4 by the given vector + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to translate + * @param {ReadonlyVec3} v vector to translate by + * @returns {mat4} out + */ + + function translate$2(out, a, v) { + var x = v[0], + y = v[1], + z = v[2]; + var a00, a01, a02, a03; + var a10, a11, a12, a13; + var a20, a21, a22, a23; + + if (a === out) { + out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; + out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; + out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; + out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; + } else { + a00 = a[0]; + a01 = a[1]; + a02 = a[2]; + a03 = a[3]; + a10 = a[4]; + a11 = a[5]; + a12 = a[6]; + a13 = a[7]; + a20 = a[8]; + a21 = a[9]; + a22 = a[10]; + a23 = a[11]; + out[0] = a00; + out[1] = a01; + out[2] = a02; + out[3] = a03; + out[4] = a10; + out[5] = a11; + out[6] = a12; + out[7] = a13; + out[8] = a20; + out[9] = a21; + out[10] = a22; + out[11] = a23; + out[12] = a00 * x + a10 * y + a20 * z + a[12]; + out[13] = a01 * x + a11 * y + a21 * z + a[13]; + out[14] = a02 * x + a12 * y + a22 * z + a[14]; + out[15] = a03 * x + a13 * y + a23 * z + a[15]; + } + + return out; + } + /** + * Scales the mat4 by the dimensions in the given vec3 not using vectorization + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to scale + * @param {ReadonlyVec3} v the vec3 to scale the matrix by + * @returns {mat4} out + **/ + + function scale$3(out, a, v) { + var x = v[0], + y = v[1], + z = v[2]; + out[0] = a[0] * x; + out[1] = a[1] * x; + out[2] = a[2] * x; + out[3] = a[3] * x; + out[4] = a[4] * y; + out[5] = a[5] * y; + out[6] = a[6] * y; + out[7] = a[7] * y; + out[8] = a[8] * z; + out[9] = a[9] * z; + out[10] = a[10] * z; + out[11] = a[11] * z; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + return out; + } + /** + * Rotates a mat4 by the given angle around the given axis + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @param {ReadonlyVec3} axis the axis to rotate around + * @returns {mat4} out + */ + + function rotate$3(out, a, rad, axis) { + var x = axis[0], + y = axis[1], + z = axis[2]; + var len = Math.hypot(x, y, z); + var s, c, t; + var a00, a01, a02, a03; + var a10, a11, a12, a13; + var a20, a21, a22, a23; + var b00, b01, b02; + var b10, b11, b12; + var b20, b21, b22; + + if (len < EPSILON) { + return null; + } + + len = 1 / len; + x *= len; + y *= len; + z *= len; + s = Math.sin(rad); + c = Math.cos(rad); + t = 1 - c; + a00 = a[0]; + a01 = a[1]; + a02 = a[2]; + a03 = a[3]; + a10 = a[4]; + a11 = a[5]; + a12 = a[6]; + a13 = a[7]; + a20 = a[8]; + a21 = a[9]; + a22 = a[10]; + a23 = a[11]; // Construct the elements of the rotation matrix + + b00 = x * x * t + c; + b01 = y * x * t + z * s; + b02 = z * x * t - y * s; + b10 = x * y * t - z * s; + b11 = y * y * t + c; + b12 = z * y * t + x * s; + b20 = x * z * t + y * s; + b21 = y * z * t - x * s; + b22 = z * z * t + c; // Perform rotation-specific matrix multiplication + + out[0] = a00 * b00 + a10 * b01 + a20 * b02; + out[1] = a01 * b00 + a11 * b01 + a21 * b02; + out[2] = a02 * b00 + a12 * b01 + a22 * b02; + out[3] = a03 * b00 + a13 * b01 + a23 * b02; + out[4] = a00 * b10 + a10 * b11 + a20 * b12; + out[5] = a01 * b10 + a11 * b11 + a21 * b12; + out[6] = a02 * b10 + a12 * b11 + a22 * b12; + out[7] = a03 * b10 + a13 * b11 + a23 * b12; + out[8] = a00 * b20 + a10 * b21 + a20 * b22; + out[9] = a01 * b20 + a11 * b21 + a21 * b22; + out[10] = a02 * b20 + a12 * b21 + a22 * b22; + out[11] = a03 * b20 + a13 * b21 + a23 * b22; + + if (a !== out) { + // If the source and destination differ, copy the unchanged last row + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + } + + return out; + } + /** + * Rotates a matrix by the given angle around the X axis + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + + function rotateX(out, a, rad) { + 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]; + + if (a !== out) { + // If the source and destination differ, copy the unchanged rows + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + } // Perform axis-specific matrix multiplication + + + out[4] = a10 * c + a20 * s; + out[5] = a11 * c + a21 * s; + out[6] = a12 * c + a22 * s; + out[7] = a13 * c + a23 * s; + out[8] = a20 * c - a10 * s; + out[9] = a21 * c - a11 * s; + out[10] = a22 * c - a12 * s; + out[11] = a23 * c - a13 * s; + return out; + } + /** + * Rotates a matrix by the given angle around the Y axis + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + + function rotateY(out, a, rad) { + 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]; + + if (a !== out) { + // If the source and destination differ, copy the unchanged rows + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + } // Perform axis-specific matrix multiplication + + + out[0] = a00 * c - a20 * s; + out[1] = a01 * c - a21 * s; + out[2] = a02 * c - a22 * s; + out[3] = a03 * c - a23 * s; + out[8] = a00 * s + a20 * c; + out[9] = a01 * s + a21 * c; + out[10] = a02 * s + a22 * c; + out[11] = a03 * s + a23 * c; + return out; + } + /** + * Rotates a matrix by the given angle around the Z axis + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + + function rotateZ(out, a, rad) { + 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]; + + if (a !== out) { + // If the source and destination differ, copy the unchanged last row + 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]; + } // Perform axis-specific matrix multiplication + + + out[0] = a00 * c + a10 * s; + out[1] = a01 * c + a11 * s; + out[2] = a02 * c + a12 * s; + out[3] = a03 * c + a13 * s; + out[4] = a10 * c - a00 * s; + out[5] = a11 * c - a01 * s; + out[6] = a12 * c - a02 * s; + out[7] = a13 * c - a03 * s; + return out; + } + /** + * Creates a matrix from a vector translation + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.translate(dest, dest, vec); + * + * @param {mat4} out mat4 receiving operation result + * @param {ReadonlyVec3} v Translation vector + * @returns {mat4} out + */ + + function fromTranslation$2(out, v) { + 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] = v[0]; + out[13] = v[1]; + out[14] = v[2]; + out[15] = 1; + return out; + } + /** + * Creates a matrix from a vector scaling + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.scale(dest, dest, vec); + * + * @param {mat4} out mat4 receiving operation result + * @param {ReadonlyVec3} v Scaling vector + * @returns {mat4} out + */ + + function fromScaling$3(out, v) { + out[0] = v[0]; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = v[1]; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = v[2]; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; + } + /** + * Creates a matrix from a given angle around a given axis + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.rotate(dest, dest, rad, axis); + * + * @param {mat4} out mat4 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @param {ReadonlyVec3} axis the axis to rotate around + * @returns {mat4} out + */ + + function fromRotation$3(out, rad, axis) { + var x = axis[0], + y = axis[1], + z = axis[2]; + var len = Math.hypot(x, y, z); + var s, c, t; + + if (len < EPSILON) { + return null; + } + + len = 1 / len; + x *= len; + y *= len; + z *= len; + s = Math.sin(rad); + c = Math.cos(rad); + t = 1 - c; // Perform rotation-specific matrix multiplication + + out[0] = x * x * t + c; + out[1] = y * x * t + z * s; + out[2] = z * x * t - y * s; + out[3] = 0; + out[4] = x * y * t - z * s; + out[5] = y * y * t + c; + out[6] = z * y * t + x * s; + out[7] = 0; + out[8] = x * z * t + y * s; + out[9] = y * z * t - x * s; + out[10] = z * z * t + c; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; + } + /** + * Creates a matrix from the given angle around the X axis + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.rotateX(dest, dest, rad); + * + * @param {mat4} out mat4 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + + function fromXRotation(out, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); // Perform axis-specific matrix multiplication + + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = c; + out[6] = s; + out[7] = 0; + out[8] = 0; + out[9] = -s; + out[10] = c; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; + } + /** + * Creates a matrix from the given angle around the Y axis + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.rotateY(dest, dest, rad); + * + * @param {mat4} out mat4 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + + function fromYRotation(out, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); // Perform axis-specific matrix multiplication + + out[0] = c; + out[1] = 0; + out[2] = -s; + out[3] = 0; + out[4] = 0; + out[5] = 1; + out[6] = 0; + out[7] = 0; + out[8] = s; + out[9] = 0; + out[10] = c; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; + } + /** + * Creates a matrix from the given angle around the Z axis + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.rotateZ(dest, dest, rad); + * + * @param {mat4} out mat4 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + + function fromZRotation(out, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); // Perform axis-specific matrix multiplication + + out[0] = c; + out[1] = s; + out[2] = 0; + out[3] = 0; + out[4] = -s; + out[5] = c; + 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 out; + } + /** + * Creates a matrix from a quaternion rotation and vector translation + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.translate(dest, vec); + * let quatMat = mat4.create(); + * quat4.toMat4(quat, quatMat); + * mat4.multiply(dest, quatMat); + * + * @param {mat4} out mat4 receiving operation result + * @param {quat4} q Rotation quaternion + * @param {ReadonlyVec3} v Translation vector + * @returns {mat4} out + */ + + function fromRotationTranslation(out, q, v) { + // Quaternion math + var x = q[0], + y = q[1], + z = q[2], + w = q[3]; + 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[0]; + out[13] = v[1]; + out[14] = v[2]; + out[15] = 1; + return out; + } + /** + * Creates a new mat4 from a dual quat. + * + * @param {mat4} out Matrix + * @param {ReadonlyQuat2} a Dual Quaternion + * @returns {mat4} mat4 receiving operation result + */ + + function fromQuat2(out, a) { + var translation = new ARRAY_TYPE(3); + var bx = -a[0], + by = -a[1], + bz = -a[2], + bw = a[3], + ax = a[4], + ay = a[5], + az = a[6], + aw = a[7]; + var magnitude = bx * bx + by * by + bz * bz + bw * bw; //Only scale if it makes sense + + if (magnitude > 0) { + translation[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2 / magnitude; + translation[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2 / magnitude; + translation[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2 / magnitude; + } else { + translation[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2; + translation[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2; + translation[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2; + } + + fromRotationTranslation(out, a, translation); + return out; + } + /** + * Returns the translation vector component of a transformation + * matrix. If a matrix is built with fromRotationTranslation, + * the returned vector will be the same as the translation vector + * originally supplied. + * @param {vec3} out Vector to receive translation component + * @param {ReadonlyMat4} mat Matrix to be decomposed (input) + * @return {vec3} out + */ + + function getTranslation(out, mat) { + out[0] = mat[12]; + out[1] = mat[13]; + out[2] = mat[14]; + return out; + } + /** + * Returns the scaling factor component of a transformation + * matrix. If a matrix is built with fromRotationTranslationScale + * with a normalized Quaternion paramter, the returned vector will be + * the same as the scaling vector + * originally supplied. + * @param {vec3} out Vector to receive scaling factor component + * @param {ReadonlyMat4} mat Matrix to be decomposed (input) + * @return {vec3} out + */ + + function getScaling(out, mat) { + var m11 = mat[0]; + var m12 = mat[1]; + var m13 = mat[2]; + var m21 = mat[4]; + var m22 = mat[5]; + var m23 = mat[6]; + var m31 = mat[8]; + var m32 = mat[9]; + var m33 = mat[10]; + out[0] = Math.hypot(m11, m12, m13); + out[1] = Math.hypot(m21, m22, m23); + out[2] = Math.hypot(m31, m32, m33); + return out; + } + /** + * Returns a quaternion representing the rotational component + * of a transformation matrix. If a matrix is built with + * fromRotationTranslation, the returned quaternion will be the + * same as the quaternion originally supplied. + * @param {quat} out Quaternion to receive the rotation component + * @param {ReadonlyMat4} mat Matrix to be decomposed (input) + * @return {quat} out + */ + + function getRotation(out, mat) { + var scaling = new ARRAY_TYPE(3); + getScaling(scaling, mat); + var is1 = 1 / scaling[0]; + var is2 = 1 / scaling[1]; + var is3 = 1 / scaling[2]; + var sm11 = mat[0] * is1; + var sm12 = mat[1] * is2; + var sm13 = mat[2] * is3; + var sm21 = mat[4] * is1; + var sm22 = mat[5] * is2; + var sm23 = mat[6] * is3; + var sm31 = mat[8] * is1; + var sm32 = mat[9] * is2; + var sm33 = mat[10] * is3; + var trace = sm11 + sm22 + sm33; + var S = 0; + + if (trace > 0) { + S = Math.sqrt(trace + 1.0) * 2; + out[3] = 0.25 * S; + out[0] = (sm23 - sm32) / S; + out[1] = (sm31 - sm13) / S; + out[2] = (sm12 - sm21) / S; + } else if (sm11 > sm22 && sm11 > sm33) { + S = Math.sqrt(1.0 + sm11 - sm22 - sm33) * 2; + out[3] = (sm23 - sm32) / S; + out[0] = 0.25 * S; + out[1] = (sm12 + sm21) / S; + out[2] = (sm31 + sm13) / S; + } else if (sm22 > sm33) { + S = Math.sqrt(1.0 + sm22 - sm11 - sm33) * 2; + out[3] = (sm31 - sm13) / S; + out[0] = (sm12 + sm21) / S; + out[1] = 0.25 * S; + out[2] = (sm23 + sm32) / S; + } else { + S = Math.sqrt(1.0 + sm33 - sm11 - sm22) * 2; + out[3] = (sm12 - sm21) / S; + out[0] = (sm31 + sm13) / S; + out[1] = (sm23 + sm32) / S; + out[2] = 0.25 * S; + } + + return out; + } + /** + * Creates a matrix from a quaternion rotation, vector translation and vector scale + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.translate(dest, vec); + * let quatMat = mat4.create(); + * quat4.toMat4(quat, quatMat); + * mat4.multiply(dest, quatMat); + * mat4.scale(dest, scale) + * + * @param {mat4} out mat4 receiving operation result + * @param {quat4} q Rotation quaternion + * @param {ReadonlyVec3} v Translation vector + * @param {ReadonlyVec3} s Scaling vector + * @returns {mat4} out + */ + + function fromRotationTranslationScale(out, q, v, s) { + // Quaternion math + var x = q[0], + y = q[1], + z = q[2], + w = q[3]; + 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; + var sx = s[0]; + var sy = s[1]; + var sz = s[2]; + out[0] = (1 - (yy + zz)) * sx; + out[1] = (xy + wz) * sx; + out[2] = (xz - wy) * sx; + out[3] = 0; + out[4] = (xy - wz) * sy; + out[5] = (1 - (xx + zz)) * sy; + out[6] = (yz + wx) * sy; + out[7] = 0; + out[8] = (xz + wy) * sz; + out[9] = (yz - wx) * sz; + out[10] = (1 - (xx + yy)) * sz; + out[11] = 0; + out[12] = v[0]; + out[13] = v[1]; + out[14] = v[2]; + out[15] = 1; + return out; + } + /** + * Creates a matrix from a quaternion rotation, vector translation and vector scale, rotating and scaling around the given origin + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.translate(dest, vec); + * mat4.translate(dest, origin); + * let quatMat = mat4.create(); + * quat4.toMat4(quat, quatMat); + * mat4.multiply(dest, quatMat); + * mat4.scale(dest, scale) + * mat4.translate(dest, negativeOrigin); + * + * @param {mat4} out mat4 receiving operation result + * @param {quat4} q Rotation quaternion + * @param {ReadonlyVec3} v Translation vector + * @param {ReadonlyVec3} s Scaling vector + * @param {ReadonlyVec3} o The origin vector around which to scale and rotate + * @returns {mat4} out + */ + + function fromRotationTranslationScaleOrigin(out, q, v, s, o) { + // Quaternion math + var x = q[0], + y = q[1], + z = q[2], + w = q[3]; + 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; + var sx = s[0]; + var sy = s[1]; + var sz = s[2]; + var ox = o[0]; + var oy = o[1]; + var oz = o[2]; + var out0 = (1 - (yy + zz)) * sx; + var out1 = (xy + wz) * sx; + var out2 = (xz - wy) * sx; + var out4 = (xy - wz) * sy; + var out5 = (1 - (xx + zz)) * sy; + var out6 = (yz + wx) * sy; + var out8 = (xz + wy) * sz; + var out9 = (yz - wx) * sz; + var out10 = (1 - (xx + yy)) * sz; + out[0] = out0; + out[1] = out1; + out[2] = out2; + out[3] = 0; + out[4] = out4; + out[5] = out5; + out[6] = out6; + out[7] = 0; + out[8] = out8; + out[9] = out9; + out[10] = out10; + out[11] = 0; + out[12] = v[0] + ox - (out0 * ox + out4 * oy + out8 * oz); + out[13] = v[1] + oy - (out1 * ox + out5 * oy + out9 * oz); + out[14] = v[2] + oz - (out2 * ox + out6 * oy + out10 * oz); + out[15] = 1; + return out; + } + /** + * Calculates a 4x4 matrix from the given quaternion + * + * @param {mat4} out mat4 receiving operation result + * @param {ReadonlyQuat} q Quaternion to create matrix from + * + * @returns {mat4} out + */ + + function fromQuat$1(out, q) { + var x = q[0], + y = q[1], + z = q[2], + w = q[3]; + var x2 = x + x; + var y2 = y + y; + var z2 = z + z; + var xx = x * x2; + var yx = y * x2; + var yy = y * y2; + var zx = z * x2; + var zy = z * y2; + var zz = z * z2; + var wx = w * x2; + var wy = w * y2; + var wz = w * z2; + out[0] = 1 - yy - zz; + out[1] = yx + wz; + out[2] = zx - wy; + out[3] = 0; + out[4] = yx - wz; + out[5] = 1 - xx - zz; + out[6] = zy + wx; + out[7] = 0; + out[8] = zx + wy; + out[9] = zy - wx; + out[10] = 1 - xx - yy; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; + } + /** + * Generates a frustum matrix with the given bounds + * + * @param {mat4} out mat4 frustum matrix will be written into + * @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 {mat4} out + */ + + function frustum(out, left, right, bottom, top, near, far) { + 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 out; + } + /** + * Generates a perspective projection matrix with the given bounds. + * The near/far clip planes correspond to a normalized device coordinate Z range of [-1, 1], + * which matches WebGL/OpenGL's clip volume. + * Passing null/undefined/no value for far will generate infinite projection matrix. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @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, can be null or Infinity + * @returns {mat4} out + */ + + function perspectiveNO(out, fovy, aspect, near, far) { + var f = 1.0 / Math.tan(fovy / 2), + nf; + 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[11] = -1; + out[12] = 0; + out[13] = 0; + out[15] = 0; + + if (far != null && far !== Infinity) { + nf = 1 / (near - far); + out[10] = (far + near) * nf; + out[14] = 2 * far * near * nf; + } else { + out[10] = -1; + out[14] = -2 * near; + } + + return out; + } + /** + * Alias for {@link mat4.perspectiveNO} + * @function + */ + + var perspective = perspectiveNO; + /** + * Generates a perspective projection matrix suitable for WebGPU with the given bounds. + * The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1], + * which matches WebGPU/Vulkan/DirectX/Metal's clip volume. + * Passing null/undefined/no value for far will generate infinite projection matrix. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @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, can be null or Infinity + * @returns {mat4} out + */ + + function perspectiveZO(out, fovy, aspect, near, far) { + var f = 1.0 / Math.tan(fovy / 2), + nf; + 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[11] = -1; + out[12] = 0; + out[13] = 0; + out[15] = 0; + + if (far != null && far !== Infinity) { + nf = 1 / (near - far); + out[10] = far * nf; + out[14] = far * near * nf; + } else { + out[10] = -1; + out[14] = -near; + } + + return out; + } + /** + * Generates a perspective projection matrix with the given field of view. + * This is primarily useful for generating projection matrices to be used + * with the still experiemental WebVR API. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {Object} fov Object containing the following values: upDegrees, downDegrees, leftDegrees, rightDegrees + * @param {number} near Near bound of the frustum + * @param {number} far Far bound of the frustum + * @returns {mat4} out + */ + + function perspectiveFromFieldOfView(out, fov, near, far) { + var upTan = Math.tan(fov.upDegrees * Math.PI / 180.0); + var downTan = Math.tan(fov.downDegrees * Math.PI / 180.0); + var leftTan = Math.tan(fov.leftDegrees * Math.PI / 180.0); + var rightTan = Math.tan(fov.rightDegrees * Math.PI / 180.0); + var xScale = 2.0 / (leftTan + rightTan); + var yScale = 2.0 / (upTan + downTan); + out[0] = xScale; + out[1] = 0.0; + out[2] = 0.0; + out[3] = 0.0; + out[4] = 0.0; + out[5] = yScale; + out[6] = 0.0; + out[7] = 0.0; + out[8] = -((leftTan - rightTan) * xScale * 0.5); + out[9] = (upTan - downTan) * yScale * 0.5; + out[10] = far / (near - far); + out[11] = -1.0; + out[12] = 0.0; + out[13] = 0.0; + out[14] = far * near / (near - far); + out[15] = 0.0; + return out; + } + /** + * Generates a orthogonal projection matrix with the given bounds. + * The near/far clip planes correspond to a normalized device coordinate Z range of [-1, 1], + * which matches WebGL/OpenGL's clip volume. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @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 {mat4} out + */ + + function orthoNO(out, left, right, bottom, top, near, far) { + var lr = 1 / (left - right); + var bt = 1 / (bottom - top); + var nf = 1 / (near - far); + 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 out; + } + /** + * Alias for {@link mat4.orthoNO} + * @function + */ + + var ortho = orthoNO; + /** + * Generates a orthogonal projection matrix with the given bounds. + * The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1], + * which matches WebGPU/Vulkan/DirectX/Metal's clip volume. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @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 {mat4} out + */ + + function orthoZO(out, left, right, bottom, top, near, far) { + var lr = 1 / (left - right); + var bt = 1 / (bottom - top); + var nf = 1 / (near - far); + 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] = nf; + out[11] = 0; + out[12] = (left + right) * lr; + out[13] = (top + bottom) * bt; + out[14] = near * nf; + out[15] = 1; + return out; + } + /** + * Generates a look-at matrix with the given eye position, focal point, and up axis. + * If you want a matrix that actually makes an object look at another object, you should use targetTo instead. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {ReadonlyVec3} eye Position of the viewer + * @param {ReadonlyVec3} center Point the viewer is looking at + * @param {ReadonlyVec3} up vec3 pointing up + * @returns {mat4} out + */ + + function lookAt(out, eye, center, up) { + var x0, x1, x2, y0, y1, y2, z0, z1, z2, len; + var eyex = eye[0]; + var eyey = eye[1]; + var eyez = eye[2]; + var upx = up[0]; + var upy = up[1]; + var upz = up[2]; + var centerx = center[0]; + var centery = center[1]; + var centerz = center[2]; + + if (Math.abs(eyex - centerx) < EPSILON && Math.abs(eyey - centery) < EPSILON && Math.abs(eyez - centerz) < EPSILON) { + return identity$3(out); + } + + z0 = eyex - centerx; + z1 = eyey - centery; + z2 = eyez - centerz; + len = 1 / Math.hypot(z0, z1, z2); + z0 *= len; + z1 *= len; + z2 *= len; + x0 = upy * z2 - upz * z1; + x1 = upz * z0 - upx * z2; + x2 = upx * z1 - upy * z0; + len = Math.hypot(x0, x1, x2); + + if (!len) { + x0 = 0; + x1 = 0; + x2 = 0; + } else { + len = 1 / len; + x0 *= len; + x1 *= len; + x2 *= len; + } + + y0 = z1 * x2 - z2 * x1; + y1 = z2 * x0 - z0 * x2; + y2 = z0 * x1 - z1 * x0; + len = Math.hypot(y0, y1, 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 out; + } + /** + * Generates a matrix that makes something look at something else. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {ReadonlyVec3} eye Position of the viewer + * @param {ReadonlyVec3} center Point the viewer is looking at + * @param {ReadonlyVec3} up vec3 pointing up + * @returns {mat4} out + */ + + function targetTo(out, eye, target, up) { + var eyex = eye[0], + eyey = eye[1], + eyez = eye[2], + upx = up[0], + upy = up[1], + upz = up[2]; + var z0 = eyex - target[0], + z1 = eyey - target[1], + z2 = eyez - target[2]; + var len = z0 * z0 + z1 * z1 + z2 * z2; + + if (len > 0) { + len = 1 / Math.sqrt(len); + z0 *= len; + z1 *= len; + z2 *= len; + } + + var x0 = upy * z2 - upz * z1, + x1 = upz * z0 - upx * z2, + x2 = upx * z1 - upy * z0; + len = x0 * x0 + x1 * x1 + x2 * x2; + + if (len > 0) { + len = 1 / Math.sqrt(len); + x0 *= len; + x1 *= len; + x2 *= len; + } + + out[0] = x0; + out[1] = x1; + out[2] = x2; + out[3] = 0; + out[4] = z1 * x2 - z2 * x1; + out[5] = z2 * x0 - z0 * x2; + out[6] = z0 * x1 - z1 * x0; + out[7] = 0; + out[8] = z0; + out[9] = z1; + out[10] = z2; + out[11] = 0; + out[12] = eyex; + out[13] = eyey; + out[14] = eyez; + out[15] = 1; + return out; + } + /** + * Returns a string representation of a mat4 + * + * @param {ReadonlyMat4} a matrix to represent as a string + * @returns {String} string representation of the matrix + */ + + function str$3(a) { + return "mat4(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ", " + a[8] + ", " + a[9] + ", " + a[10] + ", " + a[11] + ", " + a[12] + ", " + a[13] + ", " + a[14] + ", " + a[15] + ")"; + } + /** + * Returns Frobenius norm of a mat4 + * + * @param {ReadonlyMat4} a the matrix to calculate Frobenius norm of + * @returns {Number} Frobenius norm + */ + + function frob$3(a) { + return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); + } + /** + * Adds two mat4's + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the first operand + * @param {ReadonlyMat4} b the second operand + * @returns {mat4} out + */ + + function add$3(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + out[4] = a[4] + b[4]; + out[5] = a[5] + b[5]; + out[6] = a[6] + b[6]; + out[7] = a[7] + b[7]; + out[8] = a[8] + b[8]; + out[9] = a[9] + b[9]; + out[10] = a[10] + b[10]; + out[11] = a[11] + b[11]; + out[12] = a[12] + b[12]; + out[13] = a[13] + b[13]; + out[14] = a[14] + b[14]; + out[15] = a[15] + b[15]; + return out; + } + /** + * Subtracts matrix b from matrix a + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the first operand + * @param {ReadonlyMat4} b the second operand + * @returns {mat4} out + */ + + function subtract$3(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + out[3] = a[3] - b[3]; + out[4] = a[4] - b[4]; + out[5] = a[5] - b[5]; + out[6] = a[6] - b[6]; + out[7] = a[7] - b[7]; + out[8] = a[8] - b[8]; + out[9] = a[9] - b[9]; + out[10] = a[10] - b[10]; + out[11] = a[11] - b[11]; + out[12] = a[12] - b[12]; + out[13] = a[13] - b[13]; + out[14] = a[14] - b[14]; + out[15] = a[15] - b[15]; + return out; + } + /** + * Multiply each element of the matrix by a scalar. + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to scale + * @param {Number} b amount to scale the matrix's elements by + * @returns {mat4} out + */ + + function multiplyScalar$3(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + out[4] = a[4] * b; + out[5] = a[5] * b; + out[6] = a[6] * b; + out[7] = a[7] * b; + out[8] = a[8] * b; + out[9] = a[9] * b; + out[10] = a[10] * b; + out[11] = a[11] * b; + out[12] = a[12] * b; + out[13] = a[13] * b; + out[14] = a[14] * b; + out[15] = a[15] * b; + return out; + } + /** + * Adds two mat4's after multiplying each element of the second operand by a scalar value. + * + * @param {mat4} out the receiving vector + * @param {ReadonlyMat4} a the first operand + * @param {ReadonlyMat4} b the second operand + * @param {Number} scale the amount to scale b's elements by before adding + * @returns {mat4} out + */ + + function multiplyScalarAndAdd$3(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + out[3] = a[3] + b[3] * scale; + out[4] = a[4] + b[4] * scale; + out[5] = a[5] + b[5] * scale; + out[6] = a[6] + b[6] * scale; + out[7] = a[7] + b[7] * scale; + out[8] = a[8] + b[8] * scale; + out[9] = a[9] + b[9] * scale; + out[10] = a[10] + b[10] * scale; + out[11] = a[11] + b[11] * scale; + out[12] = a[12] + b[12] * scale; + out[13] = a[13] + b[13] * scale; + out[14] = a[14] + b[14] * scale; + out[15] = a[15] + b[15] * scale; + return out; + } + /** + * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyMat4} a The first matrix. + * @param {ReadonlyMat4} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + + function exactEquals$3(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8] && a[9] === b[9] && a[10] === b[10] && a[11] === b[11] && a[12] === b[12] && a[13] === b[13] && a[14] === b[14] && a[15] === b[15]; + } + /** + * Returns whether or not the matrices have approximately the same elements in the same position. + * + * @param {ReadonlyMat4} a The first matrix. + * @param {ReadonlyMat4} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + + function equals$4(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var a4 = a[4], + a5 = a[5], + a6 = a[6], + a7 = a[7]; + var a8 = a[8], + a9 = a[9], + a10 = a[10], + a11 = a[11]; + var a12 = a[12], + a13 = a[13], + a14 = a[14], + a15 = a[15]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + var b4 = b[4], + b5 = b[5], + b6 = b[6], + b7 = b[7]; + var b8 = b[8], + b9 = b[9], + b10 = b[10], + b11 = b[11]; + var b12 = b[12], + b13 = b[13], + b14 = b[14], + b15 = b[15]; + return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)) && Math.abs(a8 - b8) <= EPSILON * Math.max(1.0, Math.abs(a8), Math.abs(b8)) && Math.abs(a9 - b9) <= EPSILON * Math.max(1.0, Math.abs(a9), Math.abs(b9)) && Math.abs(a10 - b10) <= EPSILON * Math.max(1.0, Math.abs(a10), Math.abs(b10)) && Math.abs(a11 - b11) <= EPSILON * Math.max(1.0, Math.abs(a11), Math.abs(b11)) && Math.abs(a12 - b12) <= EPSILON * Math.max(1.0, Math.abs(a12), Math.abs(b12)) && Math.abs(a13 - b13) <= EPSILON * Math.max(1.0, Math.abs(a13), Math.abs(b13)) && Math.abs(a14 - b14) <= EPSILON * Math.max(1.0, Math.abs(a14), Math.abs(b14)) && Math.abs(a15 - b15) <= EPSILON * Math.max(1.0, Math.abs(a15), Math.abs(b15)); + } + /** + * Alias for {@link mat4.multiply} + * @function + */ + + var mul$3 = multiply$3; + /** + * Alias for {@link mat4.subtract} + * @function + */ + + var sub$3 = subtract$3; + // after mat4 + var mat4 = /*#__PURE__*/Object.freeze({ + __proto__: null, + create: create$3, + clone: clone$3, + copy: copy$3, + fromValues: fromValues$3, + set: set$3, + identity: identity$3, + transpose: transpose$2, + invert: invert$3, + adjoint: adjoint$2, + determinant: determinant$3, + multiply: multiply$3, + translate: translate$2, + scale: scale$3, + rotate: rotate$3, + rotateX: rotateX, + rotateY: rotateY, + rotateZ: rotateZ, + fromTranslation: fromTranslation$2, + fromScaling: fromScaling$3, + fromRotation: fromRotation$3, + fromXRotation: fromXRotation, + fromYRotation: fromYRotation, + fromZRotation: fromZRotation, + fromRotationTranslation: fromRotationTranslation, + fromQuat2: fromQuat2, + getTranslation: getTranslation, + getScaling: getScaling, + getRotation: getRotation, + fromRotationTranslationScale: fromRotationTranslationScale, + fromRotationTranslationScaleOrigin: fromRotationTranslationScaleOrigin, + fromQuat: fromQuat$1, + frustum: frustum, + perspectiveNO: perspectiveNO, + perspective: perspective, + perspectiveZO: perspectiveZO, + perspectiveFromFieldOfView: perspectiveFromFieldOfView, + orthoNO: orthoNO, + ortho: ortho, + orthoZO: orthoZO, + lookAt: lookAt, + targetTo: targetTo, + str: str$3, + frob: frob$3, + add: add$3, + subtract: subtract$3, + multiplyScalar: multiplyScalar$3, + multiplyScalarAndAdd: multiplyScalarAndAdd$3, + exactEquals: exactEquals$3, + equals: equals$4, + mul: mul$3, + sub: sub$3 + }); + // end functions + /** + * 3 Dimensional Vector + * @module vec3 + */ + + /** + * Creates a new, empty vec3 + * + * @returns {vec3} a new 3D vector + */ + + function create$4() { + var out = new ARRAY_TYPE(3); + + if (ARRAY_TYPE != Float32Array) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + } + + return out; + } + /** + * Creates a new vec3 initialized with values from an existing vector + * + * @param {ReadonlyVec3} a vector to clone + * @returns {vec3} a new 3D vector + */ + + function clone$4(a) { + var out = new ARRAY_TYPE(3); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + return out; + } + /** + * Calculates the length of a vec3 + * + * @param {ReadonlyVec3} a vector to calculate length of + * @returns {Number} length of a + */ + + function length(a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + return Math.hypot(x, y, z); + } + /** + * Creates a new vec3 initialized with the given values + * + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @returns {vec3} a new 3D vector + */ + + function fromValues$4(x, y, z) { + var out = new ARRAY_TYPE(3); + out[0] = x; + out[1] = y; + out[2] = z; + return out; + } + /** + * Copy the values from one vec3 to another + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the source vector + * @returns {vec3} out + */ + + function copy$4(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + return out; + } + /** + * Set the components of a vec3 to the given values + * + * @param {vec3} out the receiving vector + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @returns {vec3} out + */ + + function set$4(out, x, y, z) { + out[0] = x; + out[1] = y; + out[2] = z; + return out; + } + /** + * Adds two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + + function add$4(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + return out; + } + /** + * Subtracts vector b from vector a + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + + function subtract$4(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + return out; + } + /** + * Multiplies two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + + function multiply$4(out, a, b) { + out[0] = a[0] * b[0]; + out[1] = a[1] * b[1]; + out[2] = a[2] * b[2]; + return out; + } + /** + * Divides two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + + function divide(out, a, b) { + out[0] = a[0] / b[0]; + out[1] = a[1] / b[1]; + out[2] = a[2] / b[2]; + return out; + } + /** + * Math.ceil the components of a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to ceil + * @returns {vec3} out + */ + + function ceil(out, a) { + out[0] = Math.ceil(a[0]); + out[1] = Math.ceil(a[1]); + out[2] = Math.ceil(a[2]); + return out; + } + /** + * Math.floor the components of a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to floor + * @returns {vec3} out + */ + + function floor(out, a) { + out[0] = Math.floor(a[0]); + out[1] = Math.floor(a[1]); + out[2] = Math.floor(a[2]); + return out; + } + /** + * Returns the minimum of two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + + function min(out, a, b) { + out[0] = Math.min(a[0], b[0]); + out[1] = Math.min(a[1], b[1]); + out[2] = Math.min(a[2], b[2]); + return out; + } + /** + * Returns the maximum of two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + + function max(out, a, b) { + out[0] = Math.max(a[0], b[0]); + out[1] = Math.max(a[1], b[1]); + out[2] = Math.max(a[2], b[2]); + return out; + } + /** + * Math.round the components of a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to round + * @returns {vec3} out + */ + + function round(out, a) { + out[0] = Math.round(a[0]); + out[1] = Math.round(a[1]); + out[2] = Math.round(a[2]); + return out; + } + /** + * Scales a vec3 by a scalar number + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the vector to scale + * @param {Number} b amount to scale the vector by + * @returns {vec3} out + */ + + function scale$4(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + return out; + } + /** + * Adds two vec3's after scaling the second operand by a scalar value + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @param {Number} scale the amount to scale b by before adding + * @returns {vec3} out + */ + + function scaleAndAdd(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + return out; + } + /** + * Calculates the euclidian distance between two vec3's + * + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {Number} distance between a and b + */ + + function distance(a, b) { + var x = b[0] - a[0]; + var y = b[1] - a[1]; + var z = b[2] - a[2]; + return Math.hypot(x, y, z); + } + /** + * Calculates the squared euclidian distance between two vec3's + * + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {Number} squared distance between a and b + */ + + function squaredDistance(a, b) { + var x = b[0] - a[0]; + var y = b[1] - a[1]; + var z = b[2] - a[2]; + return x * x + y * y + z * z; + } + /** + * Calculates the squared length of a vec3 + * + * @param {ReadonlyVec3} a vector to calculate squared length of + * @returns {Number} squared length of a + */ + + function squaredLength(a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + return x * x + y * y + z * z; + } + /** + * Negates the components of a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to negate + * @returns {vec3} out + */ + + function negate(out, a) { + out[0] = -a[0]; + out[1] = -a[1]; + out[2] = -a[2]; + return out; + } + /** + * Returns the inverse of the components of a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to invert + * @returns {vec3} out + */ + + function inverse(out, a) { + out[0] = 1.0 / a[0]; + out[1] = 1.0 / a[1]; + out[2] = 1.0 / a[2]; + return out; + } + /** + * Normalize a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to normalize + * @returns {vec3} out + */ + + function normalize(out, a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + var len = x * x + y * y + z * z; + + if (len > 0) { + //TODO: evaluate use of glm_invsqrt here? + len = 1 / Math.sqrt(len); + } + + out[0] = a[0] * len; + out[1] = a[1] * len; + out[2] = a[2] * len; + return out; + } + /** + * Calculates the dot product of two vec3's + * + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {Number} dot product of a and b + */ + + function dot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + /** + * Computes the cross product of two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + + function cross(out, a, b) { + var ax = a[0], + ay = a[1], + az = a[2]; + var bx = b[0], + by = b[1], + bz = b[2]; + out[0] = ay * bz - az * by; + out[1] = az * bx - ax * bz; + out[2] = ax * by - ay * bx; + return out; + } + /** + * Performs a linear interpolation between two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec3} out + */ + + function lerp(out, a, b, t) { + var ax = a[0]; + var ay = a[1]; + var az = a[2]; + out[0] = ax + t * (b[0] - ax); + out[1] = ay + t * (b[1] - ay); + out[2] = az + t * (b[2] - az); + return out; + } + /** + * Performs a hermite interpolation with two control points + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @param {ReadonlyVec3} c the third operand + * @param {ReadonlyVec3} d the fourth operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec3} out + */ + + function hermite(out, a, b, c, d, t) { + var factorTimes2 = t * t; + var factor1 = factorTimes2 * (2 * t - 3) + 1; + var factor2 = factorTimes2 * (t - 2) + t; + var factor3 = factorTimes2 * (t - 1); + var factor4 = factorTimes2 * (3 - 2 * t); + out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4; + out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4; + out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4; + return out; + } + /** + * Performs a bezier interpolation with two control points + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @param {ReadonlyVec3} c the third operand + * @param {ReadonlyVec3} d the fourth operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec3} out + */ + + function bezier(out, a, b, c, d, t) { + var inverseFactor = 1 - t; + var inverseFactorTimesTwo = inverseFactor * inverseFactor; + var factorTimes2 = t * t; + var factor1 = inverseFactorTimesTwo * inverseFactor; + var factor2 = 3 * t * inverseFactorTimesTwo; + var factor3 = 3 * factorTimes2 * inverseFactor; + var factor4 = factorTimes2 * t; + out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4; + out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4; + out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4; + return out; + } + /** + * Generates a random vector with the given scale + * + * @param {vec3} out the receiving vector + * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned + * @returns {vec3} out + */ + + function random(out, scale) { + scale = scale || 1.0; + var r = RANDOM() * 2.0 * Math.PI; + var z = RANDOM() * 2.0 - 1.0; + var zScale = Math.sqrt(1.0 - z * z) * scale; + out[0] = Math.cos(r) * zScale; + out[1] = Math.sin(r) * zScale; + out[2] = z * scale; + return out; + } + /** + * Transforms the vec3 with a mat4. + * 4th vector component is implicitly '1' + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the vector to transform + * @param {ReadonlyMat4} m matrix to transform with + * @returns {vec3} out + */ + + function transformMat4(out, a, m) { + var x = a[0], + y = a[1], + z = a[2]; + var w = m[3] * x + m[7] * y + m[11] * z + m[15]; + w = w || 1.0; + out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w; + out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w; + out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w; + return out; + } + /** + * Transforms the vec3 with a mat3. + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the vector to transform + * @param {ReadonlyMat3} m the 3x3 matrix to transform with + * @returns {vec3} out + */ + + function transformMat3(out, a, m) { + var x = a[0], + y = a[1], + z = a[2]; + out[0] = x * m[0] + y * m[3] + z * m[6]; + out[1] = x * m[1] + y * m[4] + z * m[7]; + out[2] = x * m[2] + y * m[5] + z * m[8]; + return out; + } + /** + * Transforms the vec3 with a quat + * Can also be used for dual quaternions. (Multiply it with the real part) + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the vector to transform + * @param {ReadonlyQuat} q quaternion to transform with + * @returns {vec3} out + */ + + function transformQuat(out, a, q) { + // benchmarks: https://jsperf.com/quaternion-transform-vec3-implementations-fixed + var qx = q[0], + qy = q[1], + qz = q[2], + qw = q[3]; + var x = a[0], + y = a[1], + z = a[2]; // var qvec = [qx, qy, qz]; + // var uv = vec3.cross([], qvec, a); + + var uvx = qy * z - qz * y, + uvy = qz * x - qx * z, + uvz = qx * y - qy * x; // var uuv = vec3.cross([], qvec, uv); + + var uuvx = qy * uvz - qz * uvy, + uuvy = qz * uvx - qx * uvz, + uuvz = qx * uvy - qy * uvx; // vec3.scale(uv, uv, 2 * w); + + var w2 = qw * 2; + uvx *= w2; + uvy *= w2; + uvz *= w2; // vec3.scale(uuv, uuv, 2); + + uuvx *= 2; + uuvy *= 2; + uuvz *= 2; // return vec3.add(out, a, vec3.add(out, uv, uuv)); + + out[0] = x + uvx + uuvx; + out[1] = y + uvy + uuvy; + out[2] = z + uvz + uuvz; + return out; + } + /** + * Rotate a 3D vector around the x-axis + * @param {vec3} out The receiving vec3 + * @param {ReadonlyVec3} a The vec3 point to rotate + * @param {ReadonlyVec3} b The origin of the rotation + * @param {Number} rad The angle of rotation in radians + * @returns {vec3} out + */ + + function rotateX$1(out, a, b, rad) { + var p = [], + r = []; //Translate point to the origin + + p[0] = a[0] - b[0]; + p[1] = a[1] - b[1]; + p[2] = a[2] - b[2]; //perform rotation + + r[0] = p[0]; + r[1] = p[1] * Math.cos(rad) - p[2] * Math.sin(rad); + r[2] = p[1] * Math.sin(rad) + p[2] * Math.cos(rad); //translate to correct position + + out[0] = r[0] + b[0]; + out[1] = r[1] + b[1]; + out[2] = r[2] + b[2]; + return out; + } + /** + * Rotate a 3D vector around the y-axis + * @param {vec3} out The receiving vec3 + * @param {ReadonlyVec3} a The vec3 point to rotate + * @param {ReadonlyVec3} b The origin of the rotation + * @param {Number} rad The angle of rotation in radians + * @returns {vec3} out + */ + + function rotateY$1(out, a, b, rad) { + var p = [], + r = []; //Translate point to the origin + + p[0] = a[0] - b[0]; + p[1] = a[1] - b[1]; + p[2] = a[2] - b[2]; //perform rotation + + r[0] = p[2] * Math.sin(rad) + p[0] * Math.cos(rad); + r[1] = p[1]; + r[2] = p[2] * Math.cos(rad) - p[0] * Math.sin(rad); //translate to correct position + + out[0] = r[0] + b[0]; + out[1] = r[1] + b[1]; + out[2] = r[2] + b[2]; + return out; + } + /** + * Rotate a 3D vector around the z-axis + * @param {vec3} out The receiving vec3 + * @param {ReadonlyVec3} a The vec3 point to rotate + * @param {ReadonlyVec3} b The origin of the rotation + * @param {Number} rad The angle of rotation in radians + * @returns {vec3} out + */ + + function rotateZ$1(out, a, b, rad) { + var p = [], + r = []; //Translate point to the origin + + p[0] = a[0] - b[0]; + p[1] = a[1] - b[1]; + p[2] = a[2] - b[2]; //perform rotation + + r[0] = p[0] * Math.cos(rad) - p[1] * Math.sin(rad); + r[1] = p[0] * Math.sin(rad) + p[1] * Math.cos(rad); + r[2] = p[2]; //translate to correct position + + out[0] = r[0] + b[0]; + out[1] = r[1] + b[1]; + out[2] = r[2] + b[2]; + return out; + } + /** + * Get the angle between two 3D vectors + * @param {ReadonlyVec3} a The first operand + * @param {ReadonlyVec3} b The second operand + * @returns {Number} The angle in radians + */ + + function angle(a, b) { + var ax = a[0], + ay = a[1], + az = a[2], + bx = b[0], + by = b[1], + bz = b[2], + mag1 = Math.sqrt(ax * ax + ay * ay + az * az), + mag2 = Math.sqrt(bx * bx + by * by + bz * bz), + mag = mag1 * mag2, + cosine = mag && dot(a, b) / mag; + return Math.acos(Math.min(Math.max(cosine, -1), 1)); + } + /** + * Set the components of a vec3 to zero + * + * @param {vec3} out the receiving vector + * @returns {vec3} out + */ + + function zero(out) { + out[0] = 0.0; + out[1] = 0.0; + out[2] = 0.0; + return out; + } + /** + * Returns a string representation of a vector + * + * @param {ReadonlyVec3} a vector to represent as a string + * @returns {String} string representation of the vector + */ + + function str$4(a) { + return "vec3(" + a[0] + ", " + a[1] + ", " + a[2] + ")"; + } + /** + * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyVec3} a The first vector. + * @param {ReadonlyVec3} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + + function exactEquals$4(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2]; + } + /** + * Returns whether or not the vectors have approximately the same elements in the same position. + * + * @param {ReadonlyVec3} a The first vector. + * @param {ReadonlyVec3} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + + function equals$5(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2]; + var b0 = b[0], + b1 = b[1], + b2 = b[2]; + return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)); + } + /** + * Alias for {@link vec3.subtract} + * @function + */ + + var sub$4 = subtract$4; + /** + * Alias for {@link vec3.multiply} + * @function + */ + + var mul$4 = multiply$4; + /** + * Alias for {@link vec3.divide} + * @function + */ + + var div = divide; + /** + * Alias for {@link vec3.distance} + * @function + */ + + var dist = distance; + /** + * Alias for {@link vec3.squaredDistance} + * @function + */ + + var sqrDist = squaredDistance; + /** + * Alias for {@link vec3.length} + * @function + */ + + var len = length; + /** + * Alias for {@link vec3.squaredLength} + * @function + */ + + var sqrLen = squaredLength; + /** + * Perform some operation over an array of vec3s. + * + * @param {Array} a the array of vectors to iterate over + * @param {Number} stride Number of elements between the start of each vec3. If 0 assumes tightly packed + * @param {Number} offset Number of elements to skip at the beginning of the array + * @param {Number} count Number of vec3s to iterate over. If 0 iterates over entire array + * @param {Function} fn Function to call for each vector in the array + * @param {Object} [arg] additional argument to pass to fn + * @returns {Array} a + * @function + */ + + var forEach = function () { + var vec = create$4(); + return function (a, stride, offset, count, fn, arg) { + var i, l; + + if (!stride) { + stride = 3; + } + + if (!offset) { + offset = 0; + } + + if (count) { + l = Math.min(count * stride + offset, a.length); + } else { + l = a.length; + } + + for (i = offset; i < l; i += stride) { + vec[0] = a[i]; + vec[1] = a[i + 1]; + vec[2] = a[i + 2]; + fn(vec, vec, arg); + a[i] = vec[0]; + a[i + 1] = vec[1]; + a[i + 2] = vec[2]; + } + + return a; + }; + }(); + + var vec3 = /*#__PURE__*/Object.freeze({ + __proto__: null, + create: create$4, + clone: clone$4, + length: length, + fromValues: fromValues$4, + copy: copy$4, + set: set$4, + add: add$4, + subtract: subtract$4, + multiply: multiply$4, + divide: divide, + ceil: ceil, + floor: floor, + min: min, + max: max, + round: round, + scale: scale$4, + scaleAndAdd: scaleAndAdd, + distance: distance, + squaredDistance: squaredDistance, + squaredLength: squaredLength, + negate: negate, + inverse: inverse, + normalize: normalize, + dot: dot, + cross: cross, + lerp: lerp, + hermite: hermite, + bezier: bezier, + random: random, + transformMat4: transformMat4, + transformMat3: transformMat3, + transformQuat: transformQuat, + rotateX: rotateX$1, + rotateY: rotateY$1, + rotateZ: rotateZ$1, + angle: angle, + zero: zero, + str: str$4, + exactEquals: exactEquals$4, + equals: equals$5, + sub: sub$4, + mul: mul$4, + div: div, + dist: dist, + sqrDist: sqrDist, + len: len, + sqrLen: sqrLen, + forEach: forEach + }); + + /** + * 4 Dimensional Vector + * @module vec4 + */ + + /** + * Creates a new, empty vec4 + * + * @returns {vec4} a new 4D vector + */ + + function create$5() { + var out = new ARRAY_TYPE(4); + + if (ARRAY_TYPE != Float32Array) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 0; + } + + return out; + } + /** + * Creates a new vec4 initialized with values from an existing vector + * + * @param {ReadonlyVec4} a vector to clone + * @returns {vec4} a new 4D vector + */ + + function clone$5(a) { + var out = new ARRAY_TYPE(4); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; + } + /** + * Creates a new vec4 initialized with the given values + * + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @param {Number} w W component + * @returns {vec4} a new 4D vector + */ + + function fromValues$5(x, y, z, w) { + var out = new ARRAY_TYPE(4); + out[0] = x; + out[1] = y; + out[2] = z; + out[3] = w; + return out; + } + /** + * Copy the values from one vec4 to another + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the source vector + * @returns {vec4} out + */ + + function copy$5(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; + } + /** + * Set the components of a vec4 to the given values + * + * @param {vec4} out the receiving vector + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @param {Number} w W component + * @returns {vec4} out + */ + + function set$5(out, x, y, z, w) { + out[0] = x; + out[1] = y; + out[2] = z; + out[3] = w; + return out; + } + /** + * Adds two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + + function add$5(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + return out; + } + /** + * Subtracts vector b from vector a + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + + function subtract$5(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + out[3] = a[3] - b[3]; + return out; + } + /** + * Multiplies two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + + function multiply$5(out, a, b) { + out[0] = a[0] * b[0]; + out[1] = a[1] * b[1]; + out[2] = a[2] * b[2]; + out[3] = a[3] * b[3]; + return out; + } + /** + * Divides two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + + function divide$1(out, a, b) { + out[0] = a[0] / b[0]; + out[1] = a[1] / b[1]; + out[2] = a[2] / b[2]; + out[3] = a[3] / b[3]; + return out; + } + /** + * Math.ceil the components of a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to ceil + * @returns {vec4} out + */ + + function ceil$1(out, a) { + out[0] = Math.ceil(a[0]); + out[1] = Math.ceil(a[1]); + out[2] = Math.ceil(a[2]); + out[3] = Math.ceil(a[3]); + return out; + } + /** + * Math.floor the components of a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to floor + * @returns {vec4} out + */ + + function floor$1(out, a) { + out[0] = Math.floor(a[0]); + out[1] = Math.floor(a[1]); + out[2] = Math.floor(a[2]); + out[3] = Math.floor(a[3]); + return out; + } + /** + * Returns the minimum of two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + + function min$1(out, a, b) { + out[0] = Math.min(a[0], b[0]); + out[1] = Math.min(a[1], b[1]); + out[2] = Math.min(a[2], b[2]); + out[3] = Math.min(a[3], b[3]); + return out; + } + /** + * Returns the maximum of two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + + function max$1(out, a, b) { + out[0] = Math.max(a[0], b[0]); + out[1] = Math.max(a[1], b[1]); + out[2] = Math.max(a[2], b[2]); + out[3] = Math.max(a[3], b[3]); + return out; + } + /** + * Math.round the components of a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to round + * @returns {vec4} out + */ + + function round$1(out, a) { + out[0] = Math.round(a[0]); + out[1] = Math.round(a[1]); + out[2] = Math.round(a[2]); + out[3] = Math.round(a[3]); + return out; + } + /** + * Scales a vec4 by a scalar number + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the vector to scale + * @param {Number} b amount to scale the vector by + * @returns {vec4} out + */ + + function scale$5(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + return out; + } + /** + * Adds two vec4's after scaling the second operand by a scalar value + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @param {Number} scale the amount to scale b by before adding + * @returns {vec4} out + */ + + function scaleAndAdd$1(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + out[3] = a[3] + b[3] * scale; + return out; + } + /** + * Calculates the euclidian distance between two vec4's + * + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {Number} distance between a and b + */ + + function distance$1(a, b) { + var x = b[0] - a[0]; + var y = b[1] - a[1]; + var z = b[2] - a[2]; + var w = b[3] - a[3]; + return Math.hypot(x, y, z, w); + } + /** + * Calculates the squared euclidian distance between two vec4's + * + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {Number} squared distance between a and b + */ + + function squaredDistance$1(a, b) { + var x = b[0] - a[0]; + var y = b[1] - a[1]; + var z = b[2] - a[2]; + var w = b[3] - a[3]; + return x * x + y * y + z * z + w * w; + } + /** + * Calculates the length of a vec4 + * + * @param {ReadonlyVec4} a vector to calculate length of + * @returns {Number} length of a + */ + + function length$1(a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + var w = a[3]; + return Math.hypot(x, y, z, w); + } + /** + * Calculates the squared length of a vec4 + * + * @param {ReadonlyVec4} a vector to calculate squared length of + * @returns {Number} squared length of a + */ + + function squaredLength$1(a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + var w = a[3]; + return x * x + y * y + z * z + w * w; + } + /** + * Negates the components of a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to negate + * @returns {vec4} out + */ + + function negate$1(out, a) { + out[0] = -a[0]; + out[1] = -a[1]; + out[2] = -a[2]; + out[3] = -a[3]; + return out; + } + /** + * Returns the inverse of the components of a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to invert + * @returns {vec4} out + */ + + function inverse$1(out, a) { + out[0] = 1.0 / a[0]; + out[1] = 1.0 / a[1]; + out[2] = 1.0 / a[2]; + out[3] = 1.0 / a[3]; + return out; + } + /** + * Normalize a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to normalize + * @returns {vec4} out + */ + + function normalize$1(out, a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + var w = a[3]; + var len = x * x + y * y + z * z + w * w; + + if (len > 0) { + len = 1 / Math.sqrt(len); + } + + out[0] = x * len; + out[1] = y * len; + out[2] = z * len; + out[3] = w * len; + return out; + } + /** + * Calculates the dot product of two vec4's + * + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {Number} dot product of a and b + */ + + function dot$1(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + /** + * Returns the cross-product of three vectors in a 4-dimensional space + * + * @param {ReadonlyVec4} result the receiving vector + * @param {ReadonlyVec4} U the first vector + * @param {ReadonlyVec4} V the second vector + * @param {ReadonlyVec4} W the third vector + * @returns {vec4} result + */ + + function cross$1(out, u, v, w) { + var A = v[0] * w[1] - v[1] * w[0], + B = v[0] * w[2] - v[2] * w[0], + C = v[0] * w[3] - v[3] * w[0], + D = v[1] * w[2] - v[2] * w[1], + E = v[1] * w[3] - v[3] * w[1], + F = v[2] * w[3] - v[3] * w[2]; + var G = u[0]; + var H = u[1]; + var I = u[2]; + var J = u[3]; + out[0] = H * F - I * E + J * D; + out[1] = -(G * F) + I * C - J * B; + out[2] = G * E - H * C + J * A; + out[3] = -(G * D) + H * B - I * A; + return out; + } + /** + * Performs a linear interpolation between two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec4} out + */ + + function lerp$1(out, a, b, t) { + var ax = a[0]; + var ay = a[1]; + var az = a[2]; + var aw = a[3]; + out[0] = ax + t * (b[0] - ax); + out[1] = ay + t * (b[1] - ay); + out[2] = az + t * (b[2] - az); + out[3] = aw + t * (b[3] - aw); + return out; + } + /** + * Generates a random vector with the given scale + * + * @param {vec4} out the receiving vector + * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned + * @returns {vec4} out + */ + + function random$1(out, scale) { + scale = scale || 1.0; // Marsaglia, George. Choosing a Point from the Surface of a + // Sphere. Ann. Math. Statist. 43 (1972), no. 2, 645--646. + // http://projecteuclid.org/euclid.aoms/1177692644; + + var v1, v2, v3, v4; + var s1, s2; + + do { + v1 = RANDOM() * 2 - 1; + v2 = RANDOM() * 2 - 1; + s1 = v1 * v1 + v2 * v2; + } while (s1 >= 1); + + do { + v3 = RANDOM() * 2 - 1; + v4 = RANDOM() * 2 - 1; + s2 = v3 * v3 + v4 * v4; + } while (s2 >= 1); + + var d = Math.sqrt((1 - s1) / s2); + out[0] = scale * v1; + out[1] = scale * v2; + out[2] = scale * v3 * d; + out[3] = scale * v4 * d; + return out; + } + /** + * Transforms the vec4 with a mat4. + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the vector to transform + * @param {ReadonlyMat4} m matrix to transform with + * @returns {vec4} out + */ + + function transformMat4$1(out, a, m) { + var x = a[0], + y = a[1], + z = a[2], + w = a[3]; + out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w; + out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w; + out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w; + out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w; + return out; + } + /** + * Transforms the vec4 with a quat + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the vector to transform + * @param {ReadonlyQuat} q quaternion to transform with + * @returns {vec4} out + */ + + function transformQuat$1(out, a, q) { + var x = a[0], + y = a[1], + z = a[2]; + var qx = q[0], + qy = q[1], + qz = q[2], + qw = q[3]; // calculate quat * vec + + var ix = qw * x + qy * z - qz * y; + var iy = qw * y + qz * x - qx * z; + var iz = qw * z + qx * y - qy * x; + var iw = -qx * x - qy * y - qz * z; // calculate result * inverse quat + + out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy; + out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz; + out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx; + out[3] = a[3]; + return out; + } + /** + * Set the components of a vec4 to zero + * + * @param {vec4} out the receiving vector + * @returns {vec4} out + */ + + function zero$1(out) { + out[0] = 0.0; + out[1] = 0.0; + out[2] = 0.0; + out[3] = 0.0; + return out; + } + /** + * Returns a string representation of a vector + * + * @param {ReadonlyVec4} a vector to represent as a string + * @returns {String} string representation of the vector + */ + + function str$5(a) { + return "vec4(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")"; + } + /** + * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyVec4} a The first vector. + * @param {ReadonlyVec4} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + + function exactEquals$5(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]; + } + /** + * Returns whether or not the vectors have approximately the same elements in the same position. + * + * @param {ReadonlyVec4} a The first vector. + * @param {ReadonlyVec4} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + + function equals$6(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)); + } + /** + * Alias for {@link vec4.subtract} + * @function + */ + + var sub$5 = subtract$5; + /** + * Alias for {@link vec4.multiply} + * @function + */ + + var mul$5 = multiply$5; + /** + * Alias for {@link vec4.divide} + * @function + */ + + var div$1 = divide$1; + /** + * Alias for {@link vec4.distance} + * @function + */ + + var dist$1 = distance$1; + /** + * Alias for {@link vec4.squaredDistance} + * @function + */ + + var sqrDist$1 = squaredDistance$1; + /** + * Alias for {@link vec4.length} + * @function + */ + + var len$1 = length$1; + /** + * Alias for {@link vec4.squaredLength} + * @function + */ + + var sqrLen$1 = squaredLength$1; + /** + * Perform some operation over an array of vec4s. + * + * @param {Array} a the array of vectors to iterate over + * @param {Number} stride Number of elements between the start of each vec4. If 0 assumes tightly packed + * @param {Number} offset Number of elements to skip at the beginning of the array + * @param {Number} count Number of vec4s to iterate over. If 0 iterates over entire array + * @param {Function} fn Function to call for each vector in the array + * @param {Object} [arg] additional argument to pass to fn + * @returns {Array} a + * @function + */ + + var forEach$1 = function () { + var vec = create$5(); + return function (a, stride, offset, count, fn, arg) { + var i, l; + + if (!stride) { + stride = 4; + } + + if (!offset) { + offset = 0; + } + + if (count) { + l = Math.min(count * stride + offset, a.length); + } else { + l = a.length; + } + + for (i = offset; i < l; i += stride) { + vec[0] = a[i]; + vec[1] = a[i + 1]; + vec[2] = a[i + 2]; + vec[3] = a[i + 3]; + fn(vec, vec, arg); + a[i] = vec[0]; + a[i + 1] = vec[1]; + a[i + 2] = vec[2]; + a[i + 3] = vec[3]; + } + + return a; + }; + }(); + + var vec4 = /*#__PURE__*/Object.freeze({ + __proto__: null, + create: create$5, + clone: clone$5, + fromValues: fromValues$5, + copy: copy$5, + set: set$5, + add: add$5, + subtract: subtract$5, + multiply: multiply$5, + divide: divide$1, + ceil: ceil$1, + floor: floor$1, + min: min$1, + max: max$1, + round: round$1, + scale: scale$5, + scaleAndAdd: scaleAndAdd$1, + distance: distance$1, + squaredDistance: squaredDistance$1, + length: length$1, + squaredLength: squaredLength$1, + negate: negate$1, + inverse: inverse$1, + normalize: normalize$1, + dot: dot$1, + cross: cross$1, + lerp: lerp$1, + random: random$1, + transformMat4: transformMat4$1, + transformQuat: transformQuat$1, + zero: zero$1, + str: str$5, + exactEquals: exactEquals$5, + equals: equals$6, + sub: sub$5, + mul: mul$5, + div: div$1, + dist: dist$1, + sqrDist: sqrDist$1, + len: len$1, + sqrLen: sqrLen$1, + forEach: forEach$1 + }); + + /** + * Quaternion + * @module quat + */ + + /** + * Creates a new identity quat + * + * @returns {quat} a new quaternion + */ + + function create$6() { + var out = new ARRAY_TYPE(4); + + if (ARRAY_TYPE != Float32Array) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + } + + out[3] = 1; + return out; + } + /** + * Set a quat to the identity quaternion + * + * @param {quat} out the receiving quaternion + * @returns {quat} out + */ + + function identity$4(out) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 1; + return out; + } + /** + * Sets a quat from the given angle and rotation axis, + * then returns it. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyVec3} axis the axis around which to rotate + * @param {Number} rad the angle in radians + * @returns {quat} out + **/ + + function setAxisAngle(out, axis, rad) { + rad = rad * 0.5; + var s = Math.sin(rad); + out[0] = s * axis[0]; + out[1] = s * axis[1]; + out[2] = s * axis[2]; + out[3] = Math.cos(rad); + return out; + } + /** + * Gets the rotation axis and angle for a given + * quaternion. If a quaternion is created with + * setAxisAngle, this method will return the same + * values as providied in the original parameter list + * OR functionally equivalent values. + * Example: The quaternion formed by axis [0, 0, 1] and + * angle -90 is the same as the quaternion formed by + * [0, 0, 1] and 270. This method favors the latter. + * @param {vec3} out_axis Vector receiving the axis of rotation + * @param {ReadonlyQuat} q Quaternion to be decomposed + * @return {Number} Angle, in radians, of the rotation + */ + + function getAxisAngle(out_axis, q) { + var rad = Math.acos(q[3]) * 2.0; + var s = Math.sin(rad / 2.0); + + if (s > EPSILON) { + out_axis[0] = q[0] / s; + out_axis[1] = q[1] / s; + out_axis[2] = q[2] / s; + } else { + // If s is zero, return any axis (no rotation - axis does not matter) + out_axis[0] = 1; + out_axis[1] = 0; + out_axis[2] = 0; + } + + return rad; + } + /** + * Gets the angular distance between two unit quaternions + * + * @param {ReadonlyQuat} a Origin unit quaternion + * @param {ReadonlyQuat} b Destination unit quaternion + * @return {Number} Angle, in radians, between the two quaternions + */ + + function getAngle(a, b) { + var dotproduct = dot$2(a, b); + return Math.acos(2 * dotproduct * dotproduct - 1); + } + /** + * Multiplies two quat's + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @returns {quat} out + */ + + function multiply$6(out, a, b) { + var ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + var bx = b[0], + by = b[1], + bz = b[2], + bw = b[3]; + out[0] = ax * bw + aw * bx + ay * bz - az * by; + out[1] = ay * bw + aw * by + az * bx - ax * bz; + out[2] = az * bw + aw * bz + ax * by - ay * bx; + out[3] = aw * bw - ax * bx - ay * by - az * bz; + return out; + } + /** + * Rotates a quaternion by the given angle about the X axis + * + * @param {quat} out quat receiving operation result + * @param {ReadonlyQuat} a quat to rotate + * @param {number} rad angle (in radians) to rotate + * @returns {quat} out + */ + + function rotateX$2(out, a, rad) { + rad *= 0.5; + var ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + var bx = Math.sin(rad), + bw = Math.cos(rad); + out[0] = ax * bw + aw * bx; + out[1] = ay * bw + az * bx; + out[2] = az * bw - ay * bx; + out[3] = aw * bw - ax * bx; + return out; + } + /** + * Rotates a quaternion by the given angle about the Y axis + * + * @param {quat} out quat receiving operation result + * @param {ReadonlyQuat} a quat to rotate + * @param {number} rad angle (in radians) to rotate + * @returns {quat} out + */ + + function rotateY$2(out, a, rad) { + rad *= 0.5; + var ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + var by = Math.sin(rad), + bw = Math.cos(rad); + out[0] = ax * bw - az * by; + out[1] = ay * bw + aw * by; + out[2] = az * bw + ax * by; + out[3] = aw * bw - ay * by; + return out; + } + /** + * Rotates a quaternion by the given angle about the Z axis + * + * @param {quat} out quat receiving operation result + * @param {ReadonlyQuat} a quat to rotate + * @param {number} rad angle (in radians) to rotate + * @returns {quat} out + */ + + function rotateZ$2(out, a, rad) { + rad *= 0.5; + var ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + var bz = Math.sin(rad), + bw = Math.cos(rad); + out[0] = ax * bw + ay * bz; + out[1] = ay * bw - ax * bz; + out[2] = az * bw + aw * bz; + out[3] = aw * bw - az * bz; + return out; + } + /** + * Calculates the W component of a quat from the X, Y, and Z components. + * Assumes that quaternion is 1 unit in length. + * Any existing W component will be ignored. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate W component of + * @returns {quat} out + */ + + function calculateW(out, a) { + var x = a[0], + y = a[1], + z = a[2]; + out[0] = x; + out[1] = y; + out[2] = z; + out[3] = Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z)); + return out; + } + /** + * Calculate the exponential of a unit quaternion. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate the exponential of + * @returns {quat} out + */ + + function exp(out, a) { + var x = a[0], + y = a[1], + z = a[2], + w = a[3]; + var r = Math.sqrt(x * x + y * y + z * z); + var et = Math.exp(w); + var s = r > 0 ? et * Math.sin(r) / r : 0; + out[0] = x * s; + out[1] = y * s; + out[2] = z * s; + out[3] = et * Math.cos(r); + return out; + } + /** + * Calculate the natural logarithm of a unit quaternion. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate the exponential of + * @returns {quat} out + */ + + function ln(out, a) { + var x = a[0], + y = a[1], + z = a[2], + w = a[3]; + var r = Math.sqrt(x * x + y * y + z * z); + var t = r > 0 ? Math.atan2(r, w) / r : 0; + out[0] = x * t; + out[1] = y * t; + out[2] = z * t; + out[3] = 0.5 * Math.log(x * x + y * y + z * z + w * w); + return out; + } + /** + * Calculate the scalar power of a unit quaternion. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate the exponential of + * @param {Number} b amount to scale the quaternion by + * @returns {quat} out + */ + + function pow(out, a, b) { + ln(out, a); + scale$6(out, out, b); + exp(out, out); + return out; + } + /** + * Performs a spherical linear interpolation between two quat + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {quat} out + */ + + function slerp(out, a, b, t) { + // benchmarks: + // http://jsperf.com/quaternion-slerp-implementations + var ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + var bx = b[0], + by = b[1], + bz = b[2], + bw = b[3]; + var omega, cosom, sinom, scale0, scale1; // calc cosine + + cosom = ax * bx + ay * by + az * bz + aw * bw; // adjust signs (if necessary) + + if (cosom < 0.0) { + cosom = -cosom; + bx = -bx; + by = -by; + bz = -bz; + bw = -bw; + } // calculate coefficients + + + if (1.0 - cosom > EPSILON) { + // standard case (slerp) + omega = Math.acos(cosom); + sinom = Math.sin(omega); + scale0 = Math.sin((1.0 - t) * omega) / sinom; + scale1 = Math.sin(t * omega) / sinom; + } else { + // "from" and "to" quaternions are very close + // ... so we can do a linear interpolation + scale0 = 1.0 - t; + scale1 = t; + } // calculate final values + + + out[0] = scale0 * ax + scale1 * bx; + out[1] = scale0 * ay + scale1 * by; + out[2] = scale0 * az + scale1 * bz; + out[3] = scale0 * aw + scale1 * bw; + return out; + } + /** + * Generates a random unit quaternion + * + * @param {quat} out the receiving quaternion + * @returns {quat} out + */ + + function random$2(out) { + // Implementation of http://planning.cs.uiuc.edu/node198.html + // TODO: Calling random 3 times is probably not the fastest solution + var u1 = RANDOM(); + var u2 = RANDOM(); + var u3 = RANDOM(); + var sqrt1MinusU1 = Math.sqrt(1 - u1); + var sqrtU1 = Math.sqrt(u1); + out[0] = sqrt1MinusU1 * Math.sin(2.0 * Math.PI * u2); + out[1] = sqrt1MinusU1 * Math.cos(2.0 * Math.PI * u2); + out[2] = sqrtU1 * Math.sin(2.0 * Math.PI * u3); + out[3] = sqrtU1 * Math.cos(2.0 * Math.PI * u3); + return out; + } + /** + * Calculates the inverse of a quat + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate inverse of + * @returns {quat} out + */ + + function invert$4(out, a) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var dot = a0 * a0 + a1 * a1 + a2 * a2 + a3 * a3; + var invDot = dot ? 1.0 / dot : 0; // TODO: Would be faster to return [0,0,0,0] immediately if dot == 0 + + out[0] = -a0 * invDot; + out[1] = -a1 * invDot; + out[2] = -a2 * invDot; + out[3] = a3 * invDot; + return out; + } + /** + * Calculates the conjugate of a quat + * If the quaternion is normalized, this function is faster than quat.inverse and produces the same result. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate conjugate of + * @returns {quat} out + */ + + function conjugate(out, a) { + out[0] = -a[0]; + out[1] = -a[1]; + out[2] = -a[2]; + out[3] = a[3]; + return out; + } + /** + * Creates a quaternion from the given 3x3 rotation matrix. + * + * NOTE: The resultant quaternion is not normalized, so you should be sure + * to renormalize the quaternion yourself where necessary. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyMat3} m rotation matrix + * @returns {quat} out + * @function + */ + + function fromMat3(out, m) { + // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes + // article "Quaternion Calculus and Fast Animation". + var fTrace = m[0] + m[4] + m[8]; + var fRoot; + + if (fTrace > 0.0) { + // |w| > 1/2, may as well choose w > 1/2 + fRoot = Math.sqrt(fTrace + 1.0); // 2w + + out[3] = 0.5 * fRoot; + fRoot = 0.5 / fRoot; // 1/(4w) + + out[0] = (m[5] - m[7]) * fRoot; + out[1] = (m[6] - m[2]) * fRoot; + out[2] = (m[1] - m[3]) * fRoot; + } else { + // |w| <= 1/2 + var i = 0; + if (m[4] > m[0]) i = 1; + if (m[8] > m[i * 3 + i]) i = 2; + var j = (i + 1) % 3; + var k = (i + 2) % 3; + fRoot = Math.sqrt(m[i * 3 + i] - m[j * 3 + j] - m[k * 3 + k] + 1.0); + out[i] = 0.5 * fRoot; + fRoot = 0.5 / fRoot; + out[3] = (m[j * 3 + k] - m[k * 3 + j]) * fRoot; + out[j] = (m[j * 3 + i] + m[i * 3 + j]) * fRoot; + out[k] = (m[k * 3 + i] + m[i * 3 + k]) * fRoot; + } + + return out; + } + /** + * Creates a quaternion from the given euler angle x, y, z. + * + * @param {quat} out the receiving quaternion + * @param {x} Angle to rotate around X axis in degrees. + * @param {y} Angle to rotate around Y axis in degrees. + * @param {z} Angle to rotate around Z axis in degrees. + * @returns {quat} out + * @function + */ + + function fromEuler(out, x, y, z) { + var halfToRad = 0.5 * Math.PI / 180.0; + x *= halfToRad; + y *= halfToRad; + z *= halfToRad; + var sx = Math.sin(x); + var cx = Math.cos(x); + var sy = Math.sin(y); + var cy = Math.cos(y); + var sz = Math.sin(z); + var cz = Math.cos(z); + out[0] = sx * cy * cz - cx * sy * sz; + out[1] = cx * sy * cz + sx * cy * sz; + out[2] = cx * cy * sz - sx * sy * cz; + out[3] = cx * cy * cz + sx * sy * sz; + return out; + } + /** + * Returns a string representation of a quatenion + * + * @param {ReadonlyQuat} a vector to represent as a string + * @returns {String} string representation of the vector + */ + + function str$6(a) { + return "quat(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")"; + } + /** + * Creates a new quat initialized with values from an existing quaternion + * + * @param {ReadonlyQuat} a quaternion to clone + * @returns {quat} a new quaternion + * @function + */ + + var clone$6 = clone$5; + /** + * Creates a new quat initialized with the given values + * + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @param {Number} w W component + * @returns {quat} a new quaternion + * @function + */ + + var fromValues$6 = fromValues$5; + /** + * Copy the values from one quat to another + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the source quaternion + * @returns {quat} out + * @function + */ + + var copy$6 = copy$5; + /** + * Set the components of a quat to the given values + * + * @param {quat} out the receiving quaternion + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @param {Number} w W component + * @returns {quat} out + * @function + */ + + var set$6 = set$5; + /** + * Adds two quat's + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @returns {quat} out + * @function + */ + + var add$6 = add$5; + /** + * Alias for {@link quat.multiply} + * @function + */ + + var mul$6 = multiply$6; + /** + * Scales a quat by a scalar number + * + * @param {quat} out the receiving vector + * @param {ReadonlyQuat} a the vector to scale + * @param {Number} b amount to scale the vector by + * @returns {quat} out + * @function + */ + + var scale$6 = scale$5; + /** + * Calculates the dot product of two quat's + * + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @returns {Number} dot product of a and b + * @function + */ + + var dot$2 = dot$1; + /** + * Performs a linear interpolation between two quat's + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {quat} out + * @function + */ + + var lerp$2 = lerp$1; + /** + * Calculates the length of a quat + * + * @param {ReadonlyQuat} a vector to calculate length of + * @returns {Number} length of a + */ + + var length$2 = length$1; + /** + * Alias for {@link quat.length} + * @function + */ + + var len$2 = length$2; + /** + * Calculates the squared length of a quat + * + * @param {ReadonlyQuat} a vector to calculate squared length of + * @returns {Number} squared length of a + * @function + */ + + var squaredLength$2 = squaredLength$1; + /** + * Alias for {@link quat.squaredLength} + * @function + */ + + var sqrLen$2 = squaredLength$2; + /** + * Normalize a quat + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quaternion to normalize + * @returns {quat} out + * @function + */ + + var normalize$2 = normalize$1; + /** + * Returns whether or not the quaternions have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyQuat} a The first quaternion. + * @param {ReadonlyQuat} b The second quaternion. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + + var exactEquals$6 = exactEquals$5; + /** + * Returns whether or not the quaternions have approximately the same elements in the same position. + * + * @param {ReadonlyQuat} a The first vector. + * @param {ReadonlyQuat} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + + var equals$7 = equals$6; + /** + * Sets a quaternion to represent the shortest rotation from one + * vector to another. + * + * Both vectors are assumed to be unit length. + * + * @param {quat} out the receiving quaternion. + * @param {ReadonlyVec3} a the initial vector + * @param {ReadonlyVec3} b the destination vector + * @returns {quat} out + */ + + var rotationTo = function () { + var tmpvec3 = create$4(); + var xUnitVec3 = fromValues$4(1, 0, 0); + var yUnitVec3 = fromValues$4(0, 1, 0); + return function (out, a, b) { + var dot$1 = dot(a, b); + + if (dot$1 < -0.999999) { + cross(tmpvec3, xUnitVec3, a); + if (len(tmpvec3) < 0.000001) cross(tmpvec3, yUnitVec3, a); + normalize(tmpvec3, tmpvec3); + setAxisAngle(out, tmpvec3, Math.PI); + return out; + } else if (dot$1 > 0.999999) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 1; + return out; + } else { + cross(tmpvec3, a, b); + out[0] = tmpvec3[0]; + out[1] = tmpvec3[1]; + out[2] = tmpvec3[2]; + out[3] = 1 + dot$1; + return normalize$2(out, out); + } + }; + }(); + /** + * Performs a spherical linear interpolation with two control points + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @param {ReadonlyQuat} c the third operand + * @param {ReadonlyQuat} d the fourth operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {quat} out + */ + + var sqlerp = function () { + var temp1 = create$6(); + var temp2 = create$6(); + return function (out, a, b, c, d, t) { + slerp(temp1, a, d, t); + slerp(temp2, b, c, t); + slerp(out, temp1, temp2, 2 * t * (1 - t)); + return out; + }; + }(); + /** + * Sets the specified quaternion with values corresponding to the given + * axes. Each axis is a vec3 and is expected to be unit length and + * perpendicular to all other specified axes. + * + * @param {ReadonlyVec3} view the vector representing the viewing direction + * @param {ReadonlyVec3} right the vector representing the local "right" direction + * @param {ReadonlyVec3} up the vector representing the local "up" direction + * @returns {quat} out + */ + + var setAxes = function () { + var matr = create$2(); + return function (out, view, right, up) { + matr[0] = right[0]; + matr[3] = right[1]; + matr[6] = right[2]; + matr[1] = up[0]; + matr[4] = up[1]; + matr[7] = up[2]; + matr[2] = -view[0]; + matr[5] = -view[1]; + matr[8] = -view[2]; + return normalize$2(out, fromMat3(out, matr)); + }; + }(); + + var quat = /*#__PURE__*/Object.freeze({ + __proto__: null, + create: create$6, + identity: identity$4, + setAxisAngle: setAxisAngle, + getAxisAngle: getAxisAngle, + getAngle: getAngle, + multiply: multiply$6, + rotateX: rotateX$2, + rotateY: rotateY$2, + rotateZ: rotateZ$2, + calculateW: calculateW, + exp: exp, + ln: ln, + pow: pow, + slerp: slerp, + random: random$2, + invert: invert$4, + conjugate: conjugate, + fromMat3: fromMat3, + fromEuler: fromEuler, + str: str$6, + clone: clone$6, + fromValues: fromValues$6, + copy: copy$6, + set: set$6, + add: add$6, + mul: mul$6, + scale: scale$6, + dot: dot$2, + lerp: lerp$2, + length: length$2, + len: len$2, + squaredLength: squaredLength$2, + sqrLen: sqrLen$2, + normalize: normalize$2, + exactEquals: exactEquals$6, + equals: equals$7, + rotationTo: rotationTo, + sqlerp: sqlerp, + setAxes: setAxes + }); + + /** + * Dual Quaternion
+ * Format: [real, dual]
+ * Quaternion format: XYZW
+ * Make sure to have normalized dual quaternions, otherwise the functions may not work as intended.
+ * @module quat2 + */ + + /** + * Creates a new identity dual quat + * + * @returns {quat2} a new dual quaternion [real -> rotation, dual -> translation] + */ + + function create$7() { + var dq = new ARRAY_TYPE(8); + + if (ARRAY_TYPE != Float32Array) { + dq[0] = 0; + dq[1] = 0; + dq[2] = 0; + dq[4] = 0; + dq[5] = 0; + dq[6] = 0; + dq[7] = 0; + } + + dq[3] = 1; + return dq; + } + /** + * Creates a new quat initialized with values from an existing quaternion + * + * @param {ReadonlyQuat2} a dual quaternion to clone + * @returns {quat2} new dual quaternion + * @function + */ + + function clone$7(a) { + var dq = new ARRAY_TYPE(8); + dq[0] = a[0]; + dq[1] = a[1]; + dq[2] = a[2]; + dq[3] = a[3]; + dq[4] = a[4]; + dq[5] = a[5]; + dq[6] = a[6]; + dq[7] = a[7]; + return dq; + } + /** + * Creates a new dual quat initialized with the given values + * + * @param {Number} x1 X component + * @param {Number} y1 Y component + * @param {Number} z1 Z component + * @param {Number} w1 W component + * @param {Number} x2 X component + * @param {Number} y2 Y component + * @param {Number} z2 Z component + * @param {Number} w2 W component + * @returns {quat2} new dual quaternion + * @function + */ + + function fromValues$7(x1, y1, z1, w1, x2, y2, z2, w2) { + var dq = new ARRAY_TYPE(8); + dq[0] = x1; + dq[1] = y1; + dq[2] = z1; + dq[3] = w1; + dq[4] = x2; + dq[5] = y2; + dq[6] = z2; + dq[7] = w2; + return dq; + } + /** + * Creates a new dual quat from the given values (quat and translation) + * + * @param {Number} x1 X component + * @param {Number} y1 Y component + * @param {Number} z1 Z component + * @param {Number} w1 W component + * @param {Number} x2 X component (translation) + * @param {Number} y2 Y component (translation) + * @param {Number} z2 Z component (translation) + * @returns {quat2} new dual quaternion + * @function + */ + + function fromRotationTranslationValues(x1, y1, z1, w1, x2, y2, z2) { + var dq = new ARRAY_TYPE(8); + dq[0] = x1; + dq[1] = y1; + dq[2] = z1; + dq[3] = w1; + var ax = x2 * 0.5, + ay = y2 * 0.5, + az = z2 * 0.5; + dq[4] = ax * w1 + ay * z1 - az * y1; + dq[5] = ay * w1 + az * x1 - ax * z1; + dq[6] = az * w1 + ax * y1 - ay * x1; + dq[7] = -ax * x1 - ay * y1 - az * z1; + return dq; + } + /** + * Creates a dual quat from a quaternion and a translation + * + * @param {ReadonlyQuat2} dual quaternion receiving operation result + * @param {ReadonlyQuat} q a normalized quaternion + * @param {ReadonlyVec3} t tranlation vector + * @returns {quat2} dual quaternion receiving operation result + * @function + */ + + function fromRotationTranslation$1(out, q, t) { + var ax = t[0] * 0.5, + ay = t[1] * 0.5, + az = t[2] * 0.5, + bx = q[0], + by = q[1], + bz = q[2], + bw = q[3]; + out[0] = bx; + out[1] = by; + out[2] = bz; + out[3] = bw; + out[4] = ax * bw + ay * bz - az * by; + out[5] = ay * bw + az * bx - ax * bz; + out[6] = az * bw + ax * by - ay * bx; + out[7] = -ax * bx - ay * by - az * bz; + return out; + } + /** + * Creates a dual quat from a translation + * + * @param {ReadonlyQuat2} dual quaternion receiving operation result + * @param {ReadonlyVec3} t translation vector + * @returns {quat2} dual quaternion receiving operation result + * @function + */ + + function fromTranslation$3(out, t) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 1; + out[4] = t[0] * 0.5; + out[5] = t[1] * 0.5; + out[6] = t[2] * 0.5; + out[7] = 0; + return out; + } + /** + * Creates a dual quat from a quaternion + * + * @param {ReadonlyQuat2} dual quaternion receiving operation result + * @param {ReadonlyQuat} q the quaternion + * @returns {quat2} dual quaternion receiving operation result + * @function + */ + + function fromRotation$4(out, q) { + out[0] = q[0]; + out[1] = q[1]; + out[2] = q[2]; + out[3] = q[3]; + out[4] = 0; + out[5] = 0; + out[6] = 0; + out[7] = 0; + return out; + } + /** + * Creates a new dual quat from a matrix (4x4) + * + * @param {quat2} out the dual quaternion + * @param {ReadonlyMat4} a the matrix + * @returns {quat2} dual quat receiving operation result + * @function + */ + + function fromMat4$1(out, a) { + //TODO Optimize this + var outer = create$6(); + getRotation(outer, a); + var t = new ARRAY_TYPE(3); + getTranslation(t, a); + fromRotationTranslation$1(out, outer, t); + return out; + } + /** + * Copy the values from one dual quat to another + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the source dual quaternion + * @returns {quat2} out + * @function + */ + + function copy$7(out, a) { + 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]; + return out; + } + /** + * Set a dual quat to the identity dual quaternion + * + * @param {quat2} out the receiving quaternion + * @returns {quat2} out + */ + + function identity$5(out) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 1; + out[4] = 0; + out[5] = 0; + out[6] = 0; + out[7] = 0; + return out; + } + /** + * Set the components of a dual quat to the given values + * + * @param {quat2} out the receiving quaternion + * @param {Number} x1 X component + * @param {Number} y1 Y component + * @param {Number} z1 Z component + * @param {Number} w1 W component + * @param {Number} x2 X component + * @param {Number} y2 Y component + * @param {Number} z2 Z component + * @param {Number} w2 W component + * @returns {quat2} out + * @function + */ + + function set$7(out, x1, y1, z1, w1, x2, y2, z2, w2) { + out[0] = x1; + out[1] = y1; + out[2] = z1; + out[3] = w1; + out[4] = x2; + out[5] = y2; + out[6] = z2; + out[7] = w2; + return out; + } + /** + * Gets the real part of a dual quat + * @param {quat} out real part + * @param {ReadonlyQuat2} a Dual Quaternion + * @return {quat} real part + */ + + var getReal = copy$6; + /** + * Gets the dual part of a dual quat + * @param {quat} out dual part + * @param {ReadonlyQuat2} a Dual Quaternion + * @return {quat} dual part + */ + + function getDual(out, a) { + out[0] = a[4]; + out[1] = a[5]; + out[2] = a[6]; + out[3] = a[7]; + return out; + } + /** + * Set the real component of a dual quat to the given quaternion + * + * @param {quat2} out the receiving quaternion + * @param {ReadonlyQuat} q a quaternion representing the real part + * @returns {quat2} out + * @function + */ + + var setReal = copy$6; + /** + * Set the dual component of a dual quat to the given quaternion + * + * @param {quat2} out the receiving quaternion + * @param {ReadonlyQuat} q a quaternion representing the dual part + * @returns {quat2} out + * @function + */ + + function setDual(out, q) { + out[4] = q[0]; + out[5] = q[1]; + out[6] = q[2]; + out[7] = q[3]; + return out; + } + /** + * Gets the translation of a normalized dual quat + * @param {vec3} out translation + * @param {ReadonlyQuat2} a Dual Quaternion to be decomposed + * @return {vec3} translation + */ + + function getTranslation$1(out, a) { + var ax = a[4], + ay = a[5], + az = a[6], + aw = a[7], + bx = -a[0], + by = -a[1], + bz = -a[2], + bw = a[3]; + out[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2; + out[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2; + out[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2; + return out; + } + /** + * Translates a dual quat by the given vector + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to translate + * @param {ReadonlyVec3} v vector to translate by + * @returns {quat2} out + */ + + function translate$3(out, a, v) { + var ax1 = a[0], + ay1 = a[1], + az1 = a[2], + aw1 = a[3], + bx1 = v[0] * 0.5, + by1 = v[1] * 0.5, + bz1 = v[2] * 0.5, + ax2 = a[4], + ay2 = a[5], + az2 = a[6], + aw2 = a[7]; + out[0] = ax1; + out[1] = ay1; + out[2] = az1; + out[3] = aw1; + out[4] = aw1 * bx1 + ay1 * bz1 - az1 * by1 + ax2; + out[5] = aw1 * by1 + az1 * bx1 - ax1 * bz1 + ay2; + out[6] = aw1 * bz1 + ax1 * by1 - ay1 * bx1 + az2; + out[7] = -ax1 * bx1 - ay1 * by1 - az1 * bz1 + aw2; + return out; + } + /** + * Rotates a dual quat around the X axis + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @param {number} rad how far should the rotation be + * @returns {quat2} out + */ + + function rotateX$3(out, a, rad) { + var bx = -a[0], + by = -a[1], + bz = -a[2], + bw = a[3], + ax = a[4], + ay = a[5], + az = a[6], + aw = a[7], + ax1 = ax * bw + aw * bx + ay * bz - az * by, + ay1 = ay * bw + aw * by + az * bx - ax * bz, + az1 = az * bw + aw * bz + ax * by - ay * bx, + aw1 = aw * bw - ax * bx - ay * by - az * bz; + rotateX$2(out, a, rad); + bx = out[0]; + by = out[1]; + bz = out[2]; + bw = out[3]; + out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by; + out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz; + out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx; + out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz; + return out; + } + /** + * Rotates a dual quat around the Y axis + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @param {number} rad how far should the rotation be + * @returns {quat2} out + */ + + function rotateY$3(out, a, rad) { + var bx = -a[0], + by = -a[1], + bz = -a[2], + bw = a[3], + ax = a[4], + ay = a[5], + az = a[6], + aw = a[7], + ax1 = ax * bw + aw * bx + ay * bz - az * by, + ay1 = ay * bw + aw * by + az * bx - ax * bz, + az1 = az * bw + aw * bz + ax * by - ay * bx, + aw1 = aw * bw - ax * bx - ay * by - az * bz; + rotateY$2(out, a, rad); + bx = out[0]; + by = out[1]; + bz = out[2]; + bw = out[3]; + out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by; + out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz; + out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx; + out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz; + return out; + } + /** + * Rotates a dual quat around the Z axis + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @param {number} rad how far should the rotation be + * @returns {quat2} out + */ + + function rotateZ$3(out, a, rad) { + var bx = -a[0], + by = -a[1], + bz = -a[2], + bw = a[3], + ax = a[4], + ay = a[5], + az = a[6], + aw = a[7], + ax1 = ax * bw + aw * bx + ay * bz - az * by, + ay1 = ay * bw + aw * by + az * bx - ax * bz, + az1 = az * bw + aw * bz + ax * by - ay * bx, + aw1 = aw * bw - ax * bx - ay * by - az * bz; + rotateZ$2(out, a, rad); + bx = out[0]; + by = out[1]; + bz = out[2]; + bw = out[3]; + out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by; + out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz; + out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx; + out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz; + return out; + } + /** + * Rotates a dual quat by a given quaternion (a * q) + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @param {ReadonlyQuat} q quaternion to rotate by + * @returns {quat2} out + */ + + function rotateByQuatAppend(out, a, q) { + var qx = q[0], + qy = q[1], + qz = q[2], + qw = q[3], + ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + out[0] = ax * qw + aw * qx + ay * qz - az * qy; + out[1] = ay * qw + aw * qy + az * qx - ax * qz; + out[2] = az * qw + aw * qz + ax * qy - ay * qx; + out[3] = aw * qw - ax * qx - ay * qy - az * qz; + ax = a[4]; + ay = a[5]; + az = a[6]; + aw = a[7]; + out[4] = ax * qw + aw * qx + ay * qz - az * qy; + out[5] = ay * qw + aw * qy + az * qx - ax * qz; + out[6] = az * qw + aw * qz + ax * qy - ay * qx; + out[7] = aw * qw - ax * qx - ay * qy - az * qz; + return out; + } + /** + * Rotates a dual quat by a given quaternion (q * a) + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat} q quaternion to rotate by + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @returns {quat2} out + */ + + function rotateByQuatPrepend(out, q, a) { + var qx = q[0], + qy = q[1], + qz = q[2], + qw = q[3], + bx = a[0], + by = a[1], + bz = a[2], + bw = a[3]; + out[0] = qx * bw + qw * bx + qy * bz - qz * by; + out[1] = qy * bw + qw * by + qz * bx - qx * bz; + out[2] = qz * bw + qw * bz + qx * by - qy * bx; + out[3] = qw * bw - qx * bx - qy * by - qz * bz; + bx = a[4]; + by = a[5]; + bz = a[6]; + bw = a[7]; + out[4] = qx * bw + qw * bx + qy * bz - qz * by; + out[5] = qy * bw + qw * by + qz * bx - qx * bz; + out[6] = qz * bw + qw * bz + qx * by - qy * bx; + out[7] = qw * bw - qx * bx - qy * by - qz * bz; + return out; + } + /** + * Rotates a dual quat around a given axis. Does the normalisation automatically + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @param {ReadonlyVec3} axis the axis to rotate around + * @param {Number} rad how far the rotation should be + * @returns {quat2} out + */ + + function rotateAroundAxis(out, a, axis, rad) { + //Special case for rad = 0 + if (Math.abs(rad) < EPSILON) { + return copy$7(out, a); + } + + var axisLength = Math.hypot(axis[0], axis[1], axis[2]); + rad = rad * 0.5; + var s = Math.sin(rad); + var bx = s * axis[0] / axisLength; + var by = s * axis[1] / axisLength; + var bz = s * axis[2] / axisLength; + var bw = Math.cos(rad); + var ax1 = a[0], + ay1 = a[1], + az1 = a[2], + aw1 = a[3]; + out[0] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by; + out[1] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz; + out[2] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx; + out[3] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz; + var ax = a[4], + ay = a[5], + az = a[6], + aw = a[7]; + out[4] = ax * bw + aw * bx + ay * bz - az * by; + out[5] = ay * bw + aw * by + az * bx - ax * bz; + out[6] = az * bw + aw * bz + ax * by - ay * bx; + out[7] = aw * bw - ax * bx - ay * by - az * bz; + return out; + } + /** + * Adds two dual quat's + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the first operand + * @param {ReadonlyQuat2} b the second operand + * @returns {quat2} out + * @function + */ + + function add$7(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + out[4] = a[4] + b[4]; + out[5] = a[5] + b[5]; + out[6] = a[6] + b[6]; + out[7] = a[7] + b[7]; + return out; + } + /** + * Multiplies two dual quat's + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the first operand + * @param {ReadonlyQuat2} b the second operand + * @returns {quat2} out + */ + + function multiply$7(out, a, b) { + var ax0 = a[0], + ay0 = a[1], + az0 = a[2], + aw0 = a[3], + bx1 = b[4], + by1 = b[5], + bz1 = b[6], + bw1 = b[7], + ax1 = a[4], + ay1 = a[5], + az1 = a[6], + aw1 = a[7], + bx0 = b[0], + by0 = b[1], + bz0 = b[2], + bw0 = b[3]; + out[0] = ax0 * bw0 + aw0 * bx0 + ay0 * bz0 - az0 * by0; + out[1] = ay0 * bw0 + aw0 * by0 + az0 * bx0 - ax0 * bz0; + out[2] = az0 * bw0 + aw0 * bz0 + ax0 * by0 - ay0 * bx0; + out[3] = aw0 * bw0 - ax0 * bx0 - ay0 * by0 - az0 * bz0; + out[4] = ax0 * bw1 + aw0 * bx1 + ay0 * bz1 - az0 * by1 + ax1 * bw0 + aw1 * bx0 + ay1 * bz0 - az1 * by0; + out[5] = ay0 * bw1 + aw0 * by1 + az0 * bx1 - ax0 * bz1 + ay1 * bw0 + aw1 * by0 + az1 * bx0 - ax1 * bz0; + out[6] = az0 * bw1 + aw0 * bz1 + ax0 * by1 - ay0 * bx1 + az1 * bw0 + aw1 * bz0 + ax1 * by0 - ay1 * bx0; + out[7] = aw0 * bw1 - ax0 * bx1 - ay0 * by1 - az0 * bz1 + aw1 * bw0 - ax1 * bx0 - ay1 * by0 - az1 * bz0; + return out; + } + /** + * Alias for {@link quat2.multiply} + * @function + */ + + var mul$7 = multiply$7; + /** + * Scales a dual quat by a scalar number + * + * @param {quat2} out the receiving dual quat + * @param {ReadonlyQuat2} a the dual quat to scale + * @param {Number} b amount to scale the dual quat by + * @returns {quat2} out + * @function + */ + + function scale$7(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + out[4] = a[4] * b; + out[5] = a[5] * b; + out[6] = a[6] * b; + out[7] = a[7] * b; + return out; + } + /** + * Calculates the dot product of two dual quat's (The dot product of the real parts) + * + * @param {ReadonlyQuat2} a the first operand + * @param {ReadonlyQuat2} b the second operand + * @returns {Number} dot product of a and b + * @function + */ + + var dot$3 = dot$2; + /** + * Performs a linear interpolation between two dual quats's + * NOTE: The resulting dual quaternions won't always be normalized (The error is most noticeable when t = 0.5) + * + * @param {quat2} out the receiving dual quat + * @param {ReadonlyQuat2} a the first operand + * @param {ReadonlyQuat2} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {quat2} out + */ + + function lerp$3(out, a, b, t) { + var mt = 1 - t; + if (dot$3(a, b) < 0) t = -t; + out[0] = a[0] * mt + b[0] * t; + out[1] = a[1] * mt + b[1] * t; + out[2] = a[2] * mt + b[2] * t; + out[3] = a[3] * mt + b[3] * t; + out[4] = a[4] * mt + b[4] * t; + out[5] = a[5] * mt + b[5] * t; + out[6] = a[6] * mt + b[6] * t; + out[7] = a[7] * mt + b[7] * t; + return out; + } + /** + * Calculates the inverse of a dual quat. If they are normalized, conjugate is cheaper + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a dual quat to calculate inverse of + * @returns {quat2} out + */ + + function invert$5(out, a) { + var sqlen = squaredLength$3(a); + out[0] = -a[0] / sqlen; + out[1] = -a[1] / sqlen; + out[2] = -a[2] / sqlen; + out[3] = a[3] / sqlen; + out[4] = -a[4] / sqlen; + out[5] = -a[5] / sqlen; + out[6] = -a[6] / sqlen; + out[7] = a[7] / sqlen; + return out; + } + /** + * Calculates the conjugate of a dual quat + * If the dual quaternion is normalized, this function is faster than quat2.inverse and produces the same result. + * + * @param {quat2} out the receiving quaternion + * @param {ReadonlyQuat2} a quat to calculate conjugate of + * @returns {quat2} out + */ + + function conjugate$1(out, a) { + 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]; + return out; + } + /** + * Calculates the length of a dual quat + * + * @param {ReadonlyQuat2} a dual quat to calculate length of + * @returns {Number} length of a + * @function + */ + + var length$3 = length$2; + /** + * Alias for {@link quat2.length} + * @function + */ + + var len$3 = length$3; + /** + * Calculates the squared length of a dual quat + * + * @param {ReadonlyQuat2} a dual quat to calculate squared length of + * @returns {Number} squared length of a + * @function + */ + + var squaredLength$3 = squaredLength$2; + /** + * Alias for {@link quat2.squaredLength} + * @function + */ + + var sqrLen$3 = squaredLength$3; + /** + * Normalize a dual quat + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a dual quaternion to normalize + * @returns {quat2} out + * @function + */ + + function normalize$3(out, a) { + var magnitude = squaredLength$3(a); + + if (magnitude > 0) { + magnitude = Math.sqrt(magnitude); + var a0 = a[0] / magnitude; + var a1 = a[1] / magnitude; + var a2 = a[2] / magnitude; + var a3 = a[3] / magnitude; + var b0 = a[4]; + var b1 = a[5]; + var b2 = a[6]; + var b3 = a[7]; + var a_dot_b = a0 * b0 + a1 * b1 + a2 * b2 + a3 * b3; + out[0] = a0; + out[1] = a1; + out[2] = a2; + out[3] = a3; + out[4] = (b0 - a0 * a_dot_b) / magnitude; + out[5] = (b1 - a1 * a_dot_b) / magnitude; + out[6] = (b2 - a2 * a_dot_b) / magnitude; + out[7] = (b3 - a3 * a_dot_b) / magnitude; + } + + return out; + } + /** + * Returns a string representation of a dual quatenion + * + * @param {ReadonlyQuat2} a dual quaternion to represent as a string + * @returns {String} string representation of the dual quat + */ + + function str$7(a) { + return "quat2(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ")"; + } + /** + * Returns whether or not the dual quaternions have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyQuat2} a the first dual quaternion. + * @param {ReadonlyQuat2} b the second dual quaternion. + * @returns {Boolean} true if the dual quaternions are equal, false otherwise. + */ + + function exactEquals$7(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7]; + } + /** + * Returns whether or not the dual quaternions have approximately the same elements in the same position. + * + * @param {ReadonlyQuat2} a the first dual quat. + * @param {ReadonlyQuat2} b the second dual quat. + * @returns {Boolean} true if the dual quats are equal, false otherwise. + */ + + function equals$8(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5], + a6 = a[6], + a7 = a[7]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5], + b6 = b[6], + b7 = b[7]; + return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)); + } + + var quat2 = /*#__PURE__*/Object.freeze({ + __proto__: null, + create: create$7, + clone: clone$7, + fromValues: fromValues$7, + fromRotationTranslationValues: fromRotationTranslationValues, + fromRotationTranslation: fromRotationTranslation$1, + fromTranslation: fromTranslation$3, + fromRotation: fromRotation$4, + fromMat4: fromMat4$1, + copy: copy$7, + identity: identity$5, + set: set$7, + getReal: getReal, + getDual: getDual, + setReal: setReal, + setDual: setDual, + getTranslation: getTranslation$1, + translate: translate$3, + rotateX: rotateX$3, + rotateY: rotateY$3, + rotateZ: rotateZ$3, + rotateByQuatAppend: rotateByQuatAppend, + rotateByQuatPrepend: rotateByQuatPrepend, + rotateAroundAxis: rotateAroundAxis, + add: add$7, + multiply: multiply$7, + mul: mul$7, + scale: scale$7, + dot: dot$3, + lerp: lerp$3, + invert: invert$5, + conjugate: conjugate$1, + length: length$3, + len: len$3, + squaredLength: squaredLength$3, + sqrLen: sqrLen$3, + normalize: normalize$3, + str: str$7, + exactEquals: exactEquals$7, + equals: equals$8 + }); + + /** + * 2 Dimensional Vector + * @module vec2 + */ + + /** + * Creates a new, empty vec2 + * + * @returns {vec2} a new 2D vector + */ + + function create$8() { + var out = new ARRAY_TYPE(2); + + if (ARRAY_TYPE != Float32Array) { + out[0] = 0; + out[1] = 0; + } + + return out; + } + /** + * Creates a new vec2 initialized with values from an existing vector + * + * @param {ReadonlyVec2} a vector to clone + * @returns {vec2} a new 2D vector + */ + + function clone$8(a) { + var out = new ARRAY_TYPE(2); + out[0] = a[0]; + out[1] = a[1]; + return out; + } + /** + * Creates a new vec2 initialized with the given values + * + * @param {Number} x X component + * @param {Number} y Y component + * @returns {vec2} a new 2D vector + */ + + function fromValues$8(x, y) { + var out = new ARRAY_TYPE(2); + out[0] = x; + out[1] = y; + return out; + } + /** + * Copy the values from one vec2 to another + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the source vector + * @returns {vec2} out + */ + + function copy$8(out, a) { + out[0] = a[0]; + out[1] = a[1]; + return out; + } + /** + * Set the components of a vec2 to the given values + * + * @param {vec2} out the receiving vector + * @param {Number} x X component + * @param {Number} y Y component + * @returns {vec2} out + */ + + function set$8(out, x, y) { + out[0] = x; + out[1] = y; + return out; + } + /** + * Adds two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + + function add$8(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + return out; + } + /** + * Subtracts vector b from vector a + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + + function subtract$6(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + return out; + } + /** + * Multiplies two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + + function multiply$8(out, a, b) { + out[0] = a[0] * b[0]; + out[1] = a[1] * b[1]; + return out; + } + /** + * Divides two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + + function divide$2(out, a, b) { + out[0] = a[0] / b[0]; + out[1] = a[1] / b[1]; + return out; + } + /** + * Math.ceil the components of a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to ceil + * @returns {vec2} out + */ + + function ceil$2(out, a) { + out[0] = Math.ceil(a[0]); + out[1] = Math.ceil(a[1]); + return out; + } + /** + * Math.floor the components of a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to floor + * @returns {vec2} out + */ + + function floor$2(out, a) { + out[0] = Math.floor(a[0]); + out[1] = Math.floor(a[1]); + return out; + } + /** + * Returns the minimum of two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + + function min$2(out, a, b) { + out[0] = Math.min(a[0], b[0]); + out[1] = Math.min(a[1], b[1]); + return out; + } + /** + * Returns the maximum of two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + + function max$2(out, a, b) { + out[0] = Math.max(a[0], b[0]); + out[1] = Math.max(a[1], b[1]); + return out; + } + /** + * Math.round the components of a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to round + * @returns {vec2} out + */ + + function round$2(out, a) { + out[0] = Math.round(a[0]); + out[1] = Math.round(a[1]); + return out; + } + /** + * Scales a vec2 by a scalar number + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the vector to scale + * @param {Number} b amount to scale the vector by + * @returns {vec2} out + */ + + function scale$8(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + return out; + } + /** + * Adds two vec2's after scaling the second operand by a scalar value + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @param {Number} scale the amount to scale b by before adding + * @returns {vec2} out + */ + + function scaleAndAdd$2(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + return out; + } + /** + * Calculates the euclidian distance between two vec2's + * + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {Number} distance between a and b + */ + + function distance$2(a, b) { + var x = b[0] - a[0], + y = b[1] - a[1]; + return Math.hypot(x, y); + } + /** + * Calculates the squared euclidian distance between two vec2's + * + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {Number} squared distance between a and b + */ + + function squaredDistance$2(a, b) { + var x = b[0] - a[0], + y = b[1] - a[1]; + return x * x + y * y; + } + /** + * Calculates the length of a vec2 + * + * @param {ReadonlyVec2} a vector to calculate length of + * @returns {Number} length of a + */ + + function length$4(a) { + var x = a[0], + y = a[1]; + return Math.hypot(x, y); + } + /** + * Calculates the squared length of a vec2 + * + * @param {ReadonlyVec2} a vector to calculate squared length of + * @returns {Number} squared length of a + */ + + function squaredLength$4(a) { + var x = a[0], + y = a[1]; + return x * x + y * y; + } + /** + * Negates the components of a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to negate + * @returns {vec2} out + */ + + function negate$2(out, a) { + out[0] = -a[0]; + out[1] = -a[1]; + return out; + } + /** + * Returns the inverse of the components of a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to invert + * @returns {vec2} out + */ + + function inverse$2(out, a) { + out[0] = 1.0 / a[0]; + out[1] = 1.0 / a[1]; + return out; + } + /** + * Normalize a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to normalize + * @returns {vec2} out + */ + + function normalize$4(out, a) { + var x = a[0], + y = a[1]; + var len = x * x + y * y; + + if (len > 0) { + //TODO: evaluate use of glm_invsqrt here? + len = 1 / Math.sqrt(len); + } + + out[0] = a[0] * len; + out[1] = a[1] * len; + return out; + } + /** + * Calculates the dot product of two vec2's + * + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {Number} dot product of a and b + */ + + function dot$4(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + /** + * Computes the cross product of two vec2's + * Note that the cross product must by definition produce a 3D vector + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec3} out + */ + + function cross$2(out, a, b) { + var z = a[0] * b[1] - a[1] * b[0]; + out[0] = out[1] = 0; + out[2] = z; + return out; + } + /** + * Performs a linear interpolation between two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec2} out + */ + + function lerp$4(out, a, b, t) { + var ax = a[0], + ay = a[1]; + out[0] = ax + t * (b[0] - ax); + out[1] = ay + t * (b[1] - ay); + return out; + } + /** + * Generates a random vector with the given scale + * + * @param {vec2} out the receiving vector + * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned + * @returns {vec2} out + */ + + function random$3(out, scale) { + scale = scale || 1.0; + var r = RANDOM() * 2.0 * Math.PI; + out[0] = Math.cos(r) * scale; + out[1] = Math.sin(r) * scale; + return out; + } + /** + * Transforms the vec2 with a mat2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the vector to transform + * @param {ReadonlyMat2} m matrix to transform with + * @returns {vec2} out + */ + + function transformMat2(out, a, m) { + var x = a[0], + y = a[1]; + out[0] = m[0] * x + m[2] * y; + out[1] = m[1] * x + m[3] * y; + return out; + } + /** + * Transforms the vec2 with a mat2d + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the vector to transform + * @param {ReadonlyMat2d} m matrix to transform with + * @returns {vec2} out + */ + + function transformMat2d(out, a, m) { + var x = a[0], + y = a[1]; + out[0] = m[0] * x + m[2] * y + m[4]; + out[1] = m[1] * x + m[3] * y + m[5]; + return out; + } + /** + * Transforms the vec2 with a mat3 + * 3rd vector component is implicitly '1' + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the vector to transform + * @param {ReadonlyMat3} m matrix to transform with + * @returns {vec2} out + */ + + function transformMat3$1(out, a, m) { + var x = a[0], + y = a[1]; + out[0] = m[0] * x + m[3] * y + m[6]; + out[1] = m[1] * x + m[4] * y + m[7]; + return out; + } + /** + * Transforms the vec2 with a mat4 + * 3rd vector component is implicitly '0' + * 4th vector component is implicitly '1' + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the vector to transform + * @param {ReadonlyMat4} m matrix to transform with + * @returns {vec2} out + */ + + function transformMat4$2(out, a, m) { + var x = a[0]; + var y = a[1]; + out[0] = m[0] * x + m[4] * y + m[12]; + out[1] = m[1] * x + m[5] * y + m[13]; + return out; + } + /** + * Rotate a 2D vector + * @param {vec2} out The receiving vec2 + * @param {ReadonlyVec2} a The vec2 point to rotate + * @param {ReadonlyVec2} b The origin of the rotation + * @param {Number} rad The angle of rotation in radians + * @returns {vec2} out + */ + + function rotate$4(out, a, b, rad) { + //Translate point to the origin + var p0 = a[0] - b[0], + p1 = a[1] - b[1], + sinC = Math.sin(rad), + cosC = Math.cos(rad); //perform rotation and translate to correct position + + out[0] = p0 * cosC - p1 * sinC + b[0]; + out[1] = p0 * sinC + p1 * cosC + b[1]; + return out; + } + /** + * Get the angle between two 2D vectors + * @param {ReadonlyVec2} a The first operand + * @param {ReadonlyVec2} b The second operand + * @returns {Number} The angle in radians + */ + + function angle$1(a, b) { + var x1 = a[0], + y1 = a[1], + x2 = b[0], + y2 = b[1], + // mag is the product of the magnitudes of a and b + mag = Math.sqrt(x1 * x1 + y1 * y1) * Math.sqrt(x2 * x2 + y2 * y2), + // mag &&.. short circuits if mag == 0 + cosine = mag && (x1 * x2 + y1 * y2) / mag; // Math.min(Math.max(cosine, -1), 1) clamps the cosine between -1 and 1 + + return Math.acos(Math.min(Math.max(cosine, -1), 1)); + } + /** + * Set the components of a vec2 to zero + * + * @param {vec2} out the receiving vector + * @returns {vec2} out + */ + + function zero$2(out) { + out[0] = 0.0; + out[1] = 0.0; + return out; + } + /** + * Returns a string representation of a vector + * + * @param {ReadonlyVec2} a vector to represent as a string + * @returns {String} string representation of the vector + */ + + function str$8(a) { + return "vec2(" + a[0] + ", " + a[1] + ")"; + } + /** + * Returns whether or not the vectors exactly have the same elements in the same position (when compared with ===) + * + * @param {ReadonlyVec2} a The first vector. + * @param {ReadonlyVec2} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + + function exactEquals$8(a, b) { + return a[0] === b[0] && a[1] === b[1]; + } + /** + * Returns whether or not the vectors have approximately the same elements in the same position. + * + * @param {ReadonlyVec2} a The first vector. + * @param {ReadonlyVec2} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + + function equals$9(a, b) { + var a0 = a[0], + a1 = a[1]; + var b0 = b[0], + b1 = b[1]; + return Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)); + } + /** + * Alias for {@link vec2.length} + * @function + */ + + var len$4 = length$4; + /** + * Alias for {@link vec2.subtract} + * @function + */ + + var sub$6 = subtract$6; + /** + * Alias for {@link vec2.multiply} + * @function + */ + + var mul$8 = multiply$8; + /** + * Alias for {@link vec2.divide} + * @function + */ + + var div$2 = divide$2; + /** + * Alias for {@link vec2.distance} + * @function + */ + + var dist$2 = distance$2; + /** + * Alias for {@link vec2.squaredDistance} + * @function + */ + + var sqrDist$2 = squaredDistance$2; + /** + * Alias for {@link vec2.squaredLength} + * @function + */ + + var sqrLen$4 = squaredLength$4; + /** + * Perform some operation over an array of vec2s. + * + * @param {Array} a the array of vectors to iterate over + * @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed + * @param {Number} offset Number of elements to skip at the beginning of the array + * @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array + * @param {Function} fn Function to call for each vector in the array + * @param {Object} [arg] additional argument to pass to fn + * @returns {Array} a + * @function + */ + + var forEach$2 = function () { + var vec = create$8(); + return function (a, stride, offset, count, fn, arg) { + var i, l; + + if (!stride) { + stride = 2; + } + + if (!offset) { + offset = 0; + } + + if (count) { + l = Math.min(count * stride + offset, a.length); + } else { + l = a.length; + } + + for (i = offset; i < l; i += stride) { + vec[0] = a[i]; + vec[1] = a[i + 1]; + fn(vec, vec, arg); + a[i] = vec[0]; + a[i + 1] = vec[1]; + } + + return a; + }; + }(); + + var vec2 = /*#__PURE__*/Object.freeze({ + __proto__: null, + create: create$8, + clone: clone$8, + fromValues: fromValues$8, + copy: copy$8, + set: set$8, + add: add$8, + subtract: subtract$6, + multiply: multiply$8, + divide: divide$2, + ceil: ceil$2, + floor: floor$2, + min: min$2, + max: max$2, + round: round$2, + scale: scale$8, + scaleAndAdd: scaleAndAdd$2, + distance: distance$2, + squaredDistance: squaredDistance$2, + length: length$4, + squaredLength: squaredLength$4, + negate: negate$2, + inverse: inverse$2, + normalize: normalize$4, + dot: dot$4, + cross: cross$2, + lerp: lerp$4, + random: random$3, + transformMat2: transformMat2, + transformMat2d: transformMat2d, + transformMat3: transformMat3$1, + transformMat4: transformMat4$2, + rotate: rotate$4, + angle: angle$1, + zero: zero$2, + str: str$8, + exactEquals: exactEquals$8, + equals: equals$9, + len: len$4, + sub: sub$6, + mul: mul$8, + div: div$2, + dist: dist$2, + sqrDist: sqrDist$2, + sqrLen: sqrLen$4, + forEach: forEach$2 + }); + // + exports.glMatrix = common; + exports.mat2 = mat2; + exports.mat2d = mat2d; + exports.mat3 = mat3; + exports.mat4 = mat4; + exports.quat = quat; + exports.quat2 = quat2; + exports.vec2 = vec2; + exports.vec3 = vec3; + exports.vec4 = vec4; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/WebGPU/modules/misClases.js b/WebGPU/modules/misClases.js new file mode 100644 index 0000000..e69de29 diff --git a/WebGPU/modules/misFunciones.js b/WebGPU/modules/misFunciones.js index 38e19de..160b552 100644 --- a/WebGPU/modules/misFunciones.js +++ b/WebGPU/modules/misFunciones.js @@ -106,4 +106,283 @@ export function autoCanvasDims(container, dims="widthheight") { default: throw new Error("dims must be 'width', 'height', or their concatenation"); } +} + +/** + * Represents a 4x4 matrix (column-major) + */ +export class Mat4 extends Float32Array { + /** + * Creates an identity 4x4 matrix. + * @module Mat4 + */ + constructor() { + super(16); + this[0] = 1; + this[5] = 1; + this[10] = 1; + this[15] = 1; + } + /** + * Generates a perspective projection matrix suitable for WebGPU with the given bounds. + * The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1], + * which matches WebGPU/Vulkan/DirectX/Metal's clip volume. + * Passing null/undefined/no value for far will generate infinite projection matrix. + * + * @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, can be null or Infinity + */ + perspectiveZO(fovy, aspect, near, far) { + const f = 1.0 / Math.tan(fovy / 2); + + this[0] = f / aspect; + this[1] = 0; + this[2] = 0; + this[3] = 0; + this[4] = 0; + this[5] = f; + this[6] = 0; + this[7] = 0; + this[8] = 0; + this[9] = 0; + this[11] = -1; + this[12] = 0; + this[13] = 0; + this[15] = 0; + + if (far != null && far !== Infinity) { + const nf = 1 / (near - far); + this[10] = far * nf; + this[14] = far * near * nf; + } else { + this[10] = -1; + this[14] = -near; + } + + } + /** + * Translates the matrix by the given 3d vector. + * + * @param {Float32Array} v vector to translate by. + + */ + translate(v) { + const x = v[0], + y = v[1], + z = v[2]; + + this[12] = this[0] * x + this[4] * y + this[8] * z + this[12]; + this[13] = this[1] * x + this[5] * y + this[9] * z + this[13]; + this[14] = this[2] * x + this[6] * y + this[10] * z + this[14]; + this[15] = this[3] * x + this[7] * y + this[11] * z + this[15]; + + } + + /** + * Multiply the matrix by b (self = self * b). + * + * @param {Mat4} b matrix to multiply by. + */ + multiply(b) { + const a00 = this[0], + a01 = this[1], + a02 = this[2], + a03 = this[3]; + const a10 = this[4], + a11 = this[5], + a12 = this[6], + a13 = this[7]; + const a20 = this[8], + a21 = this[9], + a22 = this[10], + a23 = this[11]; + const a30 = this[12], + a31 = this[13], + a32 = this[14], + a33 = this[15]; + + let b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + this[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + this[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + this[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + this[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + b0 = b[4]; + b1 = b[5]; + b2 = b[6]; + b3 = b[7]; + this[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + this[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + this[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + this[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + b0 = b[8]; + b1 = b[9]; + b2 = b[10]; + b3 = b[11]; + this[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + this[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + this[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + this[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + b0 = b[12]; + b1 = b[13]; + b2 = b[14]; + b3 = b[15]; + this[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + this[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + this[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + this[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + + } + /** + * Multiply a by the matrix (self = a * self). + * + * @param {Mat4} a matrix to multiply. + */ + multiply2(a) { + const b00 = this[0], + b01 = this[1], + b02 = this[2], + b03 = this[3]; + const b10 = this[4], + b11 = this[5], + b12 = this[6], + b13 = this[7]; + const b20 = this[8], + b21 = this[9], + b22 = this[10], + b23 = this[11]; + const b30 = this[12], + b31 = this[13], + b32 = this[14], + b33 = this[15]; + + this[0] = a[0] * b00 + a[4] * b01 + a[8] * b02 + a[12] * b03; + this[1] = a[1] * b00 + a[5] * b01 + a[9] * b02 + a[13] * b03; + this[2] = a[2] * b00 + a[6] * b01 + a[10] * b02 + a[14] * b03; + this[3] = a[3] * b00 + a[7] * b01 + a[11] * b02 + a[15] * b03; + + this[4] = a[0] * b10 + a[4] * b11 + a[8] * b12 + a[12] * b13; + this[5] = a[1] * b10 + a[5] * b11 + a[9] * b12 + a[13] * b13; + this[6] = a[2] * b10 + a[6] * b11 + a[10] * b12 + a[14] * b13; + this[7] = a[3] * b10 + a[7] * b11 + a[11] * b12 + a[15] * b13; + + this[8] = a[0] * b20 + a[4] * b21 + a[8] * b22 + a[12] * b23; + this[9] = a[1] * b20 + a[5] * b21 + a[9] * b22 + a[13] * b23; + this[10] = a[2] * b20 + a[6] * b21 + a[10] * b22 + a[14] * b22; + this[11] = a[3] * b20 + a[7] * b21 + a[11] * b22 + a[15] * b23; + + this[12] = a[0] * b30 + a[4] * b31 + a[8] * b32 + a[12] * b33; + this[13] = a[1] * b30 + a[5] * b31 + a[9] * b32 + a[13] * b33; + this[14] = a[2] * b30 + a[6] * b31 + a[10] * b32 + a[14] * b33; + this[15] = a[3] * b30 + a[7] * b31 + a[11] * b32 + a[15] * b33; + + } + + /** + * Rotates the matrix by the given angle around the given axis + * + * @param {Number} rad the angle to rotate the matrix by + * @param {Float32Array} axis the axis to rotate around + */ + + rotate( rad, axis) { + var x = axis[0], + y = axis[1], + z = axis[2]; + var len = Math.hypot(x, y, z); + var s, c, t; + var a00, a01, a02, a03; + var a10, a11, a12, a13; + var a20, a21, a22, a23; + var b00, b01, b02; + var b10, b11, b12; + var b20, b21, b22; + + if (len < 0.000001) { + return null; + } + + len = 1 / len; + x *= len; + y *= len; + z *= len; + s = Math.sin(rad); + c = Math.cos(rad); + t = 1 - c; + a00 = this[0]; + a01 = this[1]; + a02 = this[2]; + a03 = this[3]; + a10 = this[4]; + a11 = this[5]; + a12 = this[6]; + a13 = this[7]; + a20 = this[8]; + a21 = this[9]; + a22 = this[10]; + a23 = this[11]; // Construct the elements of the rotation matrix + + b00 = x * x * t + c; + b01 = y * x * t + z * s; + b02 = z * x * t - y * s; + b10 = x * y * t - z * s; + b11 = y * y * t + c; + b12 = z * y * t + x * s; + b20 = x * z * t + y * s; + b21 = y * z * t - x * s; + b22 = z * z * t + c; // Perform rotation-specific matrix multiplication + + this[1] = a01 * b00 + a11 * b01 + a21 * b02; + this[0] = a00 * b00 + a10 * b01 + a20 * b02; + this[2] = a02 * b00 + a12 * b01 + a22 * b02; + this[3] = a03 * b00 + a13 * b01 + a23 * b02; + this[4] = a00 * b10 + a10 * b11 + a20 * b12; + this[5] = a01 * b10 + a11 * b11 + a21 * b12; + this[6] = a02 * b10 + a12 * b11 + a22 * b12; + this[7] = a03 * b10 + a13 * b11 + a23 * b12; + this[8] = a00 * b20 + a10 * b21 + a20 * b22; + this[9] = a01 * b20 + a11 * b21 + a21 * b22; + this[10] = a02 * b20 + a12 * b21 + a22 * b22; + this[11] = a03 * b20 + a13 * b21 + a23 * b22; + + } + + /** + * Generates a orthogonal projection matrix with the given bounds. + * The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1], + * which matches WebGPU/Vulkan/DirectX/Metal's clip volume. + * + * @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 + */ + + orthoZO(left, right, bottom, top, near, far) { + var lr = 1 / (left - right); + var bt = 1 / (bottom - top); + var nf = 1 / (near - far); + this[0] = -2 * lr; + this[1] = 0; + this[2] = 0; + this[3] = 0; + this[4] = 0; + this[5] = -2 * bt; + this[6] = 0; + this[7] = 0; + this[8] = 0; + this[9] = 0; + this[10] = nf; + this[11] = 0; + this[12] = (left + right) * lr; + this[13] = (top + bottom) * bt; + this[14] = near * nf; + this[15] = 1; + } } \ No newline at end of file diff --git a/WebGPU/modules/utilities.js b/WebGPU/modules/utilities.js new file mode 100644 index 0000000..e69de29 diff --git a/WebGPU/scripts/cells3D.js b/WebGPU/scripts/cells3D.js new file mode 100644 index 0000000..07634f4 --- /dev/null +++ b/WebGPU/scripts/cells3D.js @@ -0,0 +1,2619 @@ +import { inicializarCells, autoCanvasDims, Mat4 } from "inicializar-webgpu"; +import { renderShader3D, wallShader3D, computeShader3D } from "shaders"; + +// ref https://www.cg.tuwien.ac.at/research/publications/2023/PETER-2023-PSW/PETER-2023-PSW-.pdf + +const +SHOW_TITLE = false, + +[device, canvas, canvasFormat, context, timer] = await inicializarCells(SHOW_TITLE), + +SETUPS_FOLDER = "../../data/", +WORKGROUP_SIZE = 64, +NEW_USER = localStorage.getItem("NEW_USER"), +CURRENT_VERSION = document.getElementById("title").innerText, +LAST_VISITED_VERSION = localStorage.getItem("STORED_VERSION_NUMBER"), +CHANGELOG = `\ + ${CURRENT_VERSION} + + * En desarrollo. + +`, +particleStyles = [ + { + borderWidth: 1, + spherical: 0 + }, { + borderWidth: 0.85, + spherical: 0 + }, { + borderWidth: 1, + spherical: 1 + } +], +styleSettings = { + bgColor: [0, 0, 0, 1], + //particleStyle: [1, 1], //[borderStart in UV scale (0-1), is spherical?] + particleStyle: particleStyles[2], +}, +ambient = { + friction: 1 - 0.995, // 0.995 en el shader + bounce: 80, // 0.8 en el shader + maxInitVel: 0, + canvasDims: [canvas.width, canvas.height], +}, +flags = { + updateSimParams: true, + resetParts: false, + updateParticles: false, + updateRules: false, + + rulesModified: false, // Se modificaron las reglas en la UI. + + editAmbient: false, + editPStyle: true, + + justLoadedSetup: false, + + moveCamera: false, +}, +eyePosition = new Float32Array([0, 0, 1300]); + +let +N = 0, // Cantidad total de partículas +elementaries = [], // Array de familias de partículas (clase Elementary) +rules = [], // Array de reglas de interacción (clase Rule) +workgroupCount, // workgroups para ejecutar las reglas de interacción (mover las partículas) +rng, +frame = 0, +animationId, +paused = true, +stepping = false, +awaitingResetCall = false, +muted = false, +fps = 0, +frameCounter = 0, +refTime, +placePartOnClic = false, +mouseIsDown = false, +mDownX = 0, mDownY = 0, +newParticles = [], // PosiVels de partículas creadas manualmente para cada elementary +sampleCount = 1, // Parece que sólo puede ser 1 o 4. +textureView, +projectionMatrix = new Mat4(), +viewProjectionMatrix = new Mat4(), +rotAxis = new Float32Array([0, 1, 0]), +rotYCurrent = 0,//-Math.PI/2, +xlim = 500, +ylim = 300, +zlim = 300, +moveSpeed = 20, +rotateSpeed = 3*Math.PI/180; + +// TIMING & DEBUG + const STARTING_SETUP_NUMBER = 1, + SETUP_FILENAME = "Cells GPU setup - ClassicX10", + SHOW_DEBUG = true; + //localStorage.setItem("NEW_USER", 1); + //localStorage.setItem("STORED_VERSION_NUMBER", -1); + let capacity = 4; //Max number of timestamps + let t = []; + let querySet, queryBuffer; + + if (timer) { // véase https://omar-shehata.medium.com/how-to-use-webgpu-timestamp-query-9bf81fb5344a + querySet = device.createQuerySet({ + type: "timestamp", + count: capacity, + }); + queryBuffer = device.createBuffer({ + size: 8 * capacity, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, + }); + } +// + +// FUNCIONES VARIAS Y CLASES - TODO: Modularizar + + // General utility + function hexString_to_rgba(hexString, a) { + + hexString = hexString.replace("#",""); // remove possible initial # + + const red = parseInt(hexString.substr(0, 2), 16) / 255 ; // Convert red component to 0-1 range + const green = parseInt(hexString.substr(2, 2), 16) / 255; // Convert green component to 0-1 range + const blue = parseInt(hexString.substr(4, 2), 16) / 255; // Convert blue component to 0-1 range + + // console.log(`Returned RGBA array [${[red, green, blue, a]}] from "#${hexString}" [hexString_to_rgba] `); + + return new Float32Array([red, green, blue, a]); // Store the RGB values in an array + } + function randomPosition(elementaryIndex, margin = 0) { + return ([ + (rng() - 0.5) * (xlim*2 - margin), // TODO: así como está es eficiente pero el margen no es el esperado + (rng() - 0.5) * (ylim*2 - margin), + (rng() - 0.5) * (zlim*2 - margin), + elementaryIndex + ]); + } + function randomVelocity() { + return ([ + (2 * rng() - 1) * ambient.maxInitVel, + (2 * rng() - 1) * ambient.maxInitVel, + (2 * rng() - 1) * ambient.maxInitVel, + 1, + ]); + } + function crearPosiVel(n, index, margin = 0) { // crea dos n-arrays con las posiciones y velocidades de n partículas + + const buffer = new ArrayBuffer(n * 8 * 4) // n partículas, cada una tiene 28B (4*4B para la pos y 3*4B para la vel) + + const pos = new Float32Array(buffer, 0, n*4); // buffer al que hace referencia, byte offset, number of elements. [x1, y1, z1, w1, x2, y2, ...] + const vel = new Float32Array(buffer, n*4*4, n*4); + + //const start = performance.now(); + for (let i=0 ; i < n*4 ; i += 4) { + [ pos [i], pos[i+1], pos [i+2], pos[i+3] ] = randomPosition(index, margin); // randomPosition devuelve un array [x,y,z,w] + [ vel [i], vel[i+1], vel [i+2], vel[i+3] ] = randomVelocity(); + } + //console.log((performance.now()-start).toFixed(3)) + + return [pos, vel] + } + function validarNumberInput(input, estricto=true) { + /* input es un objeto representando un html element input de type number. + Estricto significa que no admite valores afuera del rango. */ + + if (input.validity.valid) { + return true; + } + + if (input.validity.badInput) { + titilarBorde(input, "red"); + return false; + } + + const outsideRange = (input.validity.rangeUnderflow || input.validity.rangeOverflow); + + if (!outsideRange) { + return true; + } + + if (!estricto && outsideRange) { + titilarBorde(input, "yellow"); //console.log(`${input.id} returns true in validar`) + return true; + } + + //console.table(input.validity) + + titilarBorde(input, "red"); + return false; + } + function importarJson(path="") { + const msg = "Error detectado antes de importar." + + if (path) { // Load from server file. + + return new Promise ((resolve, reject) => { + + fetch(path) + .then( (response) => { return response.json() }) + .catch((error) => { reject(labelError(error, msg)); }) + .then( (json) => { resolve(json); }) + }); + /* Async solution expanded + const promise = fetch(path); + const response = await fetch(path); + const json = await response.json(); + return(json); + */ + } + + // Load from user prompt + const fileInput = document.createElement("input"); + fileInput.type = "file"; + fileInput.accept = ".json"; + + return new Promise((resolve, reject) => { + + fileInput.onchange = (event)=> { + const file = event.target.files[0]; + const reader = new FileReader(); + + reader.onload = _=> { + try { + const jsonData = JSON.parse(reader.result); + resolve(jsonData); + } catch (error) { reject(error); } + } + reader.onerror = (error) => { reject(labelError(error, msg)); } + reader.readAsText(file); + }; + + fileInput.click(); + }); + + } + function hasSameStructure(obj1, obj2) { + // no revisa la estructura de los arrays de elementaries y rules + const keys1 = Object.keys(obj1).sort(); + const keys2 = Object.keys(obj2).sort(); + + if (keys1.length !== keys2.length) { + return false; + } + + for (let i = 0; i < keys1.length; i++) { + const key1 = keys1[i]; + const key2 = keys2[i]; + + if (key1 !== key2 || typeof obj1[key1] !== typeof obj2[key2]) { + return false; + } + + if (typeof obj1[key1] === "object" && !Array.isArray(obj1[key1])) { + + if (!hasSameStructure(obj1[key1], obj2[key2])) { + return false; + } + } + } + return true; + } + function rgba_to_hexString(rgbaArray) { + const [r, g, b] = rgbaArray; + const hexR = Math.floor(r * 255).toString(16).padStart(2, '0'); + const hexG = Math.floor(g * 255).toString(16).padStart(2, '0'); + const hexB = Math.floor(b * 255).toString(16).padStart(2, '0'); + return `#${hexR}${hexG}${hexB}`; + } + function labelError(error, label="Default error label") { + const labeledError = new Error (label); + labeledError.cause = error; + return labeledError; + } + function generateHistogram2(data, lim=1, nBins=10) { + const histogram = []; + + // Initialize histogram bins + for (let i = 0; i < nBins; i++) { + histogram[i] = 0; + } + + // Calculate bin size + const min = -lim//Math.min(...data); + const max = lim//Math.max(...data); + const binSize = (max - min) / nBins; + + // Increment bin counts + data.forEach((value) => { + const binIndex = Math.floor((value - min) / binSize); + histogram[binIndex]++; + }); + + let logLines = ""; + // Display histogram + for (let i = 0; i < nBins; i++) { + const binStart = min + i * binSize; + const binEnd = binStart + binSize; + const binCount = histogram[i]; + logLines += `[${binStart.toFixed(2)} :: ${binEnd.toFixed(2)}]: ${binCount}\n` + } + console.log(logLines); + + const sumNeg = histogram.slice(0, 4+1).reduce((a,b)=>a+b,0); + const sumPos = histogram.slice(5, 9+1).reduce((a,b)=>a+b,0); + let bal = "" + if (sumPos>sumNeg) { + bal = "+++"; + } else if (sumPos 0.05) || !avoidSpam) { + soundElement.currentTime = 0; + } + soundElement.play(); + } + function titilarBorde(element, color="red") { + element.classList.add("titilante"); + element.style.setProperty("--titil-color", color); + } + function removeOptions(htmlElement) { + htmlElement.options.length = 0; //<- only for select elements + //htmlElement.innerHTML = ""; + //while (htmlElement.firstChild) { + // htmlElement.removeChild(htmlElement.firstChild); + //htmlElement.remove(0) <- only for select elements + //} + //$(htmlElement).children().remove(); + } + function switchClass(element, className, state) { + // por defecto la alterna. Si tiene una input, lo pone acorde a ella. Devuelve el estado. + + const list = element.classList; + + if (state === undefined) { + if (list.contains(className)) { + list.remove(className); + return false; + } + else { + list.add(className); + return true; + } + } + + if (state) { + list.add(className); + return true; + } else { + list.remove(className) + return false; + } + + } + function switchVisibilityAttribute(element) { + element.hidden ^= true; + } + function setAutomaticInputElementWidth (inputElement, min, max, padding) { + // falla para xxxxe porque allí value = "" -> length = 0 + + if (inputElement.validity.badInput) {return;} + + const ancho = Math.max(inputElement.value.length, inputElement.placeholder.length); + inputElement.style.width = `${ Math.min(Math.max(ancho, min) + padding, max) }ch`; + } + function checkAndGetNumberInput(input, failFlag, strict = true, P=true) { + // For chained checks before value usage. Supports exponential notation. + if (!validarNumberInput(input, strict)) { // Is it a bad input (aka not a number)? + return [undefined, true]; // fail, set flag. + } + else if (P && !input.value && input.placeholder) { // Is it empty and has a placeholder (asumed valid)? + return [Number(input.placeholder), failFlag]; // return placeholder number, pass flag. + } + else if (input.value) { // Is it not empty? + if (input.step === "1") { // Is it supposed to be an integer? + return [Math.trunc(input.value), failFlag]; // return int, pass flag. + } else { + return [parseFloat(input.value), failFlag]; // return float, pass flag. + } + } + else { // No placeholder or valid value. + titilarBorde(input,"red"); + return [undefined, true]; // fail, set flag. + } + } + function suggestReset(element, flag, done="default value") { + + if (done === "done") { + element.style.setProperty("border-color","rgba(255, 255, 255, 0.2)"); + flag = false; + } else if (done !== "default value") { + console.warn("Wrong input"); + } else { + element.style.setProperty("border-color","rgba(255, 165, 0, 1)"); + flag = true; + } + } + + // Classes and their handling + class Elementary { + constructor(nombre, color, cantidad, radio, posiciones, velocidades) { + // Check input parameter types and sizes + if (typeof nombre !== "string") { + throw new Error("Nombre no es string."); + } + if (color.constructor !== Float32Array || color.length !== 4) { + throw new Error("Color no es Float32Array de 4 elementos."); + } + if (!Number.isInteger(cantidad) || cantidad < 0) { + throw new Error("Cantidad no es un entero >= 0."); + } + if (typeof radio !== "number" || radio <= 0) { + throw new Error("Radio no es un número positivo."); + } + this.#validateArray(posiciones, "posiciones"); + this.#validateArray(velocidades, "velocidades"); + + this.nombre = nombre; + this.color = color; + this.cantidad = cantidad; + this.radio = radio; + this.posiciones = posiciones; + this.velocidades = velocidades; + + } + static fromJsonObjectLit(obj) { + return new Elementary( + obj.nombre, + new Float32Array(obj.color), + obj.cantidad, + obj.radio, + new Float32Array(obj.posiciones), + new Float32Array(obj.velocidades) + ); + } + isFilled(prop) { + switch (prop) { + case "posiciones": + return this.posiciones.length === this.cantidad * 4; + case "velocidades": + return this.velocidades.length === this.cantidad * 4; + default: + console.log("a") + throw new Error('isFilled() sólo acepta "posiciones" o "velocidades".'); + } + } + + get filledPosiVels() { + let str = ""; + if (this.isFilled("posiciones")) { str += "posi"; } + if (this.isFilled("velocidades")) { str += "vels"; } + return str; + } + + get colorAsHex() { + const [r, g, b] = this.color; + const hexR = Math.floor(r * 255).toString(16).padStart(2, "0"); + const hexG = Math.floor(g * 255).toString(16).padStart(2, "0"); + const hexB = Math.floor(b * 255).toString(16).padStart(2, "0"); + return `#${hexR}${hexG}${hexB}`; + } + + get asJsonObjectLit() { + return { + nombre: this.nombre, + color: Array.from(this.color), + cantidad: this.cantidad, + radio: this.radio, + posiciones: [], + velocidades: [] + } + } + + get asJsonObjectLitFull() { + const output = this.asJsonObjectLit; + output.posiciones = Array.from(this.posiciones); + output.velocidades = Array.from(this.velocidades); + console.log("output") + return output; + } + + #validateArray(array, inputName) { + if (array.constructor !== Float32Array && !(Array.isArray(array) && array.length === 0)) { + throw new Error (`Entrada ${inputName} inválida`); + } + } + } + function cargarElementary(newElementary) { + if (!(newElementary instanceof Elementary)) { throw new Error("No es una instancia de Elementary"); } + let i = elementaries.length; + if ( elementaries.some(dict => dict.nombre == newElementary.nombre) ){ + console.log("Reemplazando partículas homónimas...") + i = elementaries.findIndex(dict => dict.nombre == newElementary.nombre); + elementaries [i] = newElementary; + } else { + elementaries.push(newElementary); + actualizarElemSelectors(newElementary); // actualizar lista de nombres en el creador de reglas de interacción + if (newParticles.length) { newParticles.push([]); } // Si estoy colocando partículas manualmente, agregar el slot. + } + partiControls.selector.selectedIndex = i; + } + class Rule { + constructor(ruleName, targetName, sourceName, intensity, quantumForce, minDist, maxDist) { + this.ruleName = ruleName || `${targetName} ← ${sourceName}`; + this.targetName = targetName; + this.sourceName = sourceName; + this.intensity = intensity; + this.quantumForce = quantumForce; + this.minDist = minDist; + this.maxDist = maxDist; + } + static fromJsonObjectLit(obj) { + return new Rule( + obj.ruleName, obj.targetName, obj.sourceName, obj.intensity, + obj.quantumForce, obj.minDist, obj.maxDist + ); + } + } + function cargarRule(newRule) { + if (!(newRule instanceof Rule)) { throw new Error("No es una instancia de Rule"); } + let i = rules.length; + if ( rules.some(dict => dict.ruleName === newRule.ruleName) ){ + console.log("Reemplazando regla homónima...") + i = rules.findIndex(dict => dict.ruleName === newRule.ruleName); + rules[i] = newRule; + } else { + rules.push(newRule); + actualizarRuleSelector(newRule); // actualizar lista de nombres en el creador de reglas de interacción + } + ruleControls.selector.selectedIndex = i; + } + class Setup { + constructor(name, seed, ambient, elementaries, rules) { + + this.#validateCanvasDims(ambient.canvasDims); + this.#validateObjectArray(elementaries, Elementary); + this.#validateObjectArray(rules, Rule); + + this.name = name; + this.seed = seed; + this.ambient = ambient; + this.elementaries = elementaries; + this.rules = rules; + } + static fromJsonObjectLit(obj) { + return new Setup( + obj.name, + obj.seed, + obj.ambient, + obj.elementaries.map(elem => Elementary.fromJsonObjectLit(elem)), + obj.rules.map(rule => Rule.fromJsonObjectLit(rule)), + ); + } + + #validateCanvasDims(dims) { + if (!Number.isInteger(dims[0]) && dims[0] !== "auto" && dims[0] !== "previous") { + throw new Error("Invalid canvas width."); + } + if (!Number.isInteger(dims[1]) && dims[1] !== "auto" && dims[1] !== "previous") { + throw new Error("Invalid canvas height."); + } + } + #validateObjectArray(array, _class) { + for (let obj of array) { + if (!(obj instanceof _class)) throw new Error(`${obj} is not instance of ${_class.name}.`); + } + } + #validateRules(rules) { + for (let rule of rules) { + if (!(rule instanceof Rule)) { throw new Error(`${rule?.ruleName} is not instance of Elementary.`); } + } + } + + get asJsonObjectLit() { + return { + name: this.name, + seed: this.seed, + ambient: this.ambient, + elementaries: this.elementaries.map(elem => elem.asJsonObjectLit), + rules: this.rules + } + } + get asJsonObjectLitFull() { + const output = this.asJsonObjectLit; + output.elementaries = this.elementaries.map(elem => elem.asJsonObjectLitFull); + return output; + } + } + async function importSetup(path) { + + const jsonPromise = importarJson(path) + .catch((error) => { + window.alert("Error al importar, archivo descartado.\n" + error); + }); + + const json = await jsonPromise; + + return Setup.fromJsonObjectLit(json); + } + function cargarSetup(setup, draw = false) { + + if (!(setup instanceof Setup)) { throw new Error("Falló la verificación, no es un objeto de clase Setup.")} + + // Load ambient // resetear() lo termina de cargar + ambientControls.inputs.friction.value = setup.ambient.friction.toString(); + ambientControls.inputs.bounce.value = setup.ambient.bounce.toString(); + ambientControls.inputs.vel.value = setup.ambient.maxInitVel.toString(); + ambient.maxInitVel = setup.ambient.maxInitVel; + + // Load canvas size to ambient + let str = ""; + switch (setup.ambient.canvasDims[0]) { + case "auto": + str += "width"; + break; + case "previous": + break; + default: + ambient.canvasDims[0] = setup.ambient.canvasDims[0]; + } + switch (setup.ambient.canvasDims[1]) { + case "auto": + str += "height"; + break; + case "previous": + break; + default: + ambient.canvasDims[1] = setup.ambient.canvasDims[1]; + } + + if (str) { + [ambient.canvasDims[0] = ambient.canvasDims[0], + ambient.canvasDims[1] = ambient.canvasDims[1]] = + autoCanvasDims(canvasContainer, str); + } + + // Load seed + setRNG(setup.seed); + if (setup.seed) { seedInput.value = setup.seed.toString(); } + else { seedInput.value = ""; } + + // Load elementaries + vaciarSelectors(); + + elementaries = setup.elementaries; + + let msgCp = "Import: Creadas posiciones para ", msgCv = "Import: Creadas velocidades para "; + let msgLp = "Import: Cargadas posiciones para ", msgLv = "Import: Cargadas velocidades para "; + + for (let index = 0; index < elementaries.length; index++) { + + const elem = elementaries[index]; + + const nom = elem.nombre + ", "; + switch (elem.filledPosiVels) { + + case "": + [elem.posiciones, elem.velocidades] = crearPosiVel(elem.cantidad, index, elem.radio * 2); + msgCp += nom; + msgCv += nom; + break; + + case "posi": + [ , elem.velocidades] = crearPosiVel(elem.cantidad, index, elem.radio * 2); + msgLp += nom; + msgCv += nom; + break; + + case "vels": + [elem.posiciones, ] = crearPosiVel(elem.cantidad, index, elem.radio * 2); + msgCp += nom; + msgLv += nom; + break; + + case "posivels": + msgLp += nom; + msgLv += nom; + break; + + default: + console.warn("Error cargando setup."); + } + + actualizarElemSelectors(elem); + } + + if (msgCp.length !== 32) { console.log(msgCp.slice(0,-2) + "."); } + if (msgCv.length !== 33) { console.log(msgCv.slice(0,-2) + "."); } + if (msgLp.length !== 33) { console.log(msgLp.slice(0,-2) + "."); } + if (msgLv.length !== 34) { console.log(msgLv.slice(0,-2) + "."); } + + if (elementaries.length) {partiControls.placeButton.hidden = false;} + + // Load rules + rules = setup.rules; + for (let rule of rules) { actualizarRuleSelector(rule); } + + flags.justLoadedSetup = true; + resetear(draw); + setPlaceholdersParticles(); + setPlaceholdersRules(); + console.log("Setup " + setup.name + " cargado."); + + } + function exportarSetup(setup, filename = "Cells GPU setup", savePosiVels = false) { + + let exportSetup; + + if (savePosiVels) { + exportSetup = setup.asJsonObjectLitFull; + console.log("Exportando con posiciones y velocidades."); + } else { + exportSetup = setup.asJsonObjectLit; + } + + const jsonString = JSON.stringify(exportSetup, null, 2); + + const blob = new Blob([jsonString], { type: "application/json" }); + const url = URL.createObjectURL(blob); + + const a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + function generarSetupClásico(m, seed) { + const e = []; + const elementaries = [ + new Elementary("A", new Float32Array([1,1,0,1]), 300*m, 3, e, e), //300 + new Elementary("R", new Float32Array([1,0,0,1]), 80*m, 4, e, e), //80 + new Elementary("P", new Float32Array([128/255,0,128/255,1]), 30*m, 5, e, e), //30 + new Elementary("V", new Float32Array([0,128/255,0,1]), 5, 7, e, e), //5 r7 + ]; + + /*q = 0.25 * g_clásico * q_clásico. q_clásico = [0.2, 0, 1, 0, 1, 0, 1, 0, 0.2] */ + const rules = [ // nom/tar/src /I /q /dmin/dmax + new Rule("","R","R", 0.5, 0.025, 15, 100 ), // los núcleos se tratan de juntar si están cerca + new Rule("","A","R", 0.5, 0.0, 60, 600 ), // los electrones siguen a los núcleos, pero son caóticos + new Rule("","A","A", -0.1, 0.025, 20, 600 ), + new Rule("","P","R", 0.4, 0.0, 0.1, 150 ), // los virus persiguen a los núcleos + new Rule("","P","A", -0.2, 0.05, 0.1, 100 ), // los virus son repelidos por los electrones + new Rule("","A","P", 0.2, 0.0, 0.1, 100 ), // los electrones persiguen a los virus + new Rule("","R","P", 1.0, 0.25, 0.1, 10 ), // los virus desorganizan los núcleos + new Rule("","R","V", 0.3, 0.0, 50, 1000), // los núcleos buscan comida + new Rule("","V","V", -0.2, 0.01, 50, 200 ), // la comida se mueve un poco y estabiliza las células + ]; + + return new Setup( + "Clásico (X" + m + ")", + seed, + { + friction: (m-1) * (0.008-0.005) / (10-1) + 0.005, //0.005 default + bounce: 80, + maxInitVel: 0, + canvasDims: ["auto", "auto"], + }, + elementaries, + rules, + ) + } + function generarSetupDebug(m, seed) { + const e = []; + const elementaries = [ + new Elementary("A", new Float32Array([1,1,0,1]), 300*m, 3, e, e), + ]; + + const rules = [ // nom/tar/src /I /q /dmin/dmax + new Rule("","R","R", 0.5, 0.025, 15, 100 ), + ]; + + return new Setup( + "Debug", + seed, + { + friction: 0.005, + bounce: 80, + maxInitVel: 0, + canvasDims: ["auto", "auto"], + }, + elementaries, + rules, + ) + } + + // UI/CPU data handling + function setRNG(seed) { + //console.log(`setRNG(${seed}) called`) + if (seed == "") { + seed = Math.random().toFixed(7).toString(); + } + rng = new alea(seed); + seedInput.placeholder = seed; + } + function vaciarSelectors(){ // orig: 1ms // innerhtml ="": 0.76ms // length = 0: 0.73ms + no html recomp. + removeOptions(partiControls.selector); + removeOptions(ruleControls.selector); + removeOptions(ruleControls.targetSelector); + removeOptions(ruleControls.sourceSelector); + } + function actualizarElemSelectors(elementary) { + + const selectorsEstabanVacíos = (ruleControls.targetSelector.options.length === 0) + + const option = document.createElement("option"); + + option.value = elementary.nombre; + option.text = elementary.nombre; + + ruleControls.targetSelector.appendChild(option); + + const option2 = option.cloneNode(true); + ruleControls.sourceSelector.appendChild(option2); + + const option3 = option.cloneNode(true); + partiControls.selector.appendChild(option3); + + if (selectorsEstabanVacíos) { setPlaceholderRuleName(); } + + } + function actualizarRuleSelector(rule) { + const option = document.createElement("option"); + option.text = rule.ruleName; + ruleControls.selector.appendChild(option); + } + function setPlaceholdersParticles() { + const i = partiControls.selector.selectedIndex; + if (i === -1) {return;} + partiControls.nameInput.placeholder = elementaries[i].nombre ?? ""; + partiControls.colorInput.value = elementaries[i].colorAsHex;//rgba_to_hexString(elementaries[i].color); + partiControls.cantInput.placeholder = elementaries[i].cantidad; + partiControls.radiusInput.placeholder = elementaries[i].radio; + } + function setPlaceholdersRules() { + const i = ruleControls.selector.selectedIndex; + if (i === -1) {return;} + ruleControls.nameInput.placeholder = rules[i].ruleName; + ruleControls.targetSelector.selectedIndex = elementaries.findIndex(elem => elem.nombre === rules[i].targetName); + ruleControls.sourceSelector.selectedIndex = elementaries.findIndex(elem => elem.nombre === rules[i].sourceName); + ruleControls.intens.placeholder = rules[i].intensity; + ruleControls.qm.placeholder = rules[i].quantumForce; + ruleControls.dmin.placeholder = rules[i].minDist; + ruleControls.dmax.placeholder = rules[i].maxDist; + } + function setPlaceholderRuleName() { + const A = elementaries[ruleControls.targetSelector.selectedIndex].nombre; + const B = elementaries[ruleControls.sourceSelector.selectedIndex].nombre; + ruleControls.nameInput.placeholder = A + " ← " + B; + } + function removePartiControlsValues() { + partiControls.nameInput.value = ""; + partiControls.cantInput.value = ""; + partiControls.radiusInput.value = ""; + } + function removeRuleControlsValues() { + ruleControls.nameInput.value = ""; + ruleControls.intens.value = ""; + ruleControls.qm.value = ""; + ruleControls.dmin.value = ""; + ruleControls.dmax.value = ""; + } + function hideCPOptions() { + CPOptions.hidden ^= true; + if (CPOptions.hidden){ panelTitle.style = "height: 3ch;"; } else { panelTitle.style = ""; } + } + function allParticlesDeleted() { + borraParticleButton.hidden = true; + partiControls.placeButton.hidden = true; + + placePartOnClic = false; + switchClass(partiControls.placeButton, "switchedoff", true); + canvas.style.cursor = "default"; + } + function updateUIAfterRulesChange() { + setPlaceholdersRules(); + markers[3].hidden = false; + if (partiControls.updateButton.classList.contains("disabled")) { + switchClass(ruleControls.updateButton, "disabled", false); + } + } + function clearTempParticles() { + tempParticles.replaceChildren(); + newParticles = []; + const placeButtonOff = !partiControls.placeButton.classList.contains("switchedoff"); + switchClass(borraParticleButton, "disabled", placeButtonOff); + } + function getDeltas(event) { + const [x1, y1] = [mDownX, mDownY]; + const [x2, y2] = [event.offsetX, event.offsetY]; + return [x2 - x1, y2 - y1]; + } + function updatePosInfoPanel() { + posInfo.innerText = `(${eyePosition[0].toFixed(2)} , ${eyePosition[1].toFixed(2)} , ${eyePosition[2].toFixed(2)}) + Y Rot: ${(rotYCurrent*180/Math.PI).toFixed(1)}°` + } + + // Simulation flow + function pausar() { + if (!paused) { + pauseButton.innerText = "Resumir"; + cancelAnimationFrame(animationId); //redundante pero a veces ahorra pasos + } else { + pauseButton.innerText = "Pausa"; + refTime = performance.now(); + frameCounter = 0; + animationId = requestAnimationFrame(newFrame); + } + paused = !paused; + stepping = false; + resetButton.hidden = false; + } + function stepear() { + stepping = true; + paused = true; + animationId = requestAnimationFrame(newFrame); + pauseButton.innerText = "Resumir"; + resetButton.hidden = false; + } + function resetear(draw=true) { + frame = 0; + clearTempParticles(); + applyCanvas(); + applyAmbient(); + applyParticles(); + applyRules(); + + //suggestReset(resetButton, awaitingResetCall, "done"); + if (paused && draw) { + stepear(); + } + } + + // WebGPU utilities + async function readBuffer(device, buffer) { + const size = buffer.size; + const gpuReadBuffer = device.createBuffer({size, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }); + const copyEncoder = device.createCommandEncoder(); + copyEncoder.copyBufferToBuffer(buffer, 0, gpuReadBuffer, 0, size); + device.queue.submit([copyEncoder.finish()]); + await gpuReadBuffer.mapAsync(GPUMapMode.READ); + return gpuReadBuffer.getMappedRange(); + } + function timestamp(timestampIndex, encoder) { + const i = timestampIndex; + if (i >= capacity) { + console.warn(`Discarded timestamp index ${i} >= ${capacity}`); + return; + } + + if (timer) { + encoder.writeTimestamp(querySet, i); + if (i === capacity - 1) { + encoder.resolveQuerySet( + querySet, + 0, // index of first query to resolve + capacity, // number of queries to resolve + queryBuffer, + 0 // destination offset + ); + } + } else { t[i] = window.performance.now(); } + } + function createPosiVelsGPUBuffers (sizeP, sizeV) { + GPUBuffers.positionBuffers = [ + device.createBuffer({ + label: "Positions buffer IN", + size: sizeP, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + }), + device.createBuffer({ + label: "Positions buffer OUT", + size: sizeP, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + }) + ]; + GPUBuffers.velocities = device.createBuffer({ + label: "Velocities buffer", + size: sizeV, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + }); + } + function getTextureView(dims) { + const texture = device.createTexture({ + size: dims, + sampleCount, + format: canvasFormat, + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + return texture.createView(); + } + + // Prepare to edit buffers (or do so if immediately possible) + function applyCanvas() { + [canvas.width, canvas.height] = ambient.canvasDims; + canvasInfo.innerText = `${canvas.width} x ${canvas.height} (${(canvas.width/canvas.height).toFixed(6)})`; + textureView = getTextureView(ambient.canvasDims); + flags.updateCanvas = true; + flags.updateSimParams = true; + } + function applyAmbient() { + let mustUpdate = false; + const [friction, frictionInvalid] = checkAndGetNumberInput(ambientControls.inputs.friction, false, false); + // obtiene número de input (value o placeholder) + if (!frictionInvalid) { + ambientControls.inputs.friction.value = ""; + if (ambient.friction !== friction) { + ambient.friction = friction; + ambientControls.inputs.friction.placeholder = friction; + mustUpdate = true; + } + } + + const [bounce, bounceInvalid] = checkAndGetNumberInput(ambientControls.inputs.bounce, false, false); + if (!bounceInvalid) { + ambientControls.inputs.bounce.value = ""; + if ( ambient.bounce !== Math.max(bounce, 0)) { + ambient.bounce = Math.max(bounce, 0); + ambientControls.inputs.bounce.placeholder = ambient.bounce; + mustUpdate = true; + setAutomaticInputElementWidth(ambientControls.inputs.bounce, 3, 12, 0); + } + } + + let [vel, velInvalid] = checkAndGetNumberInput(ambientControls.inputs.vel, false, false); + if (!velInvalid) { + ambientControls.inputs.vel.value = ""; + vel = Math.abs(vel); + if ( vel !== ambient.maxInitVel || flags.justLoadedSetup) { + ambient.maxInitVel = vel; + ambientControls.inputs.vel.placeholder = vel; + mustUpdate = true + } + } + + if (mustUpdate) { + flags.editAmbient = true; + flags.updateSimParams = true; + } + switchClass(ambientControls.updateButton, "disabled", true); + markers[1].hidden = true; + } + function applyRules() { + switchClass(ruleControls.updateButton, "disabled", true) + markers[3].hidden = true; + flags.rulesModified = false; + flags.updateRules = true; + flags.updateSimParams = true; + } + function applyParticles() { + flags.resetParts = true; + flags.updateParticles = true; + flags.updateSimParams = true; + switchClass(partiControls.updateButton, "disabled", true); + markers[2].hidden = true; + if (flags.rulesModified) { + switchClass(ruleControls.updateButton, "disabled", false); + markers[3].hidden = false; + } + } + function applyParticlesStyle() { + const i = parseInt(pStyleRange.value); + styleSettings.particleStyle = particleStyles[i]; + + if (paused) { + writePStyleToBuffer() + const encoder = device.createCommandEncoder(); + render(encoder, Math.max(frame - 1, 0)); + device.queue.submit([encoder.finish()]); + } else { + flags.editPStyle = true; + flags.updateSimParams = true; + } + } + + // Functions to edit buffers (usually used by editBuffers()) + function writeCanvasToBuffer() { + paramsArrays.canvasDims.set(ambient.canvasDims); + device.queue.writeBuffer(GPUBuffers.params, 0, paramsArrays.canvasDims); + flags.updateCanvas = false; + } + function writeAmbientToBuffer() { + paramsArrays.ambient.set([1 - ambient.friction, ambient.bounce / 100]); + device.queue.writeBuffer(GPUBuffers.params, 28, paramsArrays.ambient); + flags.editAmbient = false; + } + function updateDatosElementariesBuffer(Ne) { + + const datosElemsSize = (3 * 4 + 4 + 16) * Ne; // 3 cants, radio, color + const datosElementariesArrBuffer = new ArrayBuffer(datosElemsSize); + N = 0; + + for (let i = 0; i < Ne; i++) { //N, radios, colores, cantidades + + const nLocal = elementaries[i].cantidad; + N += nLocal; // N también hace de acumulador para este for. + + const cants = new Uint32Array(datosElementariesArrBuffer, i * 8*4, 3); + const radioColor = new Float32Array(datosElementariesArrBuffer, (i * 8*4) + 3*4, 5); + + cants.set([nLocal, N, N-nLocal]); // [cants, cantsacum, cantsAcum2] + radioColor.set([elementaries[i].radio]); + radioColor.set(elementaries[i].color,1); + } + + paramsArrays.N.set([N]); + device.queue.writeBuffer(GPUBuffers.params, 8, paramsArrays.N); + + GPUBuffers.datosElementaries = device.createBuffer({ + label: "Buffer: datos elementaries", + size: datosElemsSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + }) + device.queue.writeBuffer(GPUBuffers.datosElementaries, 0, datosElementariesArrBuffer, 0, datosElemsSize); + } + function updateParticlesBuffers() { + + const Ne = elementaries.length; + + paramsArrays.Ne.set([Ne]); + device.queue.writeBuffer(GPUBuffers.params, 12, paramsArrays.Ne); + + updateDatosElementariesBuffer(Ne); + + if (flags.justLoadedSetup) { flags.resetParts = false; } + + for (let elem of elementaries) { + const L = elem.cantidad * 4; + const posiVelsIncompleto = (elem.posiciones.length !== L || elem.velocidades.length !== L); + if (posiVelsIncompleto) { + flags.resetParts = true; + console.warn("Detectadas partículas faltantes, reseteando posiciones y velocidades..."); + break; + } + } + + // Agregar partículas manuales a las pre-existentes + if (newParticles.length && !flags.resetParts) { + + const newParticlesF = newParticles.flat(); + const oldSize = GPUBuffers.velocities?.size ?? 0; + const newSize = oldSize + newParticlesF.length * 4 * 4; + + //console.log("oldSize: " + oldSize + ", newSize: " + newSize); + + const newPositions = new Float32Array(newParticlesF.length * 4); + const newVelocities = new Float32Array(newParticlesF.length * 4); + let offset = 0; + + for (let i = 0; i oldSize + const tempPosBuffer = device.createBuffer({ + label: "Temp positions buffer", + size: newSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + }); + const tempVelBuffer = device.createBuffer({ + label: "Temp velocities buffer", + size: newSize, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + }); + + // Fill the new section with the new data + device.queue.writeBuffer(tempPosBuffer, oldSize, newPositions); + device.queue.writeBuffer(tempVelBuffer, oldSize, newVelocities); + + // Fill the old section with the old data + const copyEncoder = device.createCommandEncoder(); // Create encoder + + if (GPUBuffers.positionBuffers && GPUBuffers.velocities) { + copyEncoder.copyBufferToBuffer(GPUBuffers.positionBuffers[frame % 2], 0, tempPosBuffer, 0, oldSize); + copyEncoder.copyBufferToBuffer(GPUBuffers.velocities, 0, tempVelBuffer, 0, oldSize); + } + + // Re-create the used buffers + createPosiVelsGPUBuffers (newSize, newSize); + + // Fill them with the data from the temporary buffers + copyEncoder.copyBufferToBuffer(tempPosBuffer, 0, GPUBuffers.positionBuffers[frame % 2], 0, newSize); + copyEncoder.copyBufferToBuffer(tempVelBuffer, 0, GPUBuffers.velocities, 0, newSize); + + device.queue.submit([copyEncoder.finish()]); // Submit encoder + + console.log("Frame " +frame+ ": Añadidas partículas manuales a los GPUBuffers."); + clearTempParticles(); + + } + + // Resetear posivels o cargar posiciones precargadas + if (flags.justLoadedSetup || flags.resetParts || frame === 0) { + + let offset = 0, pos = [], vel = []; + const positionsArray = new Float32Array(N*4); + const velocitiesArray = new Float32Array(N*4); + + // llenar positionsArray y velocitiesArray + for (let i = 0; i {return elementary.nombre == rule.targetName}); + const sourceIndex = elementaries.findIndex(elementary => {return elementary.nombre == rule.sourceName}); + + if (targetIndex === -1 || sourceIndex ===-1) { continue; } + activeRules.push(rule); + + const [f, c] = [targetIndex, sourceIndex].sort(); + + const index = (f * Ne) + c; + m[index]++; + } + + const Nr = activeRules.length; + + paramsArrays.Nr.set([Nr]); + device.queue.writeBuffer(GPUBuffers.params, 16, paramsArrays.Nr); + return [activeRules, Nr, m]; + } + function updateRulesBuffer(activeRules) { + const Nr = activeRules.length; + const rulesArray = new Float32Array(Nr * 8); + + for (let i = 0; i < Nr; i++) { // llenar el array de reglas + + const targetIndex = elementaries.findIndex(elementary => {return elementary.nombre == activeRules[i].targetName}); + const sourceIndex = elementaries.findIndex(elementary => {return elementary.nombre == activeRules[i].sourceName}); + + rulesArray.set([ + targetIndex, + sourceIndex, + activeRules[i].intensity, + activeRules[i].quantumForce, + activeRules[i].minDist, + activeRules[i].maxDist, + 0.0,//padding + 0.0, + ], 8*i) + } + + GPUBuffers.rules = device.createBuffer({ + label: "Reglas", + size: rulesArray.byteLength || 32, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + }); + device.queue.writeBuffer(GPUBuffers.rules, 0, rulesArray) + + flags.updateRules = false; + } + function writePStyleToBuffer() { + const data = new Float32Array([styleSettings.particleStyle.borderWidth, styleSettings.particleStyle.spherical]) + paramsArrays.pStyle.set(data); + device.queue.writeBuffer(GPUBuffers.params, 36, paramsArrays.pStyle); + flags.editPStyle = false; + } + function writeRNGSeedToBuffer() { + paramsArrays.seeds.set([ + rng() * 100, + rng() * 100, // seed.xy + 1 + rng(), + 1 + rng(), // seed.zw + ]) + device.queue.writeBuffer(GPUBuffers.params, 48, paramsArrays.seeds); + } + + // Camera updating + function renderNewPerspective() { + if (paused) { + updateCamera(); + const encoder = device.createCommandEncoder(); + render(encoder, frame); + device.queue.submit([encoder.finish()]); + } else { + flags.moveCamera = true; + } + updatePosInfoPanel(); + } + +// + +// ELEMENTOS HTML + + const + canvasContainer = document.getElementById("canvascontainer"), + // ambos paneles + panels = document.getElementById("panels"), + // panel de info + infoPanel = document.getElementById("infopanel"), + ageInfo = document.getElementById("ageinfo"), + fpsInfo = document.getElementById("fpsinfo"), + debugInfo = document.getElementById("debuginfo"), + canvasInfo = document.getElementById("canvasinfo"), + posInfo = document.getElementById("positioninfo"), + displayTiming = document.getElementById("performanceinfo"), + // panel de opciones + panelTitle = document.getElementById("controlPanelTitle"), + CPOptions = document.getElementById("controlPanelOptions"), + markers = { + 1: document.getElementById("marker1"), + 2: document.getElementById("marker2"), + 3: document.getElementById("marker3"), + }, + // opciones + pauseButton = document.getElementById("pausebutton"), + stepButton = document.getElementById("stepbutton"), + resetButton = document.getElementById("resetbutton"), + + seedInput = document.getElementById("seed"), + //preloadPosButton = document.getElementById("preloadPositions"), CODE 0 + + bgColorPicker = document.getElementById("bgcolorpicker"), + pStyleRange = document.getElementById("pstyle"), + + volumeRange = document.getElementById("volume"), + clickSound = document.getElementById("clicksound"), + + ambientOptionsTitle = document.getElementById("ambientoptionstitle"), + ambientOptionsPanel = document.getElementById("ambientoptions"), + ambientControls = { + inputs: { + friction: document.getElementById("friction"), + bounce: document.getElementById("bounce"), + vel: document.getElementById("initialvel"), + }, + updateButton: document.getElementById("ambientupdate"), + }, + + creadorPartTitle = document.getElementById("creadorparticulasTitle"), + creadorPartPanel = document.getElementById("creadorparticulas"), + partiControls = { + nameInput: document.getElementById("c.nom"), + colorInput: document.getElementById("c.col"), + cantInput: document.getElementById("c.cant"), + radiusInput:document.getElementById("c.radius"), + selector: document.getElementById("particleselect"), + submitButton: document.getElementById("c.elemsubmit"), + updateButton: document.getElementById("c.update"), + placeButton: document.getElementById("c.place"), + }, + borraParticleButton = document.getElementById("borraparticula"), + + creadorReglasTitle = document.getElementById("creadorreglasTitle"), + creadorReglasPanel = document.getElementById("creadorreglas"), + ruleControls = { + nameInput: document.getElementById("rulename"), + targetSelector: document.getElementById("targetselect"), + sourceSelector: document.getElementById("sourceselect"), + intens: document.getElementById("r.intens"), + qm: document.getElementById("r.qm"), + dmin: document.getElementById("r.dmin"), + dmax: document.getElementById("r.dmax"), + selector: document.getElementById("ruleselect"), + submitButton: document.getElementById("r.submit"), + updateButton: document.getElementById("r.update"), + }, + borraRuleButton = document.getElementById("borrarule"), + + exportButton = document.getElementById("export"), + importButton = document.getElementById("import"), + infoButton = document.getElementById("mostrarinfo"), + + helpDialog = document.getElementById("helpdialog"), + dialogOkButton = document.getElementById("dialogok"), + dialogNVMButton = document.getElementById("dialognvm"), + + newsDialog = document.getElementById("newsdialog"), + newsText = document.getElementById("newstext"), + dialogOk2Button = document.getElementById("dialogok2"), + dialogNVM2Button = document.getElementById("dialognvm2"), + + circle = document.getElementById("circle"), + arrowEnd = document.getElementById("arrowend"), + line = document.getElementById("line"), + tempParticles = document.getElementById("temporarycircles"); +// + +// EVENT HANDLING + + // Botones de tiempo + pauseButton.onclick = _=> pausar(); + stepButton.onclick = _=> stepear(); + resetButton.onclick = (event)=> { + if (event.ctrlKey) { + const path = SETUPS_FOLDER + "Cells GPU setup - Vacío.json" + importSetup(path) + .then( (setup) => { cargarSetup(setup, true);} ); + return; + } + resetear(); + } + + // Controles + document.addEventListener("keydown", function(event) { + + const isTextInput = event.target.tagName === "INPUT" && event.target.type === "text"; + + if (isTextInput || event.ctrlKey) { return; } + + if (event.target.type === "range") { event.target.blur(); } + + switch (event.code){ + case "Space": + event.preventDefault(); + pausar(); playSound(clickSound); + break; + case "KeyR": + resetear(); playSound(clickSound); + break; + case "KeyS": + stepear(); playSound(clickSound); + break; + case "KeyW": + hideCPOptions(); //playSound(clickSound); + break; + case "KeyM": + muted = !muted; + clickSound.volume = `${volumeRange.value * !muted}`; + let alpha = 1; + if (muted) { alpha = 0.3;} + volumeRange.style.setProperty("--thumbg", `rgba(255, 255, 255, ${alpha})`); + break; + case "KeyI": + switchVisibilityAttribute(infoPanel); + break; + case "KeyH": + switchVisibilityAttribute(panels); + break; + case "KeyD": + switchVisibilityAttribute(debugInfo); + break; + case "ArrowLeft": + event.preventDefault(); + eyePosition[0] -= moveSpeed; + renderNewPerspective(); + break; + case "ArrowRight": + event.preventDefault(); + eyePosition[0] += moveSpeed; + renderNewPerspective(); + break; + case "ArrowUp": + event.preventDefault(); + eyePosition[2] -= moveSpeed; + renderNewPerspective(); + break; + case "ArrowDown": + event.preventDefault(); + eyePosition[2] += moveSpeed; + renderNewPerspective(); + break; + case "KeyJ": + rotYCurrent -= rotateSpeed; + renderNewPerspective() + break; + case "KeyL": + rotYCurrent += rotateSpeed; + renderNewPerspective() + break; + } + }); + + // Creador de elementaries / partículas + partiControls.submitButton.onclick = _=> { + + let returnFlag = false, + name = partiControls.nameInput, + radius, cant; + + // Usar placeholders si vacíos. Si no lo están: validar. + if (name.value) { name = name.value; } + else if (name.placeholder) { name = name.placeholder; } + else { titilarBorde(name); returnFlag = true; } + + [cant, returnFlag] = checkAndGetNumberInput(partiControls.cantInput, returnFlag); + partiControls.radiusInput.max = Math.min(canvas.height, canvas.width)/2; + //console.log(partiControls.radiusInput.max); + [radius, returnFlag] = checkAndGetNumberInput(partiControls.radiusInput, returnFlag); + + if (returnFlag) { return; } + + const elemIndex = partiControls.selector.selectedIndex; + + const [pos, vel] = crearPosiVel(cant, elemIndex, radius * 2); + + cargarElementary( new Elementary( + name, + hexString_to_rgba(partiControls.colorInput.value, 1), + cant, + radius, + pos, + vel, + )); + setPlaceholdersParticles(); + removePartiControlsValues(); + borraParticleButton.hidden = false; + partiControls.placeButton.hidden = false; + switchClass(partiControls.updateButton, "disabled", false) + switchClass(ruleControls.updateButton, "disabled", true); + markers[2].hidden = false; + markers[3].hidden = true; + } + partiControls.updateButton.onclick = _=> { + if (partiControls.updateButton.classList.contains("disabled")) { return; } + + playSound(clickSound); + applyParticles(); + applyRules(); + } + partiControls.placeButton.onclick = _=> { + placePartOnClic = !placePartOnClic; + switchClass(partiControls.placeButton, "switchedoff"); + if (placePartOnClic) { + canvas.style.cursor = "crosshair"; + switchClass(borraParticleButton, "disabled", true); + } + else { + canvas.style.cursor = "default"; + switchClass(borraParticleButton, "disabled", newParticles.length); + } + } + + // Creador de reglas de interacción + ruleControls.submitButton.onclick = (event)=> { + + let returnFlag = false, + newRuleName = ruleControls.nameInput, + intens, qm, dmin, dmax; + + if (!ruleControls.targetSelector.options.length) { + titilarBorde(ruleControls.targetSelector); + returnFlag = true; + } + + if (!ruleControls.sourceSelector.options.length) { + titilarBorde(ruleControls.sourceSelector); + returnFlag = true; + } + + [intens, returnFlag] = checkAndGetNumberInput(ruleControls.intens, returnFlag); + [qm, returnFlag] = checkAndGetNumberInput(ruleControls.qm, returnFlag); + [dmin, returnFlag] = checkAndGetNumberInput(ruleControls.dmin, returnFlag); + [dmax, returnFlag] = checkAndGetNumberInput(ruleControls.dmax, returnFlag); + + if (returnFlag) { return; } + + const targetIndex = ruleControls.targetSelector.selectedIndex; + const sourceIndex = ruleControls.sourceSelector.selectedIndex; + + if (newRuleName.value) { newRuleName = newRuleName.value; } + else if (newRuleName.placeholder) { newRuleName = newRuleName.placeholder; } + else { // Si no hay nombres para poner, usa el nombre estándar + newRuleName = `${ruleControls.targetSelector.options[targetIndex].value} ← ${ruleControls.sourceSelector.options[sourceIndex].value}`; + } + + if (!event.ctrlKey) { // Si es un click normal, veo si debo añadir (n) al nombre + while (rules.some(rule => rule.ruleName == newRuleName)) { // Mientras sea nombre repetido, añade (n) + + if (/\(\d+\)$/.test(newRuleName)) { + newRuleName = newRuleName.replace(/\((\d+)\)$/, (_, number) => { + return "(" + (parseInt(number) + 1) + ")"; + }); + + } else { + newRuleName += " (1)"; + } + } + } + + const newRule = new Rule( + newRuleName, + ruleControls.targetSelector.value, + ruleControls.sourceSelector.value, + intens, + qm, + dmin, + dmax, + ); + + cargarRule(newRule) + + updateUIAfterRulesChange(); + removeRuleControlsValues(); + flags.rulesModified = true; + borraRuleButton.hidden = false; + + } + ruleControls.updateButton.onclick = _=> { + if (ruleControls.updateButton.classList.contains("disabled")) { return; } + + playSound(clickSound); + applyRules(); + } + ruleControls.targetSelector.onchange = _=> setPlaceholderRuleName(); + ruleControls.sourceSelector.onchange = _=> setPlaceholderRuleName(); + + // Rule manager + borraRuleButton.onclick = (event)=> { + const indexToDelete = ruleControls.selector.selectedIndex; + if (indexToDelete === -1) { + console.warn("Botón borraRuleButton debería estar desactivado.") + titilarBorde(ruleControls.selector) + return; + } + + if (event.ctrlKey) { + rules = []; + ruleControls.selector.innerHTML = ""; + borraRuleButton.hidden = true; + } else { + rules.splice(indexToDelete,1); + ruleControls.selector.options[indexToDelete].remove(); + if (!ruleControls.selector.length) { + borraRuleButton.hidden = true; + } + } + updateUIAfterRulesChange(); + flags.rulesModified = true; + } + ruleControls.selector.onchange = _=> setPlaceholdersRules(); + + // Particle manager + borraParticleButton.onclick = (event)=> { + if (borraParticleButton.classList.contains("disabled")) { return; } + + const indexToDelete = partiControls.selector.selectedIndex; + if (indexToDelete === -1) { + console.warn("Esto no debería haber pasado...") + titilarBorde(partiControls.selector) + return; + } + + if (event.ctrlKey) { + elementaries = []; + partiControls.selector.innerHTML = ""; + ruleControls.targetSelector.innerHTML = ""; + ruleControls.sourceSelector.innerHTML = ""; + allParticlesDeleted(); + } else { + elementaries.splice(indexToDelete, 1); + partiControls.selector.options[indexToDelete].remove(); + ruleControls.targetSelector.options[indexToDelete].remove(); + ruleControls.sourceSelector.options[indexToDelete].remove(); + if (!partiControls.selector.options.length) { + allParticlesDeleted(); + } + } + + setPlaceholdersParticles(); + switchClass(partiControls.updateButton,"disabled", false); + markers[2].hidden = true; + } + partiControls.selector.onchange = _=> setPlaceholdersParticles(); + + // Parar animaciones + CPOptions.addEventListener("animationend", function(event) { event.target.classList.remove("titilante"); }); + + // Ocultar interfaces + panelTitle.onclick = _=> hideCPOptions(); + ambientOptionsTitle.onclick = _=> switchVisibilityAttribute(ambientOptionsPanel); + creadorPartTitle.onclick = _=> switchVisibilityAttribute(creadorPartPanel); + creadorReglasTitle.onclick = _=> switchVisibilityAttribute(creadorReglasPanel); + + // Seed input + seedInput.onchange = _=> setRNG(seedInput.value); + seedInput.onclick = (event)=> { + if (event.ctrlKey) { + seedInput.value = seedInput.placeholder; + } + } + //preloadPosButton.onclick = _=> { preloadPositions = !preloadPositions; switchClass(preloadPosButton); } + + // Canvas color + bgColorPicker.oninput = _=> { + styleSettings.bgColor = hexString_to_rgba(bgColorPicker.value, 1); + if (paused) { + const encoder = device.createCommandEncoder(); + render(encoder, Math.max(frame - 1, 0)); // -1 porque al final del loop anterior se incrementó. + device.queue.submit([encoder.finish()]); + } + } + + // Particles stye + pStyleRange.oninput = _=> { + playSound(clickSound, false); + applyParticlesStyle(); + } + + // Particle placing + canvas.onmousedown = (ev)=> { + if (!placePartOnClic || ev.buttons !==1) { return; } + mouseIsDown = true; + canvas.style.cursor = "none"; + panels.style.pointerEvents = "none"; + + [mDownX, mDownY] = [ev.offsetX, ev.offsetY]; + + const elem = elementaries[partiControls.selector.selectedIndex]; + circle.style.width = elem.radio * 2 + "px"; + circle.style.backgroundColor = "rgba(" + elem.color.subarray(0, 3).map(x => x * 255) + "," + 0.6 + ")"; + + const strx = mDownX + "px"; + const stry = mDownY + "px"; + + circle.style.left = strx; + circle.style.top = stry; + + line.style.left = strx; + line.style.top = stry; + + arrowEnd.style.top = strx; + arrowEnd.style.bottom = stry; + arrowEnd.style.setProperty("--origin", "0px 0px" /*strx + " " + stry*/); + + circle.hidden = false; + } + canvas.onmousemove = (ev)=> { + + if (!placePartOnClic || !mouseIsDown) { + return; + } + + if (ev.buttons !== 1) { + mouseIsDown = false; + circle.hidden = true; + arrowEnd.hidden = true; + line.hidden = true; + panels.style.pointerEvents = "auto"; + canvas.style.cursor = "crosshair"; + return; + } + + const [dx, dy] = getDeltas(ev); + + const d = Math.sqrt(dx*dx + dy*dy); + const a = Math.atan2(dy,dx); + + line.style.width = d + "px"; + line.style.setProperty("--rot", a + "rad") + + arrowEnd.style.setProperty("--rot", a + "rad") + arrowEnd.style.left = ev.offsetX + "px"; + arrowEnd.style.top = ev.offsetY + "px"; + + arrowEnd.hidden = false; + line.hidden = false; + } + canvas.onmouseup = (ev)=> { + + if (!placePartOnClic) { return; } + circle.hidden = true; + arrowEnd.hidden = true; + line.hidden = true; + canvas.style.cursor = "crosshair"; + + if (!newParticles.length) { newParticles = Array.from(Array(elementaries.length), () => []); } + + mouseIsDown = false; + circle.hidden = true; + arrowEnd.hidden = true; + panels.style.pointerEvents = "auto"; + + const [dx, dy] = getDeltas(ev); + + const i = partiControls.selector.selectedIndex + const elem = elementaries[i]; + + // Revisar si entra en el canvas + const pos = [mDownX - canvas.width/2, -(mDownY - canvas.height/2), 0, i]; + + if (Math.abs(pos[0]) + elem.radio > canvas.width/2 || Math.abs(pos[1] + elem.radio > canvas.height/2)) { + return; + } + + // Agregar partícula a elementaries + + // Escalar el módulo del vector velocidad linealmente y luego exponencialmente. + const fac = 1/30; + const exp = 1.4; + const [x, y] = [dx*fac, dy*fac]; + const s = (x*x + y*y)**((exp-1)/2); + const vel = [x*s, -y*s, 0, 1]; + + const n = ++elem.cantidad * 4; + const newPos = new Float32Array(n); + const newVel = new Float32Array(n); + + newPos.set(elem.posiciones); + newPos.set(pos, n-4); + newVel.set(elem.velocidades); + newVel.set(vel, n-4); + elem.posiciones = newPos; + elem.velocidades = newVel; + + // Agregar a lista temporal para pasar al GPUBuffer + newParticles[i].push([pos, vel]); + + // Si está pausado, dibujar preview temporal con HTML y CSS. En lugar de eso podría pasarse al GPUBuffer y renderizar. + if (paused) { + const newPartC = circle.cloneNode(false); + const id = newParticles.flat().length/2; + newPartC.id = "newpartc" + id; + + if (dx || dy) { + const newPartL = line.cloneNode(false); + newPartL.id = "newpartl" + id; + newPartL.style.setProperty("--alpha", "0.2") + tempParticles.appendChild(newPartL); + newPartL.hidden = false; + } + tempParticles.appendChild(newPartC); + newPartC.hidden = false; + } + + partiControls.cantInput.placeholder = elem.cantidad; + + // Levantar flags + flags.updateSimParams = true; + flags.updateParticles = true; + //flags.resetParts = false; + } + + // Botón de info debug + infoButton.onclick = _=> switchVisibilityAttribute(infoPanel); + + // Botón de export e import + exportButton.onclick = (event)=> exportarSetup( + new Setup( + "Manualmente exportado", + seedInput.value, + { + friction: parseFloat(ambientControls.inputs.friction.placeholder), + bounce: parseInt(ambientControls.inputs.bounce.placeholder), + maxInitVel: parseFloat(ambientControls.inputs.vel.placeholder), + canvasDims: [canvas.width, canvas.height], + }, + elementaries, + rules + ), + "Cells GPU setup", + !!event.ctrlKey + ); + importButton.onclick = _=> { + importSetup() + .then( (setup) => { cargarSetup(setup, true); } ); + } + + // Sonidos + volumeRange.onchange = _=> { + clickSound.volume = `${volumeRange.value * !muted}`; + playSound(clickSound); + } + panels.onclick = (event)=> { + if (event.target.tagName === "BUTTON" && !event.target.classList.contains("disabled")) { + playSound(clickSound); + } + } + + // Opciones del ambiente/simulación + function enableIfChanged(inputs) { + let allFieldsEmpty = true; + for (const input in inputs) { + if (inputs[input].value) { + allFieldsEmpty = false; + break; + } + } + switchClass(ambientControls.updateButton, "disabled", allFieldsEmpty); + markers[1].hidden = allFieldsEmpty; + } + for (const input in ambientControls.inputs) { + ambientControls.inputs[input].onchange = _=> enableIfChanged(ambientControls.inputs); + } + ambientControls.inputs.bounce.oninput = _=> setAutomaticInputElementWidth(ambientControls.inputs.bounce, 3, 12, 0); + ambientControls.updateButton.onclick = _=> { + if (ambientControls.updateButton.classList.contains("disabled")) { return; } + playSound(clickSound); + applyAmbient(); + } +// + +// INICIALIZACIÓN + // Interfaz + + if (SHOW_DEBUG) { switchVisibilityAttribute(debugInfo); } + // Novedades + if (LAST_VISITED_VERSION !== CURRENT_VERSION) { + + newsText.innerText = CHANGELOG; + newsDialog.open = true; + + dialogOk2Button.onclick = _=> { + localStorage.setItem("STORED_VERSION_NUMBER", "_" + CURRENT_VERSION); + newsDialog.open = false; + } + dialogNVM2Button.onclick = _=> { + localStorage.setItem("STORED_VERSION_NUMBER", CURRENT_VERSION); //CURRENT_VERSION + newsDialog.open = false; + } + } + // Diálogo de ayuda + if ((NEW_USER === "1"|| NEW_USER === null)) { + + helpDialog.open = true; + + dialogOkButton.onclick = _=> { + localStorage.setItem("NEW_USER", 1); + helpDialog.open = false; + } + dialogNVMButton.onclick = _=> { + localStorage.setItem("NEW_USER", 0); + helpDialog.open = false; + } + } + // Tamaño canvas y sonido + canvasInfo.innerText = `${canvas.width} x ${canvas.height} (${(canvas.width/canvas.height).toFixed(6)})`; + clickSound.volume = volumeRange.value; + // Info + updatePosInfoPanel(); + + // Valores por defecto + ambientControls.inputs.friction.placeholder = ambient.friction.toFixed(3); + ambientControls.inputs.bounce.placeholder = ambient.bounce; + ambientControls.inputs.vel.placeholder = ambient.maxInitVel; + + // Inicializar seed o importar + switch (STARTING_SETUP_NUMBER) { + case 0: + setRNG(seedInput.value); + break; + case 1: + cargarSetup(generarSetupClásico(10, ""), false); //"0.6452130" x10 + break; + case 2: + const path = SETUPS_FOLDER + SETUP_FILENAME + ".json"; + importSetup(path) + .then( (setup) => { cargarSetup(setup, false);} ); + break; + case 3: + generarSetupDebug(10, ""); + break; + } +// + +// INICIALIZAR WEBGPU + + // Vértices + const v = 1; // ojo!: afecta el shader + const vertices = new Float32Array([ // Coordenadas en clip space + // X, Y, + -v, -v, // Triangle 1 (Blue) + v, -v, + v, v, + + -v, -v, // Triangle 2 (Red) + v, v, + -v, v, + ]); + const vertexBuffer = device.createBuffer({ + label: "Particle vertices", + size: vertices.byteLength, //12 * 32-bit floats (4 bits c/u) = 48 bytes + usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, + }); + device.queue.writeBuffer(vertexBuffer, /*bufferOffset=*/ 0, vertices); + + const vertexBufferLayout = { + arrayStride: 8, // cada vertex ocupa 8 bytes (2 *4-bytes) + attributes:[{ // array que es un atributo que almacena cada vertice (BLENDER!!!) + format: "float32x2", // elijo el formato adecuado de la lista de GPUVertexFormat + offset: 0, // a cuántos bytes del inicio del vertice empieza este atributo. + shaderLocation: 0, // Position, see vertex shader. es un identificador exclusivo de este atributo. de 0 a 15. + }] + }; + + const scenarioVertices = new Float32Array([ + // X, Y, Z + -xlim, -ylim, -zlim, // left wall upper triangle + -xlim, ylim, zlim, + -xlim, ylim, -zlim, + + -xlim, -ylim, -zlim, // left wall lower triangle + -xlim, -ylim, zlim, + -xlim, ylim, zlim, + + xlim, -ylim, -zlim, // right wall upper triangle + xlim, ylim, zlim, + xlim, ylim, -zlim, + + xlim, -ylim, -zlim, // right wall lower triangle + xlim, -ylim, zlim, + xlim, ylim, zlim, + + -xlim, -ylim, -zlim, // bottom wall near triangle + xlim, -ylim, -zlim, + xlim, -ylim, zlim, + + -xlim, -ylim, -zlim, // bottom wall far triangle + xlim, -ylim, zlim, + -xlim, -ylim, zlim, + + -xlim, ylim, -zlim, // upper wall near triangle + xlim, ylim, -zlim, + xlim, ylim, zlim, + + -xlim, ylim, -zlim, // upper wall far triangle + xlim, ylim, zlim, + -xlim, ylim, zlim, + + -xlim, -ylim, -zlim, // front wall lower triangle + xlim, -ylim, -zlim, + xlim, ylim, -zlim, + + -xlim, -ylim, -zlim, // front wall upper triangle + xlim, ylim, -zlim, + -xlim, ylim, -zlim, + + -xlim, -ylim, zlim, // back wall lower triangle + xlim, -ylim, zlim, + xlim, ylim, zlim, + + -xlim, -ylim, zlim, // back wall upper triangle + xlim, ylim, zlim, + -xlim, ylim, zlim + ]) + const vertexBuffer2 = device.createBuffer({ + label: "Walls vertices", + size: scenarioVertices.byteLength, // 12 tris * 3 vert per tri * 12 bytes per tri + usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC, + }); + device.queue.writeBuffer(vertexBuffer2, 0, scenarioVertices); + + // texture y su view, para multisampling (MSAA) + if (!textureView) { textureView = getTextureView(ambient.canvasDims); } + + const depthTexture = device.createTexture({ + size: [canvas.width, canvas.height], + format: 'depth24plus', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const renderPassDescriptor = { // Parámetros para el render pass que se ejecutará cada frame + colorAttachments: [{ // es un array, de momento sólo hay uno, su @location en el fragment shader es entonces 0 + view: textureView, + resolveTarget: context.getCurrentTexture().createView(), // para multisampling. Sin él, view sería esto. + loadOp: "clear", + clearValue: styleSettings.bgColor, + storeOp: "store", + }], + depthStencilAttachment: { + view: depthTexture.createView(), + depthClearValue: 1.0, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }, + }; + + + // Shaders + + const particleShaderModule = device.createShaderModule({ + label: "Particle shader", + code: renderShader3D(), + }); + const wallShaderModule = device.createShaderModule({ + label: "Walls shader", + code: wallShader3D(), + }); + const simulationShaderModule = device.createShaderModule({ + label: "Compute shader", + code: computeShader3D(WORKGROUP_SIZE), + }) + + // Bind groups + + let bindGroups = []; // updateSimulationParameters los actualiza. + const bindGroupLayoutPos = device.createBindGroupLayout({ + label: "Positions Bind Group Layout", + entries: [{ + binding: 0, // Entrada. Siempre voy a renderizar éste. + visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE, + buffer: { type: "read-only-storage" } + }, { + binding: 1, // salida + visibility: GPUShaderStage.COMPUTE, + buffer: { type: "storage" } + }] + }); + + const bindGroupLayoutResto = device.createBindGroupLayout({ + label: "Resto Bind Group Layout", + entries: [{ + binding: 0, // Parámetros de longitud fija + visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT, + buffer: { type: "uniform"} + }, { + binding: 1, // velocidades + visibility: GPUShaderStage.COMPUTE, + buffer: { type: "storage" } // Initial state input buffer + }, { + binding: 2, // reglas + visibility: GPUShaderStage.COMPUTE, + buffer: { type: "read-only-storage"} + }, { + binding: 4, // Datos elementaries (cantidades, radio, color) + visibility: GPUShaderStage.COMPUTE | GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, + buffer: { type: "read-only-storage" } + }, { + binding: 6, // render perspective + visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, + buffer: { type: "uniform"} + }] + }); + + + // Pipelines + + const pipelineLayout = device.createPipelineLayout({ + label: "Pipeline Layout", + bindGroupLayouts: [ bindGroupLayoutPos, bindGroupLayoutResto], + }); // El orden de los bind group layouts tiene que coincider con los atributos @group en el shader + + const renderPipelineDescriptor = { + label: "Particle render pipeline", + layout: pipelineLayout, + vertex: { + module: particleShaderModule, + entryPoint: "vertexMain", + buffers: [vertexBufferLayout] + }, + fragment: { + module: particleShaderModule, + entryPoint: "fragmentMain", + targets: [{ + format: canvasFormat + }] + }, + multisample: { + count: sampleCount, + }, + depthStencil: { + depthWriteEnabled: true, + depthCompare: "less", + format: "depth24plus", + }, + } + + const simulationPipelineDescriptor = { + label: "Simulation pipeline", + layout: pipelineLayout, + compute: { + module: simulationShaderModule, + entryPoint: "computeMain", + constants: { // es una entrada opcional, acá puedo poner valores que usará el compute shader + //constante: 1, // Así paso el workgroup size al compute shader + }, + }, + } + + // Crear render pipelines + const particleRenderPipeline = device.createRenderPipeline(renderPipelineDescriptor); + + const wallRenderPipeline = device.createRenderPipeline({ + layout: "auto", + vertex: { + module: wallShaderModule, + entryPoint: "vertexMain", + buffers:[{ + arrayStride: 12, + attributes:[{ + format: "float32x3", + offset: 0, + shaderLocation: 0, + }] + }, + ], + }, + fragment: { + module: wallShaderModule, + entryPoint: "fragmentMain", + targets: [{ + format: canvasFormat + }], + }, + depthStencil: { + depthWriteEnabled: true, + depthCompare: "always", + format: "depth24plus", + }, + }) + + + // Crear compute pipelines + const simulationPipeline = device.createComputePipeline(simulationPipelineDescriptor); + + // Buffers + + // Parámetros de longitud fija (por lo tanto buffers de size fijo) + + const paramsBufferSize = 8 + 4 + 4 + 4 + 4 + 4 + 8 + 8 + 4 + 16 + 12 + 4; + // [canvasDims], N, Ne, Nr, Nd, Npi, [frictionInv, bounceF], [borderStart, spherical], padding, [4 RNGSeeds], [3 lims], padding + const paramsArrBuffer = new ArrayBuffer(paramsBufferSize); + + const paramsArrays = { + canvasDims: new Float32Array(paramsArrBuffer, 0, 2), // offset en bytes, longitud en cant de elementos + N: new Uint32Array(paramsArrBuffer, 8, 1), // Cantidad total de partículas + Nr: new Uint32Array(paramsArrBuffer, 16, 1), // Cantidad de reglas activas (que involucran elementaries cargados) + Nd: new Uint32Array(paramsArrBuffer, 20, 1), // Cantidad total de distancias a precalcular (deprecated) + Ne: new Uint32Array(paramsArrBuffer, 12, 1), // Cantidad de elementaries + Npi: new Uint32Array(paramsArrBuffer, 24, 1), // Cantidad de pares de interacción distintos + ambient: new Float32Array(paramsArrBuffer, 28, 2), // Parámetros de entorno + pStyle: new Float32Array(paramsArrBuffer, 36, 2), // Estilo visual de las partículas + // 4 bytes of padding + seeds: new Float32Array(paramsArrBuffer, 48, 4), // Seed para el rng en los shaders + lims: new Float32Array(paramsArrBuffer, 64, 3), // Paredes para colisiones + } + + paramsArrays.lims.set([xlim, ylim, zlim]) + + const GPUBuffers = { + params: device.createBuffer({ + label: "Params buffer", + size: paramsBufferSize, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + }), + + renderPerspective: device.createBuffer({ + label: "Render perspective buffer", + size: 16*4, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + }), + + scenarioData: device.createBuffer({ + label: "Scenario data buffer", + size: 12, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + }), + + }; + device.queue.writeBuffer(GPUBuffers.params, 64, paramsArrays.lims); + device.queue.writeBuffer(GPUBuffers.scenarioData, 0, paramsArrays.lims); + updateCamera(); + + + const scenarioBindGroups = [ + device.createBindGroup({ + label: "Scenario BindGroup", + layout: wallRenderPipeline.getBindGroupLayout(0), + entries: [{ + binding: 0, + resource: { buffer: GPUBuffers.renderPerspective } + }, { + binding: 1, + resource: { buffer: GPUBuffers.scenarioData } + }], + }), + ]; +// + +// Funciones importantes + +function editBuffers() { + + let msg = ""; + + // Canvas size + if (flags.updateCanvas) { + writeCanvasToBuffer(); + msg += "canvas/"; + } + + // Entorno + if (flags.editAmbient) { + writeAmbientToBuffer(); + msg += "ambient/"; + } + + // Datos elementaries, posiciones y velocidades + if (flags.updateParticles) { + updateParticlesBuffers(); + msg += "posivels/"; + } + + // Reglas + if (flags.updateRules) { + const [activeRules, Nr, m] = updateActiveRules(); // Reglas parte A + + updateRulesBuffer(activeRules); // Reglas parte B + msg += "rules/"; + } + + // Estilo + if (flags.editPStyle) { + writePStyleToBuffer(); + msg += "pstyle/"; + } + + if (!msg) { console.warn("No se editó ningún buffer."); } + return msg.slice(0,-1); +} + +function updateSimulationParameters() { + + setRNG(seedInput.value); + + // CREACIÓN DE BUFFERS + const msg = editBuffers(); + + if (N === 0) { flags.updateSimParams = false; return;} + + // Shaders pueden ir acá + + // Bind groups + + bindGroups = [ + device.createBindGroup({ // posiciones A + label: "Particle positions bind group A", + layout: bindGroupLayoutPos, + entries: [{ + binding: 0, + resource: { buffer: GPUBuffers.positionBuffers[0] } + }, { + binding: 1, + resource: { buffer: GPUBuffers.positionBuffers[1] } + }], + }), + device.createBindGroup({ // posiciones B + label: "Particle positions bind group B", + layout: bindGroupLayoutPos, + entries: [{ + binding: 0, + resource: { buffer: GPUBuffers.positionBuffers[1] } + }, { + binding: 1, + resource: { buffer: GPUBuffers.positionBuffers[0] } + }], + }), + device.createBindGroup({ // el resto de bind groups + label: "Resto bind group", + layout: bindGroupLayoutResto, + entries: [ + { + binding: 0, // Parámetros de longitud fija + resource: { buffer: GPUBuffers.params } // los resources admiten más parametros (offset, size) + }, { + binding: 1, + resource: { buffer: GPUBuffers.velocities } + }, { + binding: 2, + resource: { buffer: GPUBuffers.rules } + }, { + binding: 4, + resource: { buffer: GPUBuffers.datosElementaries } + }, { + binding: 6, + resource: { buffer: GPUBuffers.renderPerspective } + } + ], + }), + + ]; + + // Pipelines pueden ir acá + + // Actualizar workgroup counts para compute passes + workgroupCount = Math.ceil(N / WORKGROUP_SIZE); + //console.log( `N / workgroup size: ${N} / ${WORKGROUP_SIZE} = ${N/WORKGROUP_SIZE}\nworkgroup count: ${workgroupCount}`); + + console.log("Updated sim params: " + msg + "."); + flags.updateSimParams = false; +} + +// Funciones para el loop principal + +function render(encoder, frame) { + // Actualizar color de fondo. + renderPassDescriptor.colorAttachments[0].clearValue = styleSettings.bgColor; + + // Actualizar matriz de proyección + + if (sampleCount > 1) { + renderPassDescriptor.colorAttachments[0].view = textureView; + renderPassDescriptor.colorAttachments[0].resolveTarget = context.getCurrentTexture().createView(); + } else { + renderPassDescriptor.colorAttachments[0].view = context.getCurrentTexture().createView(); + renderPassDescriptor.colorAttachments[0].resolveTarget = undefined; + } + + const pass = encoder.beginRenderPass(renderPassDescriptor); + + /* + pass.setPipeline(wallRenderPipeline); + pass.setVertexBuffer(0,vertexBuffer2); + pass.setBindGroup(0, scenarioBindGroups[0]); + pass.draw(scenarioVertices.length/3);*/ + + if (N) { + pass.setPipeline(particleRenderPipeline); + pass.setVertexBuffer(0, vertexBuffer); + pass.setBindGroup(0, bindGroups[frame % 2]); + pass.setBindGroup(1, bindGroups[2]); + pass.draw(vertices.length /2, N); // 6 vertices. renderizados N veces + } + + + + + pass.end(); // finaliza el render pass +} + +function computeNextFrame(encoder, frame) { + if (N) { // Aunque no haya reglas activas, las partículas pueden estar moviéndose. Hay que calcular su pos. + + timestamp(2, encoder); // Compute dist + + writeRNGSeedToBuffer(); + + // Calcular simulación (actualizar posiciones y velocidades) + const computePass = encoder.beginComputePass(); + computePass.setPipeline(simulationPipeline); + computePass.setBindGroup(0, bindGroups[frame % 2]); // posiciones alternantes + computePass.setBindGroup(1, bindGroups[2]); // lo demás + /* El compute shader se ejecutará N veces. El workgroup size es 64, entonces despacho ceil(N/64) workgroups, todos en el eje x. */ + computePass.dispatchWorkgroups(workgroupCount, 1, 1); // Este vec3 tiene su propio @builtin en el compute shader. + computePass.end(); + + } else {timestamp(2, encoder);} // render - compute all (=0) +} + +function updateCamera() { + + projectionMatrix = new Mat4(); + viewProjectionMatrix = new Mat4(); + + projectionMatrix.perspectiveZO(1.0, canvas.width / canvas.height, 0.1, 3000.0); + //projectionMatrix.orthoZO(-canvas.width/2,canvas.width/2,-canvas.height/2,canvas.height/2,0.1,5000); + + //printMatrix(projectionMatrix); + + viewProjectionMatrix.rotate(rotYCurrent, rotAxis); + viewProjectionMatrix.translate(eyePosition.map(x => -x)); + + viewProjectionMatrix.multiply2(projectionMatrix); + + device.queue.writeBuffer(GPUBuffers.renderPerspective, 0, viewProjectionMatrix); + + flags.moveCamera = false; +} +// ANIMATION LOOP + +async function newFrame() { + + if (flags.moveCamera) { + updateCamera(); + const encoder = device.createCommandEncoder(); + render(encoder, frame); + device.queue.submit([encoder.finish()]); + } + + if (paused && !stepping) { return; } + + if (flags.updateSimParams) { // Rearmar buffers y pipeline + updateSimulationParameters(); + } + + const encoder = device.createCommandEncoder(); + + timestamp(0, encoder); + + render(encoder, frame); + + timestamp(1, encoder); + + computeNextFrame(encoder, frame); + + timestamp(3, encoder); + + device.queue.submit([encoder.finish()]); + + + if ( false && (frame % 60 === 30)) { //frame % 60 === 30 + + const values = new Float32Array(await readBuffer(device, GPUBuffers.positionBuffers[0])); + + const values2 = []; + //for (let i=2; i 30) { + text += "\nGPU: Brrrrrrrrrrr"; + } + displayTiming.innerText = text; + } + + frame++; frameCounter++; + ageInfo.innerText = frame; // "Edad = frame drawn on screen + 1" + + const timeNow = performance.now(); + if (timeNow - refTime >= 1000) { + fps = frameCounter; + frameCounter = 0; + refTime = timeNow; + fpsInfo.innerText = fps; + } + + if ( !stepping ) { animationId = requestAnimationFrame(newFrame); } +} + +//TODO: +/* +PERMITIR APLICAR PARTÍCULAS SIN RESETEAR POSIVELS. WE HAVE THE TECHNOLOGY! +*/ +/* Ctrl + Arrastrar para colocar un trazo de partículas*/ +/* Pasar los parámetros pertinentes mediante writebuffer en lugar de recrear nuevos buffers */ +/* Funciones para quitar o agregar partículas. permite mergers/eaters */ +/* Antialiasing / renderizar a mayor resolución */ +/* Fondo con efectos con shader */ diff --git a/WebGPU/scripts/cellsGPU.js b/WebGPU/scripts/cellsGPU.js index d8c9886..99e7b59 100644 --- a/WebGPU/scripts/cellsGPU.js +++ b/WebGPU/scripts/cellsGPU.js @@ -113,7 +113,7 @@ textureView; size: 8 * capacity, usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, }); - } + } // // FUNCIONES VARIAS Y CLASES - TODO: Modularizar @@ -1651,7 +1651,6 @@ textureView; switchClass(ruleControls.updateButton, "disabled", true); markers[2].hidden = false; markers[3].hidden = true; - } partiControls.updateButton.onclick = _=> { if (partiControls.updateButton.classList.contains("disabled")) { return; } @@ -2030,13 +2029,9 @@ textureView; playSound(clickSound); applyAmbient(); } - - - // // INICIALIZACIÓN - // Interfaz if (SHOW_DEBUG) { switchVisibilityAttribute(debugInfo); } @@ -2099,33 +2094,33 @@ textureView; // INICIALIZAR WEBGPU // Vértices - const v = 1; // ojo!: afecta el shader - const vertices = new Float32Array([ // Coordenadas en clip space - // X, Y, - -v, -v, // Triangle 1 (Blue) - v, -v, - v, v, - - -v, -v, // Triangle 2 (Red) - v, v, - -v, v, - ]); - const vertexBuffer = device.createBuffer({ - label: "Particle vertices", - size: vertices.byteLength, //12 * 32-bit floats (4 bits c/u) = 48 bytes - usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, - }); - device.queue.writeBuffer(vertexBuffer, /*bufferOffset=*/ 0, vertices); - - const vertexBufferLayout = { - arrayStride: 8, // cada vertex ocupa 8 bytes (2 *4-bytes) - attributes:[{ // array que es un atributo que almacena cada vertice (BLENDER!!!) - format: "float32x2", // elijo el formato adecuado de la lista de GPUVertexFormat - offset: 0, // a cuántos bytes del inicio del vertice empieza este atributo. - shaderLocation: 0, // Position, see vertex shader. es un identificador exclusivo de este atributo. de 0 a 15. - }] - }; - // + const v = 1; // ojo!: afecta el shader + const vertices = new Float32Array([ // Coordenadas en clip space + // X, Y, + -v, -v, // Triangle 1 (Blue) + v, -v, + v, v, + + -v, -v, // Triangle 2 (Red) + v, v, + -v, v, + ]); + const vertexBuffer = device.createBuffer({ + label: "Particle vertices", + size: vertices.byteLength, //12 * 32-bit floats (4 bits c/u) = 48 bytes + usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, + }); + device.queue.writeBuffer(vertexBuffer, /*bufferOffset=*/ 0, vertices); + + const vertexBufferLayout = { + arrayStride: 8, // cada vertex ocupa 8 bytes (2 *4-bytes) + attributes:[{ // array que es un atributo que almacena cada vertice (BLENDER!!!) + format: "float32x2", // elijo el formato adecuado de la lista de GPUVertexFormat + offset: 0, // a cuántos bytes del inicio del vertice empieza este atributo. + shaderLocation: 0, // Position, see vertex shader. es un identificador exclusivo de este atributo. de 0 a 15. + }] + }; + // texture y su view, para multisampling (MSAA) if (!textureView) { textureView = getTextureView(ambient.canvasDims); } @@ -2305,8 +2300,8 @@ textureView; params: device.createBuffer({ label: "Params buffer", size: paramsBufferSize, - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST - }), + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }) }; // @@ -2411,7 +2406,7 @@ function updateSimulationParameters() { }, { binding: 5, resource: { buffer: GPUBuffers.datosInteracciones } - }, + } ], }), /*device.createBindGroup({ @@ -2438,7 +2433,10 @@ function updateSimulationParameters() { // Funciones para el loop principal function render(encoder, frame) { - renderPassDescriptor.colorAttachments[0].clearValue = styleSettings.bgColor; // Actualizar color de fondo. + // Actualizar color de fondo. + renderPassDescriptor.colorAttachments[0].clearValue = styleSettings.bgColor; + + // Actualizar matriz de proyección if (sampleCount > 1) { renderPassDescriptor.colorAttachments[0].view = textureView; @@ -2516,14 +2514,15 @@ async function newFrame() { if ( false && (frame % 60 === 30)) { - const values = new Float32Array(await readBuffer(device, GPUBuffers.velocities )); + const values = new Float32Array(await readBuffer(device, GPUBuffers.velocities)); const values2 = []; - for (let i=2; i positionsIn: array; // read only + @group(0) @binding(1) var positionsOut: array; //al poder write, lo uso como output del shader + + @group(1) @binding(0) var params: Params; // parameters + @group(1) @binding(1) var velocities: array; //al poder write, lo uso como output del shader + @group(1) @binding(2) var rules: array; + @group(1) @binding(4) var elems: array; + // + + // rng mostrado en Hello Triangle demos: + var seed : vec2u; + var rand_seed : vec2f; + + fn init_rng2(invocation_id : u32, seed : vec4) { + rand_seed = seed.xz; + rand_seed = fract(rand_seed * cos(35.456 + f32(invocation_id) * seed.yw)); + rand_seed = fract(rand_seed * cos(41.235 + f32(invocation_id) * seed.xw)); + } + + fn rng2() -> f32 { + rand_seed.x = fract(cos(dot(rand_seed, vec2(23.14077926, 232.61690225))) * 136.8168); + rand_seed.y = fract(cos(dot(rand_seed, vec2(54.47856553, 345.84153136))) * 534.7645); + return rand_seed.y; + } + + fn applyrule2( posi: vec3f, posj: vec3f , d:f32, g: f32, q: f32, rmin: f32, rmax: f32 ) -> vec3f { + // Usa el rng mostrado en HelloTriangle + if d > rmax { + return vec3f(); + } + if d >= rmin { + let f = -g/(d*d); + return vec3f(posi.x - posj.x, posi.y - posj.y, posi.z - posj.z) * f; + } + return (vec3f(rng2(), rng2(), rng2()) * 2 - 1) * q; + } + + @compute + @workgroup_size(${sz}, 1, 1) // el tercer parámetro (z) es default 1. + fn computeMain(@builtin(global_invocation_id) ind: vec3u){ + + let i = ind.x; // index global + let n = params.n; + + if i >= n { + return; + } + + init_rng2(i, params.seeds); + + //var iterations = u32(); // debug iterations counter + + let k = u32(positionsIn[i].w); // pos.w is used as elementary index. + + var pos = positionsIn[i].xyz; + var vel = velocities[i].xyz; + var deltav = vec3f(); + var kj = u32(); // elementary index de pj + + // por cada regla: -- Es mucho más eficiente tomar el loop más corto como el más externo. + for (var r: u32 = 0; r < params.nr; r++) { + + if k != u32(rules[r].tarInd) { continue; } + + // Por cada partícula: + for (var pj: u32 = 0; pj < n; pj++) { + + if pj == i {continue;} + + kj = u32(positionsIn[pj].w); + + //iterations++; + // revisar si esta regla le afecta y pj es source + if kj == u32(rules[r].srcInd) { + + // Obtengo la posición de pj + let posj = positionsIn[pj].xyz; + let ilj = pj - elems[kj].cantAcum2; // índice local de pj + + // Obtengo la distancia a pj + let d = distance(pos, posj); + + deltav += applyrule2(pos, posj, d, rules[r].g, rules[r].q, rules[r].mind, rules[r].maxd); + } + } + } + + vel = (vel + deltav) * params.frictionInv; + + pos += vel; + + let bordex = params.lims.x; + let bordey = params.lims.y; + let bordez = params.lims.z; + + let r = elems[k].radio; + + if abs(pos.x) > bordex - r { + pos.x = 2 * sign(vel.x) * (bordex - r) - (pos.x); // parece que es seguro usar sign: vel nunca es 0 aquí. + vel.x *= -params.bounceF; + } + if abs(pos.y) > bordey - r { + pos.y = 2 * sign(vel.y) * (bordey - r) - (pos.y); + vel.y *= -params.bounceF; + } + if abs(pos.z) > bordez - r { + pos.z = 2 * sign(vel.z) * (bordez - r) - (pos.z); + vel.z *= -params.bounceF; + } + + positionsOut[i].x = pos.x; + positionsOut[i].y = pos.y; + positionsOut[i].z = pos.z; + positionsOut[i].w = positionsIn[i].w; + + velocities[i].x = vel.x; + velocities[i].y = vel.y; + velocities[i].z = vel.z; + //velocities[0].w = ;// for debugging + } + `; +} + +export function renderShader3D() { return /*wgsl*/` + + struct Params { + ancho: f32, + alto: f32, + n: u32, + ne: u32, + + nr: u32, + nd: u32, + lp: u32, + frictionInv: f32, + + bounceF: f32, + borderStart: f32, + spherical: f32, + padding: f32, + + seeds: vec4f, + + lims: vec3f, + + } + + struct DatosElementaries { + cant: u32, + cantAcum: u32, + cantAcum2: u32, + radio: f32, + color: vec4f, + } + + // Bindings + @group(0) @binding(0) var updatedpositions: array>; // read only + + @group(1) @binding(0) var params: Params; // parameters + @group(1) @binding(4) var elems: array; + @group(1) @binding(6) var perspective: mat4x4; + // + + // VERTEX SHADER + + struct VertexInput { + @builtin(instance_index) instance: u32, // índice de cada instancia. Hay N instancias. + @builtin(vertex_index) vertex: u32, // índice de cada vértice. Hay 6 vertices. + @location(0) pos: vec2f, // índice 0 de la lista de vertex attributes en el vertex buffer layout. + // TODO: Ver si puedo traer desde el compute shader al índice de elementary (k). + }; + + struct VertexOutput{ + @builtin (position) pos: vec4f, // posición de cada vértice || para el fragment shader, al parecer usa un sist. coords distinto (abs from top left) + @location(1) @interpolate(flat) idx: u32, // Índice de instancia + @location(2) quadpos: vec2f, + @location(3) @interpolate(flat) k: u32, + @location(4) @interpolate(flat) random: f32, + }; + + fn LFSR( z: u32, s1: u32, s2: u32, s3: u32, m:u32) -> u32 { + let b = (((z << s1) ^ z) >> s2); + return (((z & m) << s3) ^ b); + } + fn rng(i: u32) -> f32 { + let seed = i*1099087573; + let z1 = LFSR(seed,13,19,12, u32(4294967294)); + let z2 = LFSR(seed,2 ,25,4 , u32(4294967288)); + let z3 = LFSR(seed,3 ,11,17, u32(4294967280)); + let z4 = 1664525 * seed + 1013904223; + let r0 = z1^z2^z3^z4; + return f32( r0 ) * 2.3283064365387e-10 ; + } + + @vertex + fn vertexMain(input: VertexInput) -> VertexOutput { + + let idx = input.instance; + + let ancho = params.ancho; + let alto = params.alto; + let ar = ancho/alto; + + let k = u32(updatedpositions[idx].w); // elementary index. + + let diameter = f32(elems[k].radio);// * 2 / ancho; // diámetro en pixeles. Dividido ancho da en clip space [-1 1] + + // OUTPUT + var output: VertexOutput; + output.pos = vec4f(updatedpositions[idx].xyz, 1); + + output.pos.z += params.lims.z; + + output.pos = perspective * output.pos; // output.pos must end up in clip space: [x(-1,1) y(-1,1) z(0,1)]/w + + //output.pos.z = min(output.pos.z, 2); + + output.pos.x += input.pos.x * diameter; + output.pos.y += input.pos.y * diameter * ar; + + output.idx = idx; // índice de cada instancia, pasado a float para el fragment shader + output.quadpos = input.pos; + output.k = k; + output.random = mix(0.8, 1.2, rng(idx) ); + + return output; + } + + // FRAGMENT SHADER + + @fragment + fn fragmentMain(input: VertexOutput) -> @location(0) vec4f { // @location(n) está asociado al índice n del colorAttachment en el renderpass a utilizar + + let r = length(input.quadpos); + if r > 1 { discard; } + + //let idx = input.idx; + let k = input.k; + + //let border = step(r, 1 - params.borderStart/elems[k].radio); // Si uso borderStart como ancho en píxeles + let border = step(r, params.borderStart); + + let gradient = ( 1 + params.spherical * (sqrt(1 - r*r) - 1) ); + var color_xyz = elems[k].color.xyz * border * gradient * input.random; + + let test = vec3f(input.pos.x,input.pos.y,input.pos.z); + + if input.idx == 3801 {color_xyz = vec3f(0,1,1);} + + return vec4f(color_xyz, 1); //color_xyz + + } + `; +} + +export function wallShader3D() { return /*wgsl*/` + + @group(0) @binding(0) var perspective: mat4x4; + @group(0) @binding(1) var lims: vec3f; + + struct VertexInput { + @location(0) pos: vec3f, // índice 0 de la lista de vertex attributes en el vertex buffer layout. + @builtin(vertex_index) ind: u32, + }; + + struct VertexOutput{ + @builtin (position) pos: vec4f, + @location(1) vpos: vec3f, + }; + + + @vertex + fn vertexMain(input:VertexInput) -> VertexOutput { + + let limsx = lims.x; + let pers = perspective[0][0]; + + var output: VertexOutput; + output.pos = perspective * vec4f(input.pos, 1); + + output.vpos = vec3f(input.pos/lims); + + return output; + } + + @fragment + fn fragmentMain(input: VertexOutput) -> @location(0) vec4f { + + return vec4f(input.vpos, 1); + } + `; } \ No newline at end of file diff --git a/data/Cells GPU setup - Test posiciones.json b/data/Cells GPU setup - Test posiciones.json new file mode 100644 index 0000000..24d3f99 --- /dev/null +++ b/data/Cells GPU setup - Test posiciones.json @@ -0,0 +1,31 @@ +{ + "name": "Test posiciones", + "seed": "", + "ambient": { + "friction": 0, + "bounce": 100, + "maxInitVel": 0, + "canvasDims": [ + "auto", + "auto" + ] + }, + "elementaries": [ + { + "nombre": "A", + "color": [ + 1, + 0, + 0, + 1 + ], + "cantidad": 1, + "radio": 10, + "posiciones": [350,300,0,0], + "velocidades": [-1,-1,0,0] + } + ], + "rules": [ + + ] +} \ No newline at end of file diff --git a/index.html b/index.html index 268fb82..e22c150 100644 --- a/index.html +++ b/index.html @@ -32,6 +32,9 @@

Simulador de partículas customizable



+ +

Serious business

+

Controles: Space/W/S/R

diff --git a/sounds/crystal_click.wav b/sounds/crystal_click.wav new file mode 100644 index 0000000000000000000000000000000000000000..e5dc388f51d58714a474b22554381b1fad6724a1 GIT binary patch literal 26804 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 Y0-{M0`YpObDnFIeHsSktzVIRZKMAm2Pyhe` literal 0 HcmV?d00001 diff --git a/sounds/glass_click.wav b/sounds/glass_click.wav new file mode 100644 index 0000000000000000000000000000000000000000..7fc0b81a2b73d5bde4c594e52fbb7d9a19b9f8e3 GIT binary patch literal 71874 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� z*oi}}#3-~m`;_-O#Y9oZj0Ky`hWYpMXCa0cao6i&)>zt&_x%zaPHL!Yx7| z#cv=fxM;FR(`-n)Viz}}dnr~cy%UKYN??t~BGG|Wk5F;^1774+f?bOQAN|y5>M8I) zDltPi^sX4b-qfY?2x12xX0pLOhJv9`@4&e$2_78Ig
;M?{T6=*U9RQ3VKr0p2@ zJRZXCKHxQ+e`Zc6y->Q}Htvd+3q?*!V;`*Fv)PV$IO6M8zF20ZP*3Osj=vaB`d0#2 z@9%L2@?rC=*0a`2b@)AX33P{pB$k$~Ws8q@C8QM7)13i!0$L$Nu{h1D}$ z(Phv|Xel2;v95(|WxOHXwl1PcnFFBLS7{iO9stdf0d%*tJO6!|6`ME4lunwb;G&IL zu(~~%*?o({hdoC@(2!K7t13@3Z8O3AfC9yNXmE9FF7io^n`vq2Cc*yb3z9Brto_J4 z&i0cTEFEMB{j+vqX%NtbZTXa4FT`MuNMVeWB^7*X<|ltJ=f+K+2g`aKWhx&hVvdC* znW$M3qZn#;wW7(#11YvunN_M+3iw|g?%~||RKIl{TP{1D64gT?du2~>zc19j2#UbI zQL3~|?KpZqZpX-NY21l%axBi}2{+kPoui(!xa32N*^L{$Xv8H%{r6|FMO+gyjE zVyXW2*hzI3=wqoqW8_5riDCA>0fFO|hWk!k>f$zkY0!K#A9ZUhS&L}%=!IbmeIvbD z^upkf2=i!5b#I^0dA{bt@sHDb>g<2P zYhXOm#Kb_TYNBsosHZRdGtn_HHXmtXZa9kl?N8lGRyeW<_wh4#b#VM!cf$WH)dM0U z!_4*cmMmGKyTnL0G~8d$z|73-hZsXc9pMU{h?tN_?`WNnh{3-K`Av?4Z^ZoYz_7@` z&=B@RuJ^pq#gUfk>OVUA{qJwjwJ_}WjzS`Ie|bjT`JoH-qP@fP40QGNewQpXv=DS+ zK2dmjAMZ$S2k%HZw{gJ z7cUg<{cmD@1hGc8c82!)CI)7Xe-mq_Z|7iVY-DEdIBMibQ-j~c{$1055<4k8P*{)N z!GCwnkGuXw{HVVs{$Ggrr*6gtMnr~&$NV-yex3fmOp~d;i~fA}xA_ws_{+2j^A3;j z{V{}=>c5TQ-!Fz=CH-jg%TzG;p8sRLSw(n9`TAJs{ps8vH}#K=0{?l)jQ-n)|5f(i zHT|#UFA4As`O_jbGB-3cH#Yojk^U;`m#hA3ArYZ|kxRV8eQo`PSzz_++s<~ePv9@_ z7yGFuOlwA9h)?K}h+)4y_n%w**OKQ4c!&7=`dI1xc%}Y$sr)A3w;%qo>8}evIPibV zfiwN@X6^qE2k!sq0Ws|C3jmsr`Rv zhWt+k>>muz|E#os-%9)M?Z^Kg4&480Vq?NZ|GKcC#>n!n(ve5hE^xy9P-*$(>ba42uwzLa>-P1e7gm^CuoIlamCs1%? z{HCKHTbW-Po4bVz9+QZ_JOAUSA3JM1Z~s4^{q2YUyIZDT`-%L~kBj$0Un~2?zLP@( z1uB4v-r+&M;Ss+KrNcz?Kd$&=nE%*A{hgE_`?McY{?zc7lmB%?zi^>z*B@>SKSM+P zk-z#ZgiilB|Br3|z1xq!yZv93Fm>^~`N4r<|1%wo51F($^8X7h{HmKj&ye5l(YH6@TU6KkE7`TKfH(|ETMC xRs5BM|ETM)XzBNB{-dtnRq