diff --git a/src/math/Matrices/Matrix.js b/src/math/Matrices/Matrix.js new file mode 100644 index 0000000000..58ee51fd26 --- /dev/null +++ b/src/math/Matrices/Matrix.js @@ -0,0 +1,1322 @@ +import { Vector } from "../p5.Vector"; +import { MatrixInterface } from "./MatrixInterface"; + +const isPerfectSquare = (arr) => { + const sqDimention = Math.sqrt(Array.from(arr).length); + if (sqDimention % 1 !== 0) { + throw new Error("Array length must be a perfect square."); + } + return true; +}; + +export let GLMAT_ARRAY_TYPE = Array; +export let isMatrixArray = (x) => Array.isArray(x); +if (typeof Float32Array !== "undefined") { + GLMAT_ARRAY_TYPE = Float32Array; + isMatrixArray = (x) => Array.isArray(x) || x instanceof Float32Array; +} +/** + * The `Matrix` class represents a mathematical matrix and provides various methods for matrix operations. + * + * This class extends the `MatrixInterface` and includes methods for creating, manipulating, and performing + * operations on matrices. It supports both 3x3 and 4x4 matrices, as well as general NxN matrices. + * + * @class + * @extends MatrixInterface + * + * @example + * // Creating a 3x3 matrix from an array + * const matrix = new Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + * + * // Creating a 4x4 identity matrix + * const identityMatrix = new Matrix(4); + * + * // Adding two matrices + * const matrix1 = new Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + * const matrix2 = new Matrix([9, 8, 7, 6, 5, 4, 3, 2, 1]); + * matrix1.add(matrix2); // matrix1 is now [10, 10, 10, 10, 10, 10, 10, 10, 10] + * + * // Setting an element in the matrix + * matrix.setElement(0, 10); // matrix is now [10, 2, 3, 4, 5, 6, 7, 8, 9] + * + * // Resetting the matrix to an identity matrix + * matrix.reset(); + * + * // Getting the diagonal elements of the matrix + * const diagonal = matrix.diagonal(); // [1, 1, 1] + * + * // Transposing the matrix + * matrix.transpose(); + * + * // Multiplying two matrices + * matrix1.mult(matrix2); + * + * // Inverting the matrix + * matrix.invert(); + * + * // Scaling the matrix + * matrix.scale(2, 2, 2); + * + * // Rotating the matrix around an axis + * matrix.rotate4x4(Math.PI / 4, 1, 0, 0); + * + * // Applying a perspective transformation + * matrix.perspective(Math.PI / 4, 1, 0.1, 100); + * + * // Applying an orthographic transformation + * matrix.ortho(-1, 1, -1, 1, 0.1, 100); + * + * // Multiplying a vector by the matrix + * const vector = new Vector(1, 2, 3); + * const result = matrix.multiplyPoint(vector); + */ +export class Matrix extends MatrixInterface { + matrix; + #sqDimention; + + constructor(...args) { + super(...args); + // This is default behavior when object + // instantiated using createMatrix() + if (isMatrixArray(args[0]) && isPerfectSquare(args[0])) { + const sqDimention = Math.sqrt(Array.from(args[0]).length); + this.#sqDimention = sqDimention; + this.matrix = Array.from(args[0]); + } else if (typeof args[0] === "number") { + this.#sqDimention = Number(args[0]); + this.matrix = this.#createIdentityMatrix(args[0]); + } + return this; + } + + /** + * Getter for a 3x3 matrix. + * + * This method returns the matrix if its dimensions are 3x3. + * If the matrix is not 3x3, it returns `undefined`. + * + * @returns {Array|undefined} The 3x3 matrix or `undefined` if the matrix is not 3x3. + */ + get mat3() { + if (this.#sqDimention === 3) { + return this.matrix; + } else { + return undefined; + } + } + + /** + * Getter for a 4x4 matrix. + * + * This method returns the matrix if its dimensions are 4x4. + * If the matrix is not 4x4, it returns `undefined`. + * + * @returns {Array|undefined} The 4x4 matrix or `undefined` if the matrix is not 4x4. + */ + get mat4() { + if (this.#sqDimention === 4) { + return this.matrix; + } else { + return undefined; + } + } + + /** + * Adds the corresponding elements of the given matrix to this matrix. + * + * @param {Matrix} matrix - The matrix to add to this matrix. It must have the same dimensions as this matrix. + * @returns {Matrix} The resulting matrix after addition. + * @throws {Error} If the matrices do not have the same dimensions. + * + * @example + * const matrix1 = new Matrix([1, 2, 3]); + * const matrix2 = new Matrix([4, 5, 6]); + * matrix1.add(matrix2); // matrix1 is now [5, 7, 9] + */ + add(matrix) { + if (this.matrix.length !== matrix.matrix.length) { + throw new Error("Matrices must be of the same dimension to add."); + } + for (let i = 0; i < this.matrix.length; i++) { + this.matrix[i] += matrix.matrix[i]; + } + return this; + } + + /** + * Sets the value of a specific element in the matrix. + * + * @param {number} index - The position in the matrix where the value should be set. + * Must be a non-negative integer less than the length of the matrix. + * @param {*} value - The new value to be assigned to the specified position in the matrix. + * @returns {Matrix} The current instance of the Matrix, allowing for method chaining. + * + * @example + * // Assuming matrix is an instance of Matrix with initial values [1, 2, 3] + * matrix.setElement(1, 10); + * // Now the matrix values are [1, 10, 3] + */ + setElement(index, value) { + if (index >= 0 && index < this.matrix.length) { + this.matrix[index] = value; + } + return this; + } + + /** + * Resets the current matrix to an identity matrix. + * + * This method replaces the current matrix with an identity matrix of the same dimensions. + * An identity matrix is a square matrix with ones on the main diagonal and zeros elsewhere. + * + * @returns {Matrix} The current instance of the Matrix class, allowing for method chaining. + */ + reset() { + this.matrix = this.#createIdentityMatrix(this.#sqDimention); + return this; + } + + /** + * Replace the entire contents of a NxN matrix. + * If providing an array or a p5.Matrix, the values will be copied without + * referencing the source object. + * Can also provide NxN numbers as individual arguments. + * + * @param {p5.Matrix|Float32Array|Number[]} [inMatrix] the input p5.Matrix or + * an Array of length 16 + * @chainable + */ + /** + * @param {Number[]} elements 16 numbers passed by value to avoid + * array copying. + * @chainable + */ + set(inMatrix) { + let refArray = Array.from([...arguments]); + if (inMatrix instanceof Matrix) { + refArray = inMatrix.matrix; + } else if (isMatrixArray(inMatrix)) { + refArray = inMatrix; + } + if (refArray.length !== this.matrix.length) { + p5._friendlyError( + `Expected same dimentions values but received different ${refArray.length}.`, + "p5.Matrix.set" + ); + return this; + } + this.matrix = [...refArray]; + return this; + } + + /** + * Gets a copy of the vector, returns a p5.Matrix object. + * + * @return {p5.Matrix} the copy of the p5.Matrix object + */ + get() { + return new Matrix(this.matrix); // TODO: Pass p5 + } + + /** + * return a copy of this matrix. + * If this matrix is 4x4, a 4x4 matrix with exactly the same entries will be + * generated. The same is true if this matrix is 3x3. + * + * @return {p5.Matrix} the result matrix + */ + copy() { + return new Matrix(this.matrix); + } + + /** + * Creates a copy of the current matrix instance. + * This method is useful when you need a duplicate of the matrix + * without modifying the original one. + * + * @returns {Matrix} A new matrix instance that is a copy of the current matrix. + */ + clone() { + return this.copy(); + } + /** + * Returns the diagonal elements of the matrix in the form of an array. + * A NxN matrix will return an array of length N. + * + * @return {Number[]} An array obtained by arranging the diagonal elements + * of the matrix in ascending order of index + */ + diagonal() { + const diagonal = []; + for (let i = 0; i < this.#sqDimention; i++) { + diagonal.push(this.matrix[i * (this.#sqDimention + 1)]); + } + return diagonal; + } + + /** + * This function is only for 3x3 matrices. + * A function that returns a row vector of a NxN matrix. + * + * @param {Number} columnIndex matrix column number + * @return {p5.Vector} + */ + row(columnIndex) { + const columnVector = []; + for (let i = 0; i < this.#sqDimention; i++) { + columnVector.push(this.matrix[i * this.#sqDimention + columnIndex]); + } + return new Vector(...columnVector); + } + + /** + * A function that returns a column vector of a NxN matrix. + * + * @param {Number} rowIndex matrix row number + * @return {p5.Vector} + */ + column(rowIndex) { + const rowVector = []; + for (let i = 0; i < this.#sqDimention; i++) { + rowVector.push(this.matrix[rowIndex * this.#sqDimention + i]); + } + return new Vector(...rowVector); + } + + + + + /** + * Transposes the given matrix `a` based on the square dimension of the matrix. + * + * This method rearranges the elements of the matrix such that the rows become columns + * and the columns become rows. It handles matrices of different dimensions (4x4, 3x3, NxN) + * by delegating to specific transpose methods for each case. + * + * @param {Array} a - The matrix to be transposed. It should be a 2D array where each sub-array represents a row. + * @returns {Array} - The transposed matrix. + */ + transpose(a) { + // TODO: Cristian: What does passing an argument to a transpose mean? + // In the codebase this is never done in any reference + // Actually transposse of a 4x4 is never done dierectly, + // I'm thinking it is incorrect, transpose3x3 is only used for inverseTranspose4x4 + if (this.#sqDimention === 4) { + return this.#transpose4x4(a); + } else if (this.#sqDimention === 3) { + return this.#transpose3x3(a); + } else { + return this.#transposeNxN(a); + } + } + + + /** + * Multiplies the current matrix with another matrix or matrix-like array. + * + * This method supports several types of input: + * - Another Matrix instance + * - A matrix-like array (must be a perfect square, e.g., 4x4 or 3x3) + * - Multiple arguments that form a perfect square matrix + * + * If the input is the same as the current matrix, a copy is made to avoid modifying the original matrix. + * + * @param {Matrix|Array|...number} multMatrix - The matrix or matrix-like array to multiply with. + * @returns {Matrix|undefined} The resulting matrix after multiplication, or undefined if the input is invalid. + * @chainable + */ + mult(multMatrix) { + let _src; + if (multMatrix === this || multMatrix === this.matrix) { + _src = this.copy().matrix; // only need to allocate in this rare case + } else if (multMatrix instanceof Matrix) { + _src = multMatrix.matrix; + } else if (isMatrixArray(multMatrix) && isPerfectSquare(multMatrix)) { + _src = multMatrix; + } else if (isPerfectSquare(arguments)) { + _src = Array.from(arguments); + } else { + return; // nothing to do. + } + if (this.#sqDimention === 4 && _src.length === 16) { + return this.#mult4x4(_src); + } else if (this.#sqDimention === 3 && _src.length === 9) { + return this.#mult3x3(_src); + } else { + return this.#multNxN(_src); + } + } + + /** + * This function is only for 3x3 matrices. + * Takes a vector and returns the vector resulting from multiplying to + * that vector by this matrix from left. + * + * @param {p5.Vector} multVector the vector to which this matrix applies + * @param {p5.Vector} [target] The vector to receive the result + * @return {p5.Vector} + */ + multiplyVec(multVector, target) { + if (target === undefined) { + target = multVector.copy(); + } + for (let i = 0; i < this.#sqDimention; i++) { + target.values[i] = this.row(i).dot(multVector); + } + return target; + } + + /** + * Inverts a given matrix. + * + * This method inverts a matrix based on its dimensions. Currently, it supports + * 3x3 and 4x4 matrices. If the matrix dimension is greater than 4, an error is thrown. + * + * @param {Array} a - The matrix to be inverted. It should be a 2D array representing the matrix. + * @returns {Array} - The inverted matrix. + * @throws {Error} - Throws an error if the matrix dimension is greater than 4. + */ + invert(a) { + if (this.#sqDimention === 4) { + return this.#invert4x4(a); + } else if (this.#sqDimention === 3) { + return this.#invert3x3(a); + } else { + throw new Error( + "Invert is not implemented for N>4 at the moment, we are working on it" + ); + } + } + + /** + * This function is only for 4x4 matrices. + * Creates a 3x3 matrix whose entries are the top left 3x3 part and returns it. + * + * @return {p5.Matrix} + */ + createSubMatrix3x3() { + if (this.#sqDimention === 4) { + const result = new Matrix(3); + result.mat3[0] = this.matrix[0]; + result.mat3[1] = this.matrix[1]; + result.mat3[2] = this.matrix[2]; + result.mat3[3] = this.matrix[4]; + result.mat3[4] = this.matrix[5]; + result.mat3[5] = this.matrix[6]; + result.mat3[6] = this.matrix[8]; + result.mat3[7] = this.matrix[9]; + result.mat3[8] = this.matrix[10]; + return result; + } else { + throw new Error("Matrix dimension must be 4 to create a 3x3 submatrix."); + } + } + + /** + * Converts a 4×4 matrix to its 3×3 inverse transform + * commonly used in MVMatrix to NMatrix conversions. + * @param {p5.Matrix} mat4 the matrix to be based on to invert + * @chainable + * @todo finish implementation + */ + inverseTranspose4x4({ mat4 }) { + if (this.#sqDimention !== 3) { + p5._friendlyError("sorry, this function only works with mat3"); + } else { + //convert mat4 -> mat3 + this.matrix[0] = mat4[0]; + this.matrix[1] = mat4[1]; + this.matrix[2] = mat4[2]; + this.matrix[3] = mat4[4]; + this.matrix[4] = mat4[5]; + this.matrix[5] = mat4[6]; + this.matrix[6] = mat4[8]; + this.matrix[7] = mat4[9]; + this.matrix[8] = mat4[10]; + } + + const inverse = this.invert(); + // check inverse succeeded + if (inverse) { + inverse.transpose(this.matrix); + } else { + // in case of singularity, just zero the matrix + for (let i = 0; i < 9; i++) { + this.matrix[i] = 0; + } + } + return this; + } + + /** + * Applies a transformation matrix to the current matrix. + * + * This method multiplies the current matrix by another matrix, which can be provided + * in several forms: another Matrix instance, an array representing a matrix, or as + * individual arguments representing the elements of a 4x4 matrix. + * + * @param {Matrix|Array|number} multMatrix - The matrix to multiply with. This can be: + * - An instance of the Matrix class. + * - An array of 16 numbers representing a 4x4 matrix. + * - 16 individual numbers representing the elements of a 4x4 matrix. + * @returns {Matrix} The current matrix after applying the transformation. + * + * @example + * // Assuming `matrix` is an instance of Matrix + * const anotherMatrix = new Matrix(); + * matrix.apply(anotherMatrix); + * + * @example + * // Applying a transformation using an array + * const matrixArray = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + * matrix.apply(matrixArray); + * + * @example + * // Applying a transformation using individual arguments + * matrix.apply(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + */ + apply(multMatrix) { + let _src; + + if (multMatrix === this || multMatrix === this.matrix) { + _src = this.copy().matrix; // only need to allocate in this rare case + } else if (multMatrix instanceof Matrix) { + _src = multMatrix.matrix; + } else if (isMatrixArray(multMatrix)) { + _src = multMatrix; + } else if (arguments.length === 16) { + _src = arguments; + } else { + return; // nothing to do. + } + + const mat4 = this.matrix; + + // each row is used for the multiplier + const m0 = mat4[0]; + const m4 = mat4[4]; + const m8 = mat4[8]; + const m12 = mat4[12]; + mat4[0] = _src[0] * m0 + _src[1] * m4 + _src[2] * m8 + _src[3] * m12; + mat4[4] = _src[4] * m0 + _src[5] * m4 + _src[6] * m8 + _src[7] * m12; + mat4[8] = _src[8] * m0 + _src[9] * m4 + _src[10] * m8 + _src[11] * m12; + mat4[12] = _src[12] * m0 + _src[13] * m4 + _src[14] * m8 + _src[15] * m12; + + const m1 = mat4[1]; + const m5 = mat4[5]; + const m9 = mat4[9]; + const m13 = mat4[13]; + mat4[1] = _src[0] * m1 + _src[1] * m5 + _src[2] * m9 + _src[3] * m13; + mat4[5] = _src[4] * m1 + _src[5] * m5 + _src[6] * m9 + _src[7] * m13; + mat4[9] = _src[8] * m1 + _src[9] * m5 + _src[10] * m9 + _src[11] * m13; + mat4[13] = _src[12] * m1 + _src[13] * m5 + _src[14] * m9 + _src[15] * m13; + + const m2 = mat4[2]; + const m6 = mat4[6]; + const m10 = mat4[10]; + const m14 = mat4[14]; + mat4[2] = _src[0] * m2 + _src[1] * m6 + _src[2] * m10 + _src[3] * m14; + mat4[6] = _src[4] * m2 + _src[5] * m6 + _src[6] * m10 + _src[7] * m14; + mat4[10] = _src[8] * m2 + _src[9] * m6 + _src[10] * m10 + _src[11] * m14; + mat4[14] = _src[12] * m2 + _src[13] * m6 + _src[14] * m10 + _src[15] * m14; + + const m3 = mat4[3]; + const m7 = mat4[7]; + const m11 = mat4[11]; + const m15 = mat4[15]; + mat4[3] = _src[0] * m3 + _src[1] * m7 + _src[2] * m11 + _src[3] * m15; + mat4[7] = _src[4] * m3 + _src[5] * m7 + _src[6] * m11 + _src[7] * m15; + mat4[11] = _src[8] * m3 + _src[9] * m7 + _src[10] * m11 + _src[11] * m15; + mat4[15] = _src[12] * m3 + _src[13] * m7 + _src[14] * m11 + _src[15] * m15; + + return this; + } + + /** + * scales a p5.Matrix by scalars or a vector + * @param {p5.Vector|Float32Array|Number[]} s vector to scale by + * @chainable + */ + scale(x, y, z) { + if (x instanceof Vector) { + // x is a vector, extract the components from it. + y = x.y; + z = x.z; + x = x.x; // must be last + } else if (x instanceof Array) { + // x is an array, extract the components from it. + y = x[1]; + z = x[2]; + x = x[0]; // must be last + } + + this.matrix[0] *= x; + this.matrix[1] *= x; + this.matrix[2] *= x; + this.matrix[3] *= x; + this.matrix[4] *= y; + this.matrix[5] *= y; + this.matrix[6] *= y; + this.matrix[7] *= y; + this.matrix[8] *= z; + this.matrix[9] *= z; + this.matrix[10] *= z; + this.matrix[11] *= z; + + return this; + } + + /** + * rotate our Matrix around an axis by the given angle. + * @param {Number} a The angle of rotation in radians + * @param {p5.Vector|Number[]} axis the axis(es) to rotate around + * @chainable + * inspired by Toji's gl-matrix lib, mat4 rotation + */ + rotate4x4(a, x, y, z) { + if (x instanceof Vector) { + // x is a vector, extract the components from it. + y = x.y; + z = x.z; + x = x.x; //must be last + } else if (x instanceof Array) { + // x is an array, extract the components from it. + y = x[1]; + z = x[2]; + x = x[0]; //must be last + } + + const len = Math.sqrt(x * x + y * y + z * z); + x *= 1 / len; + y *= 1 / len; + z *= 1 / len; + + const a00 = this.matrix[0]; + const a01 = this.matrix[1]; + const a02 = this.matrix[2]; + const a03 = this.matrix[3]; + const a10 = this.matrix[4]; + const a11 = this.matrix[5]; + const a12 = this.matrix[6]; + const a13 = this.matrix[7]; + const a20 = this.matrix[8]; + const a21 = this.matrix[9]; + const a22 = this.matrix[10]; + const a23 = this.matrix[11]; + + //sin,cos, and tan of respective angle + const sA = Math.sin(a); + const cA = Math.cos(a); + const tA = 1 - cA; + // Construct the elements of the rotation matrix + const b00 = x * x * tA + cA; + const b01 = y * x * tA + z * sA; + const b02 = z * x * tA - y * sA; + const b10 = x * y * tA - z * sA; + const b11 = y * y * tA + cA; + const b12 = z * y * tA + x * sA; + const b20 = x * z * tA + y * sA; + const b21 = y * z * tA - x * sA; + const b22 = z * z * tA + cA; + + // rotation-specific matrix multiplication + this.matrix[0] = a00 * b00 + a10 * b01 + a20 * b02; + this.matrix[1] = a01 * b00 + a11 * b01 + a21 * b02; + this.matrix[2] = a02 * b00 + a12 * b01 + a22 * b02; + this.matrix[3] = a03 * b00 + a13 * b01 + a23 * b02; + this.matrix[4] = a00 * b10 + a10 * b11 + a20 * b12; + this.matrix[5] = a01 * b10 + a11 * b11 + a21 * b12; + this.matrix[6] = a02 * b10 + a12 * b11 + a22 * b12; + this.matrix[7] = a03 * b10 + a13 * b11 + a23 * b12; + this.matrix[8] = a00 * b20 + a10 * b21 + a20 * b22; + this.matrix[9] = a01 * b20 + a11 * b21 + a21 * b22; + this.matrix[10] = a02 * b20 + a12 * b21 + a22 * b22; + this.matrix[11] = a03 * b20 + a13 * b21 + a23 * b22; + + return this; + } + + /** + * @todo finish implementing this method! + * translates + * @param {Number[]} v vector to translate by + * @chainable + */ + translate(v) { + const x = v[0], + y = v[1], + z = v[2] || 0; + this.matrix[12] += + this.matrix[0] * x + this.matrix[4] * y + this.matrix[8] * z; + this.matrix[13] += + this.matrix[1] * x + this.matrix[5] * y + this.matrix[9] * z; + this.matrix[14] += + this.matrix[2] * x + this.matrix[6] * y + this.matrix[10] * z; + this.matrix[15] += + this.matrix[3] * x + this.matrix[7] * y + this.matrix[11] * z; + } + + /** + * Rotates the matrix around the X-axis by a given angle. + * + * This method modifies the current matrix to apply a rotation transformation + * around the X-axis. The rotation angle is specified in radians. + * + * @param {number} a - The angle in radians to rotate the matrix by. + */ + rotateX(a) { + this.rotate4x4(a, 1, 0, 0); + } + + /** + * Rotates the matrix around the Y-axis by a given angle. + * + * This method modifies the current matrix to apply a rotation transformation + * around the Y-axis. The rotation is performed in 3D space, and the angle + * is specified in radians. + * + * @param {number} a - The angle in radians to rotate the matrix by. Positive + * values rotate the matrix counterclockwise, and negative values rotate it + * clockwise. + */ + rotateY(a) { + this.rotate4x4(a, 0, 1, 0); + } + + /** + * Rotates the matrix around the Z-axis by a given angle. + * + * @param {number} a - The angle in radians to rotate the matrix by. + * + * This method modifies the current matrix to apply a rotation transformation + * around the Z-axis. The rotation is performed in a 4x4 matrix context, which + * is commonly used in 3D graphics to handle transformations. + * + * Example usage: + * ``` + * const matrix = new Matrix(); + * matrix.rotateZ(Math.PI / 4); // Rotates the matrix 45 degrees around the Z-axis + * ``` + */ + rotateZ(a) { + this.rotate4x4(a, 0, 0, 1); + } + + /** + * sets the perspective matrix + * @param {Number} fovy [description] + * @param {Number} aspect [description] + * @param {Number} near near clipping plane + * @param {Number} far far clipping plane + * @chainable + */ + perspective(fovy, aspect, near, far) { + const f = 1.0 / Math.tan(fovy / 2), + nf = 1 / (near - far); + + this.matrix[0] = f / aspect; + this.matrix[1] = 0; + this.matrix[2] = 0; + this.matrix[3] = 0; + this.matrix[4] = 0; + this.matrix[5] = f; + this.matrix[6] = 0; + this.matrix[7] = 0; + this.matrix[8] = 0; + this.matrix[9] = 0; + this.matrix[10] = (far + near) * nf; + this.matrix[11] = -1; + this.matrix[12] = 0; + this.matrix[13] = 0; + this.matrix[14] = 2 * far * near * nf; + this.matrix[15] = 0; + + return this; + } + + /** + * sets the ortho matrix + * @param {Number} left [description] + * @param {Number} right [description] + * @param {Number} bottom [description] + * @param {Number} top [description] + * @param {Number} near near clipping plane + * @param {Number} far far clipping plane + * @chainable + */ + ortho(left, right, bottom, top, near, far) { + const lr = 1 / (left - right), + bt = 1 / (bottom - top), + nf = 1 / (near - far); + this.matrix[0] = -2 * lr; + this.matrix[1] = 0; + this.matrix[2] = 0; + this.matrix[3] = 0; + this.matrix[4] = 0; + this.matrix[5] = -2 * bt; + this.matrix[6] = 0; + this.matrix[7] = 0; + this.matrix[8] = 0; + this.matrix[9] = 0; + this.matrix[10] = 2 * nf; + this.matrix[11] = 0; + this.matrix[12] = (left + right) * lr; + this.matrix[13] = (top + bottom) * bt; + this.matrix[14] = (far + near) * nf; + this.matrix[15] = 1; + + return this; + } + + /** + * apply a matrix to a vector with x,y,z,w components + * get the results in the form of an array + * @param {Number} + * @return {Number[]} + */ + multiplyVec4(x, y, z, w) { + const result = new Array(4); + const m = this.matrix; + + result[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w; + result[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w; + result[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w; + result[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w; + + return result; + } + + /** + * Applies a matrix to a vector. + * The fourth component is set to 1. + * Returns a vector consisting of the first + * through third components of the result. + * + * @param {p5.Vector} + * @return {p5.Vector} + */ + multiplyPoint({ x, y, z }) { + const array = this.multiplyVec4(x, y, z, 1); + return new Vector(array[0], array[1], array[2]); + } + + /** + * Applies a matrix to a vector. + * The fourth component is set to 1. + * Returns the result of dividing the 1st to 3rd components + * of the result by the 4th component as a vector. + * + * @param {p5.Vector} + * @return {p5.Vector} + */ + multiplyAndNormalizePoint({ x, y, z }) { + const array = this.multiplyVec4(x, y, z, 1); + array[0] /= array[3]; + array[1] /= array[3]; + array[2] /= array[3]; + return new Vector(array[0], array[1], array[2]); + } + + /** + * Applies a matrix to a vector. + * The fourth component is set to 0. + * Returns a vector consisting of the first + * through third components of the result. + * + * @param {p5.Vector} + * @return {p5.Vector} + */ + multiplyDirection({ x, y, z }) { + const array = this.multiplyVec4(x, y, z, 0); + return new Vector(array[0], array[1], array[2]); + } + + /** + * This function is only for 3x3 matrices. + * Takes a vector and returns the vector resulting from multiplying to + * that vector by this matrix from left. + * + * @param {p5.Vector} multVector the vector to which this matrix applies + * @param {p5.Vector} [target] The vector to receive the result + * @return {p5.Vector} + */ + /** + * This function is only for 3x3 matrices. + * Takes a vector and returns the vector resulting from multiplying to + * that vector by this matrix from left. + * + * @param {p5.Vector} multVector the vector to which this matrix applies + * @param {p5.Vector} [target] The vector to receive the result + * @return {p5.Vector} + */ + multiplyVec3(multVector, target) { + if (target === undefined) { + target = multVector.copy(); + } + target.x = this.row(0).dot(multVector); + target.y = this.row(1).dot(multVector); + target.z = this.row(2).dot(multVector); + return target; + } + + // ==================== + // PRIVATE + #createIdentityMatrix(dimension) { + // This it to prevent loops in the most common 3x3 and 4x4 cases + // TODO: check performance if it actually helps + if (dimension === 3) + return new GLMAT_ARRAY_TYPE([1, 0, 0, 0, 1, 0, 0, 0, 1]); + if (dimension === 4) + return new GLMAT_ARRAY_TYPE([ + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, + ]); + const identityMatrix = new GLMAT_ARRAY_TYPE(dimension * dimension).fill(0); + for (let i = 0; i < dimension; i++) { + identityMatrix[i * dimension + i] = 1; + } + return identityMatrix; + } + + /** + * Multiplies the current 4x4 matrix with another 4x4 matrix. + * This method updates the current matrix with the result of the multiplication. + * + * @private + * @param {number[]} _src - A 16-element array representing the 4x4 matrix to multiply with. + * + * @returns {this} The current instance with the updated matrix. + * + * @example + * // Assuming `matrix` is an instance of the Matrix class + * const srcMatrix = [ + * 1, 0, 0, 0, + * 0, 1, 0, 0, + * 0, 0, 1, 0, + * 0, 0, 0, 1 + * ]; + * matrix.#mult4x4(srcMatrix); + */ + #mult4x4(_src) { + // each row is used for the multiplier + let b0 = this.matrix[0], + b1 = this.matrix[1], + b2 = this.matrix[2], + b3 = this.matrix[3]; + this.matrix[0] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12]; + this.matrix[1] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13]; + this.matrix[2] = + b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14]; + this.matrix[3] = + b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15]; + + b0 = this.matrix[4]; + b1 = this.matrix[5]; + b2 = this.matrix[6]; + b3 = this.matrix[7]; + this.matrix[4] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12]; + this.matrix[5] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13]; + this.matrix[6] = + b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14]; + this.matrix[7] = + b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15]; + + b0 = this.matrix[8]; + b1 = this.matrix[9]; + b2 = this.matrix[10]; + b3 = this.matrix[11]; + this.matrix[8] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12]; + this.matrix[9] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13]; + this.matrix[10] = + b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14]; + this.matrix[11] = + b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15]; + + b0 = this.matrix[12]; + b1 = this.matrix[13]; + b2 = this.matrix[14]; + b3 = this.matrix[15]; + this.matrix[12] = + b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12]; + this.matrix[13] = + b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13]; + this.matrix[14] = + b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14]; + this.matrix[15] = + b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15]; + + return this; + } + + /** + * @param {p5.Matrix|Float32Array|Number[]} multMatrix The matrix + * we want to multiply by + * @chainable + */ + #multNxN(multMatrix) { + if (multMatrix.length !== this.matrix.length) { + throw new Error("Matrices must be of the same dimension to multiply."); + } + const result = new GLMAT_ARRAY_TYPE(this.matrix.length).fill(0); + for (let i = 0; i < this.#sqDimention; i++) { + for (let j = 0; j < this.#sqDimention; j++) { + for (let k = 0; k < this.#sqDimention; k++) { + result[i * this.#sqDimention + j] += + this.matrix[i * this.#sqDimention + k] * + multMatrix[k * this.#sqDimention + j]; + } + } + } + this.matrix = result; + return this; + } + + /** + * This function is only for 3x3 matrices. + * multiply two mat3s. It is an operation to multiply the 3x3 matrix of + * the argument from the right. Arguments can be a 3x3 p5.Matrix, + * a Float32Array of length 9, or a javascript array of length 9. + * In addition, it can also be done by enumerating 9 numbers. + * + * @param {p5.Matrix|Float32Array|Number[]} multMatrix The matrix + * we want to multiply by + * @chainable + */ + #mult3x3(_src) { + // each row is used for the multiplier + let b0 = this.mat3[0]; + let b1 = this.mat3[1]; + let b2 = this.mat3[2]; + this.mat3[0] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6]; + this.mat3[1] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7]; + this.mat3[2] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8]; + + b0 = this.mat3[3]; + b1 = this.mat3[4]; + b2 = this.mat3[5]; + this.mat3[3] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6]; + this.mat3[4] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7]; + this.mat3[5] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8]; + + b0 = this.mat3[6]; + b1 = this.mat3[7]; + b2 = this.mat3[8]; + this.mat3[6] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6]; + this.mat3[7] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7]; + this.mat3[8] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8]; + + return this; + } + + /** + * Transposes a square matrix in place. + * This method swaps the rows and columns of the matrix, effectively flipping it over its diagonal. + * + * @private + * @returns {Matrix} The current instance of the Matrix, with the transposed values. + */ + #transposeNxN() { + const n = this.#sqDimention; + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + this.matrix[i * n + j] = this.matrix[j * n + i]; + } + } + return this; + } + + /** + * transpose according to a given matrix + * @param {p5.Matrix|Float32Array|Number[]} a the matrix to be + * based on to transpose + * @chainable + */ + #transpose4x4(a) { + console.log("====> 4x4"); + let a01, a02, a03, a12, a13, a23; + if (a instanceof Matrix) { + a01 = a.matrix[1]; + a02 = a.matrix[2]; + a03 = a.matrix[3]; + a12 = a.matrix[6]; + a13 = a.matrix[7]; + a23 = a.matrix[11]; + + this.matrix[0] = a.matrix[0]; + this.matrix[1] = a.matrix[4]; + this.matrix[2] = a.matrix[8]; + this.matrix[3] = a.matrix[12]; + this.matrix[4] = a01; + this.matrix[5] = a.matrix[5]; + this.matrix[6] = a.matrix[9]; + this.matrix[7] = a.matrix[13]; + this.matrix[8] = a02; + this.matrix[9] = a12; + this.matrix[10] = a.matrix[10]; + this.matrix[11] = a.matrix[14]; + this.matrix[12] = a03; + this.matrix[13] = a13; + this.matrix[14] = a23; + this.matrix[15] = a.matrix[15]; + } else if (isMatrixArray(a)) { + a01 = a[1]; + a02 = a[2]; + a03 = a[3]; + a12 = a[6]; + a13 = a[7]; + a23 = a[11]; + + this.matrix[0] = a[0]; + this.matrix[1] = a[4]; + this.matrix[2] = a[8]; + this.matrix[3] = a[12]; + this.matrix[4] = a01; + this.matrix[5] = a[5]; + this.matrix[6] = a[9]; + this.matrix[7] = a[13]; + this.matrix[8] = a02; + this.matrix[9] = a12; + this.matrix[10] = a[10]; + this.matrix[11] = a[14]; + this.matrix[12] = a03; + this.matrix[13] = a13; + this.matrix[14] = a23; + this.matrix[15] = a[15]; + } + return this; + } + + /** + * This function is only for 3x3 matrices. + * transposes a 3×3 p5.Matrix by a mat3 + * If there is an array of arguments, the matrix obtained by transposing + * the 3x3 matrix generated based on that array is set. + * If no arguments, it transposes itself and returns it. + * + * @param {Number[]} mat3 1-dimensional array + * @chainable + */ + #transpose3x3(mat3) { + if (mat3 === undefined) { + mat3 = this.mat3; + } + const a01 = mat3[1]; + const a02 = mat3[2]; + const a12 = mat3[5]; + this.mat3[0] = mat3[0]; + this.mat3[1] = mat3[3]; + this.mat3[2] = mat3[6]; + this.mat3[3] = a01; + this.mat3[4] = mat3[4]; + this.mat3[5] = mat3[7]; + this.mat3[6] = a02; + this.mat3[7] = a12; + this.mat3[8] = mat3[8]; + + return this; + } + + /** + * Only 4x4 becasuse determinant is only 4x4 currently + * invert matrix according to a give matrix + * @param {p5.Matrix|Float32Array|Number[]} a the matrix to be + * based on to invert + * @chainable + */ + #invert4x4(a) { + let a00, a01, a02, a03, a10, a11, a12, a13; + let a20, a21, a22, a23, a30, a31, a32, a33; + if (a instanceof Matrix) { + a00 = a.matrix[0]; + a01 = a.matrix[1]; + a02 = a.matrix[2]; + a03 = a.matrix[3]; + a10 = a.matrix[4]; + a11 = a.matrix[5]; + a12 = a.matrix[6]; + a13 = a.matrix[7]; + a20 = a.matrix[8]; + a21 = a.matrix[9]; + a22 = a.matrix[10]; + a23 = a.matrix[11]; + a30 = a.matrix[12]; + a31 = a.matrix[13]; + a32 = a.matrix[14]; + a33 = a.matrix[15]; + } else if (isMatrixArray(a)) { + 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]; + a30 = a[12]; + a31 = a[13]; + a32 = a[14]; + a33 = a[15]; + } + const b00 = a00 * a11 - a01 * a10; + const b01 = a00 * a12 - a02 * a10; + const b02 = a00 * a13 - a03 * a10; + const b03 = a01 * a12 - a02 * a11; + const b04 = a01 * a13 - a03 * a11; + const b05 = a02 * a13 - a03 * a12; + const b06 = a20 * a31 - a21 * a30; + const b07 = a20 * a32 - a22 * a30; + const b08 = a20 * a33 - a23 * a30; + const b09 = a21 * a32 - a22 * a31; + const b10 = a21 * a33 - a23 * a31; + const b11 = a22 * a33 - a23 * a32; + + // Calculate the determinant + let det = + b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + if (!det) { + return null; + } + det = 1.0 / det; + + this.matrix[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + this.matrix[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + this.matrix[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + this.matrix[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; + this.matrix[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + this.matrix[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + this.matrix[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + this.matrix[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; + this.matrix[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + this.matrix[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + this.matrix[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + this.matrix[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; + this.matrix[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; + this.matrix[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; + this.matrix[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; + this.matrix[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; + + return this; + } + + /** + * Inverts a 3×3 matrix + * @chainable + */ + #invert3x3() { + const a00 = this.mat3[0]; + const a01 = this.mat3[1]; + const a02 = this.mat3[2]; + const a10 = this.mat3[3]; + const a11 = this.mat3[4]; + const a12 = this.mat3[5]; + const a20 = this.mat3[6]; + const a21 = this.mat3[7]; + const a22 = this.mat3[8]; + const b01 = a22 * a11 - a12 * a21; + const b11 = -a22 * a10 + a12 * a20; + const b21 = a21 * a10 - a11 * a20; + + // Calculate the determinant + let det = a00 * b01 + a01 * b11 + a02 * b21; + if (!det) { + return null; + } + det = 1.0 / det; + this.mat3[0] = b01 * det; + this.mat3[1] = (-a22 * a01 + a02 * a21) * det; + this.mat3[2] = (a12 * a01 - a02 * a11) * det; + this.mat3[3] = b11 * det; + this.mat3[4] = (a22 * a00 - a02 * a20) * det; + this.mat3[5] = (-a12 * a00 + a02 * a10) * det; + this.mat3[6] = b21 * det; + this.mat3[7] = (-a21 * a00 + a01 * a20) * det; + this.mat3[8] = (a11 * a00 - a01 * a10) * det; + return this; + } + + /** + * inspired by Toji's mat4 determinant + * @return {Number} Determinant of our 4×4 matrix + */ + #determinant4x4() { + if (this.#sqDimention !== 4) { + throw new Error( + "Determinant is only implemented for 4x4 matrices. We are working on it." + ); + } + + const d00 = + this.matrix[0] * this.matrix[5] - this.matrix[1] * this.matrix[4], + d01 = this.matrix[0] * this.matrix[6] - this.matrix[2] * this.matrix[4], + d02 = this.matrix[0] * this.matrix[7] - this.matrix[3] * this.matrix[4], + d03 = this.matrix[1] * this.matrix[6] - this.matrix[2] * this.matrix[5], + d04 = this.matrix[1] * this.matrix[7] - this.matrix[3] * this.matrix[5], + d05 = this.matrix[2] * this.matrix[7] - this.matrix[3] * this.matrix[6], + d06 = this.matrix[8] * this.matrix[13] - this.matrix[9] * this.matrix[12], + d07 = + this.matrix[8] * this.matrix[14] - this.matrix[10] * this.matrix[12], + d08 = + this.matrix[8] * this.matrix[15] - this.matrix[11] * this.matrix[12], + d09 = + this.matrix[9] * this.matrix[14] - this.matrix[10] * this.matrix[13], + d10 = + this.matrix[9] * this.matrix[15] - this.matrix[11] * this.matrix[13], + d11 = + this.matrix[10] * this.matrix[15] - this.matrix[11] * this.matrix[14]; + + // Calculate the determinant + return ( + d00 * d11 - d01 * d10 + d02 * d09 + d03 * d08 - d04 * d07 + d05 * d06 + ); + } + + /** + * PRIVATE + */ + // matrix methods adapted from: + // https://developer.mozilla.org/en-US/docs/Web/WebGL/ + // gluPerspective + // + // function _makePerspective(fovy, aspect, znear, zfar){ + // const ymax = znear * Math.tan(fovy * Math.PI / 360.0); + // const ymin = -ymax; + // const xmin = ymin * aspect; + // const xmax = ymax * aspect; + // return _makeFrustum(xmin, xmax, ymin, ymax, znear, zfar); + // } + + //// + //// glFrustum + //// + //function _makeFrustum(left, right, bottom, top, znear, zfar){ + // const X = 2*znear/(right-left); + // const Y = 2*znear/(top-bottom); + // const A = (right+left)/(right-left); + // const B = (top+bottom)/(top-bottom); + // const C = -(zfar+znear)/(zfar-znear); + // const D = -2*zfar*znear/(zfar-znear); + // const frustrumMatrix =[ + // X, 0, A, 0, + // 0, Y, B, 0, + // 0, 0, C, D, + // 0, 0, -1, 0 + //]; + //return frustrumMatrix; + // } + + // function _setMVPMatrices(){ + ////an identity matrix + ////@TODO use the p5.Matrix class to abstract away our MV matrices and + ///other math + //const _mvMatrix = + //[ + // 1.0,0.0,0.0,0.0, + // 0.0,1.0,0.0,0.0, + // 0.0,0.0,1.0,0.0, + // 0.0,0.0,0.0,1.0 + //]; +} diff --git a/src/math/Matrices/MatrixInterface.js b/src/math/Matrices/MatrixInterface.js new file mode 100644 index 0000000000..4ab4a0b63a --- /dev/null +++ b/src/math/Matrices/MatrixInterface.js @@ -0,0 +1,53 @@ +export let GLMAT_ARRAY_TYPE = Array; +export let isMatrixArray = (x) => Array.isArray(x); +if (typeof Float32Array !== "undefined") { + GLMAT_ARRAY_TYPE = Float32Array; + isMatrixArray = (x) => Array.isArray(x) || x instanceof Float32Array; +} +export class MatrixInterface { + // Private field to store the matrix + #matrix = null; + constructor(...args) { + if (this.constructor === MatrixInterface) { + throw new Error("Class is of abstract type and can't be instantiated"); + } + const methods = [ + "add", + "setElement", + "reset", + "set", + "get", + "copy", + "clone", + "diagonal", + "row", + "column", + "transpose", + "mult", + "multiplyVec", + "invert", + "createSubMatrix3x3", + "inverseTranspose4x4", + "apply", + "scale", + "rotate4x4", + "translate", + "rotateX", + "rotateY", + "rotateZ", + "perspective", + "ortho", + "multiplyVec4", + "multiplyPoint", + "multiplyAndNormalizePoint", + "multiplyDirection", + "multiplyVec3", + ]; + + methods.forEach((method) => { + if (this[method] === undefined) { + throw new Error(`${method}() method must be implemented`); + } + }); + } +} diff --git a/src/math/Matrices/MatrixNumjs.js b/src/math/Matrices/MatrixNumjs.js new file mode 100644 index 0000000000..75d731dbee --- /dev/null +++ b/src/math/Matrices/MatrixNumjs.js @@ -0,0 +1,966 @@ +import nj from "@d4c/numjs/build/module/numjs.min.js"; +import { Vector } from "../p5.Vector"; +import { MatrixInterface } from "./MatrixInterface"; + +/** + * @requires constants + * @todo see methods below needing further implementation. + * future consideration: implement SIMD optimizations + * when browser compatibility becomes available + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/ + * Reference/Global_Objects/SIMD + */ + +let GLMAT_ARRAY_TYPE = Array; +let isMatrixArray = (x) => Array.isArray(x); +if (typeof Float32Array !== "undefined") { + GLMAT_ARRAY_TYPE = Float32Array; + isMatrixArray = (x) => Array.isArray(x) || x instanceof Float32Array; +} + +/** + * A class to describe a matrix, which can be either a 3×3 or 4×4 matrix, + * for various matrix manipulations in the p5.js webgl renderer. + * This class provides methods for common matrix operations such as + * multiplication, inversion, transposition, and transformation. + * It supports both 3×3 matrices, typically used for normal transformations, + * and 4×4 matrices, commonly used for model, view, and projection transformations. + * + * @class MatrixNumjs + * @private + * @param {Array} [mat4] column-major array literal of our 4×4 matrix + * @param {Array} [mat3] column-major array literal of our 3×3 matrix + * @example + *
+ * + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create Vector objects. + * let p1 = createMatrix(1,1,1,1,1,1,1,1,1); + * console.log(p1); + * } + * + *
+ */ +// const matrixEngine = "numjs"; +export class MatrixNumjs extends MatrixInterface{ + constructor(...args) { + // This is default behavior when object + super(...args) + + if (args[0] === 3) { + this._mat3 = Array.isArray(args[1]) ? nj.array(args[1]) : nj.identity(3); + } else { + this._mat4 = Array.isArray(args[0]) ? nj.array(args[0]) : nj.identity(4); + } + return this; + } + + get mat3() { + return this._mat3?.flatten().tolist(); + } + get mat4() { + return this._mat4?.flatten().tolist(); + } + /** + * Resets the matrix to the identity matrix. + * + * If the matrix is a 3x3 matrix (`mat3`), it sets the matrix to: + * [1, 0, 0, + * 0, 1, 0, + * 0, 0, 1] + * + * If the matrix is a 4x4 matrix (`mat4`), it sets the matrix to: + * [1, 0, 0, 0, + * 0, 1, 0, 0, + * 0, 0, 1, 0, + * 0, 0, 0, 1] + * + * @returns {this} The current instance for chaining. + */ + reset() { + if (this._mat3) { + this._mat3 = nj.identity(3).flatten(); + } else if (this._mat4) { + this._mat4 = nj.identity(4).flatten(); + } + return this; + } + + /** + * Replace the entire contents of a 4x4 matrix. + * If providing an array or a MatrixNumjs, the values will be copied without + * referencing the source object. + * Can also provide 16 numbers as individual arguments. + * + * @param {MatrixNumjs|Float32Array|Number[]} [inMatrix] the input MatrixNumjs or + * an Array of length 16 + * @chainable + */ + /** + * @param {Number[]} elements 16 numbers passed by value to avoid + * array copying. + * @chainable + */ + set(inMatrix) { + let refArray = [...arguments]; + if (inMatrix instanceof MatrixNumjs) { + refArray = inMatrix.mat4; + } else if (isMatrixArray(inMatrix)) { + refArray = inMatrix; + } + if (refArray.length !== 16) { + // p5._friendlyError( + // `Expected 16 values but received ${refArray.length}.`, + // "MatrixNumjs.set" + // ); + return this; + } + this._mat4 = nj.array(refArray); + return this; + } + + setElement(index, value) { + if (this._mat3) { + this._mat3.set(index, value); + } + return this; + } + + + /** + * Gets a copy of the vector, returns a MatrixNumjs object. + * + * @return {MatrixNumjs} the copy of the MatrixNumjs object + */ + get() { + let temp = new MatrixNumjs(this.mat4); + return new MatrixNumjs(this.mat4); + } + + /** + * return a copy of this matrix. + * If this matrix is 4x4, a 4x4 matrix with exactly the same entries will be + * generated. The same is true if this matrix is 3x3. + * + * @return {MatrixNumjs} the result matrix + */ + copy() { + if (this._mat3 !== undefined) { + const copied3x3 = new MatrixNumjs(3, this._mat3.tolist()); + copied3x3._mat3 = copied3x3._mat3.flatten(); + return copied3x3; + } + const copied = new MatrixNumjs(this._mat4.tolist()); + // copied.set(this); + copied._mat4 = copied._mat4.flatten(); + return copied; + } + + /** + * Creates a copy of the current matrix. + * + * @returns {MatrixNumjs} A new matrix that is a copy of the current matrix. + */ + clone() { + return this.copy(); + } + + /** + * return an identity matrix + * @return {MatrixNumjs} the result matrix + */ + static identity(pInst) { + return new MatrixNumjs(pInst); + } + + /** + * transpose according to a given matrix + * @param {MatrixNumjs|Float32Array|Number[]} a the matrix to be + * based on to transpose + * @chainable + */ + transpose(a) { + if (a instanceof MatrixNumjs) { + if (a._mat3) { + this._mat3 = nj.array(a.mat3).reshape(3, 3).transpose().flatten(); + } else if (a._mat4) { + this._mat4 = nj.array(a.mat4).reshape(4, 4).transpose().flatten(); + } + } else if (isMatrixArray(a)) { + if (a.length === 9) { + let temp3 = new MatrixNumjs(3, a); + this._mat3 = nj.array(temp3.mat3).reshape(3, 3).transpose().flatten(); + } else if (a.length === 16) { + let temp4 = new MatrixNumjs(a); + this._mat4 = nj.array(temp4.mat4).reshape(4, 4).transpose().flatten(); + } + } + return this; + } + + /** + * invert matrix according to a give matrix + * @param {MatrixNumjs|Float32Array|Number[]} a the matrix to be + * based on to invert + * @chainable + */ + invert(a) { + let a00, a01, a02, a03, a10, a11, a12, a13; + let a20, a21, a22, a23, a30, a31, a32, a33; + if (a instanceof MatrixNumjs) { + a00 = a._mat4.get(0); + a01 = a._mat4.get(1); + a02 = a._mat4.get(2); + a03 = a._mat4.get(3); + a10 = a._mat4.get(4); + a11 = a._mat4.get(5); + a12 = a._mat4.get(6); + a13 = a._mat4.get(7); + a20 = a._mat4.get(8); + a21 = a._mat4.get(9); + a22 = a._mat4.get(10); + a23 = a._mat4.get(11); + a30 = a._mat4.get(12); + a31 = a._mat4.get(13); + a32 = a._mat4.get(14); + a33 = a._mat4.get(15); + } else if (isMatrixArray(a)) { + 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]; + a30 = a[12]; + a31 = a[13]; + a32 = a[14]; + a33 = a[15]; + } + const b00 = a00 * a11 - a01 * a10; + const b01 = a00 * a12 - a02 * a10; + const b02 = a00 * a13 - a03 * a10; + const b03 = a01 * a12 - a02 * a11; + const b04 = a01 * a13 - a03 * a11; + const b05 = a02 * a13 - a03 * a12; + const b06 = a20 * a31 - a21 * a30; + const b07 = a20 * a32 - a22 * a30; + const b08 = a20 * a33 - a23 * a30; + const b09 = a21 * a32 - a22 * a31; + const b10 = a21 * a33 - a23 * a31; + const b11 = a22 * a33 - a23 * a32; + + // Calculate the determinant + let det = + b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + if (!det) { + return null; + } + det = 1.0 / det; + + this._mat4.set(0, (a11 * b11 - a12 * b10 + a13 * b09) * det); + this._mat4.set(1, (a02 * b10 - a01 * b11 - a03 * b09) * det); + this._mat4.set(2, (a31 * b05 - a32 * b04 + a33 * b03) * det); + this._mat4.set(3, (a22 * b04 - a21 * b05 - a23 * b03) * det); + this._mat4.set(4, (a12 * b08 - a10 * b11 - a13 * b07) * det); + this._mat4.set(5, (a00 * b11 - a02 * b08 + a03 * b07) * det); + this._mat4.set(6, (a32 * b02 - a30 * b05 - a33 * b01) * det); + this._mat4.set(7, (a20 * b05 - a22 * b02 + a23 * b01) * det); + this._mat4.set(8, (a10 * b10 - a11 * b08 + a13 * b06) * det); + this._mat4.set(9, (a01 * b08 - a00 * b10 - a03 * b06) * det); + this._mat4.set(10, (a30 * b04 - a31 * b02 + a33 * b00) * det); + this._mat4.set(11, (a21 * b02 - a20 * b04 - a23 * b00) * det); + this._mat4.set(12, (a11 * b07 - a10 * b09 - a12 * b06) * det); + this._mat4.set(13, (a00 * b09 - a01 * b07 + a02 * b06) * det); + this._mat4.set(14, (a31 * b01 - a30 * b03 - a32 * b00) * det); + this._mat4.set(15, (a20 * b03 - a21 * b01 + a22 * b00) * det); + + return this; + } + + /** + * Inverts a 3×3 matrix + * @chainable + */ + invert3x3() { + const a00 = this._mat3.get(0); + const a01 = this._mat3.get(1); + const a02 = this._mat3.get(2); + const a10 = this._mat3.get(3); + const a11 = this._mat3.get(4); + const a12 = this._mat3.get(5); + const a20 = this._mat3.get(6); + const a21 = this._mat3.get(7); + const a22 = this._mat3.get(8); + const b01 = a22 * a11 - a12 * a21; + const b11 = -a22 * a10 + a12 * a20; + const b21 = a21 * a10 - a11 * a20; + + // Calculate the determinant + let det = a00 * b01 + a01 * b11 + a02 * b21; + if (!det) { + return null; + } + det = 1.0 / det; + this._mat3.set(0, b01 * det); + this._mat3.set(1, (-a22 * a01 + a02 * a21) * det); + this._mat3.set(2, (a12 * a01 - a02 * a11) * det); + this._mat3.set(3, b11 * det); + this._mat3.set(4, (a22 * a00 - a02 * a20) * det); + this._mat3.set(5, (-a12 * a00 + a02 * a10) * det); + this._mat3.set(6, b21 * det); + this._mat3.set(7, (-a21 * a00 + a01 * a20) * det); + this._mat3.set(8, (a11 * a00 - a01 * a10) * det); + return this; + } + + /** + * This function is only for 3x3 matrices. + * transposes a 3×3 MatrixNumjs by a mat3 + * If there is an array of arguments, the matrix obtained by transposing + * the 3x3 matrix generated based on that array is set. + * If no arguments, it transposes itself and returns it. + * + * @param {Number[]} mat3 1-dimensional array + * @chainable + */ + transpose3x3(mat3) { + if (mat3 === undefined) { + mat3 = this._mat3; + this._mat3 = this._mat3.reshape(3, 3).transpose().flatten(); + } else { + const temp = new MatrixNumjs(3, mat3); + temp._mat3 = temp._mat3.reshape(3, 3).transpose().flatten(); + this._mat3 = temp._mat3; + } + return this; + } + + /** + * converts a 4×4 matrix to its 3×3 inverse transform + * commonly used in MVMatrix to NMatrix conversions. + * @param {MatrixNumjs} mat4 the matrix to be based on to invert + * @chainable + * @todo finish implementation + */ + inverseTranspose({ mat4 }) { + if (this._mat3 === undefined) { + // p5._friendlyError("sorry, this function only works with mat3"); + } else { + //convert mat4 -> mat3 + this._mat3 = this._mat3.flatten(); + this._mat3.set(0, mat4[0]); + this._mat3.set(1, mat4[1]); + this._mat3.set(2, mat4[2]); + this._mat3.set(3, mat4[4]); + this._mat3.set(4, mat4[5]); + this._mat3.set(5, mat4[6]); + this._mat3.set(6, mat4[8]); + this._mat3.set(7, mat4[9]); + this._mat3.set(8, mat4[10]); + } + + const inverse = this.invert3x3(); + // check inverse succeeded + if (inverse) { + inverse.transpose3x3(this._mat3); + } else { + // in case of singularity, just zero the matrix + for (let i = 0; i < 9; i++) { + this._mat3.set(i, 0); + } + } + return this; + } + + /** + * inspired by Toji's mat4 determinant + * @return {Number} Determinant of our 4×4 matrix + */ + determinant() { + const d00 = + this._mat4.get(0) * this._mat4.get(5) - + this._mat4.get(1) * this._mat4.get(4), + d01 = + this._mat4.get(0) * this._mat4.get(6) - + this._mat4.get(2) * this._mat4.get(4), + d02 = + this._mat4.get(0) * this._mat4.get(7) - + this._mat4.get(3) * this._mat4.get(4), + d03 = + this._mat4.get(1) * this._mat4.get(6) - + this._mat4.get(2) * this._mat4.get(5), + d04 = + this._mat4.get(1) * this._mat4.get(7) - + this._mat4.get(3) * this._mat4.get(5), + d05 = + this._mat4.get(2) * this._mat4.get(7) - + this._mat4.get(3) * this._mat4.get(6), + d06 = + this._mat4.get(8) * this._mat4.get(13) - + this._mat4.get(9) * this._mat4.get(12), + d07 = + this._mat4.get(8) * this._mat4.get(14) - + this._mat4.get(10) * this._mat4.get(12), + d08 = + this._mat4.get(8) * this._mat4.get(15) - + this._mat4.get(11) * this._mat4.get(12), + d09 = + this._mat4.get(9) * this._mat4.get(14) - + this._mat4.get(10) * this._mat4.get(13), + d10 = + this._mat4.get(9) * this._mat4.get(15) - + this._mat4.get(11) * this._mat4.get(13), + d11 = + this._mat4.get(10) * this._mat4.get(15) - + this._mat4.get(11) * this._mat4.get(14); + + // Calculate the determinant + return ( + d00 * d11 - d01 * d10 + d02 * d09 + d03 * d08 - d04 * d07 + d05 * d06 + ); + } + + /** + * multiply two mat4s + * @param {MatrixNumjs|Float32Array|Number[]} multMatrix The matrix + * we want to multiply by + * @chainable + */ + mult(multMatrix) { + if (isMatrixArray(multMatrix)) { + multMatrix = new MatrixNumjs(multMatrix); + } + if (this._mat3 !== undefined) { + let a = this._mat3.reshape(3, 3); + a = a.dot(multMatrix._mat3?.reshape(3, 3)).flatten(); + this._mat3 = a; + } else if (this._mat4 !== undefined) { + let a = this._mat4.reshape(4, 4); + a = a.dot(multMatrix._mat4?.reshape(4, 4)).flatten(); + this._mat4 = a; + } + return this; + } + + apply(multMatrix) { + let _src; + + if (multMatrix === this || multMatrix === this._mat4) { + _src = this.copy().mat4; // only need to allocate in this rare case + } else if (multMatrix instanceof MatrixNumjs) { + _src = multMatrix.mat4; + } else if (isMatrixArray(multMatrix)) { + _src = multMatrix; + } else if (arguments.length === 16) { + _src = arguments; + } else { + return; // nothing to do. + } + + const mat4 = this._mat4.tolist(); + + // each row is used for the multiplier + const m0 = mat4[0]; + const m4 = mat4[4]; + const m8 = mat4[8]; + const m12 = mat4[12]; + mat4[0] = _src[0] * m0 + _src[1] * m4 + _src[2] * m8 + _src[3] * m12; + mat4[4] = _src[4] * m0 + _src[5] * m4 + _src[6] * m8 + _src[7] * m12; + mat4[8] = _src[8] * m0 + _src[9] * m4 + _src[10] * m8 + _src[11] * m12; + mat4[12] = _src[12] * m0 + _src[13] * m4 + _src[14] * m8 + _src[15] * m12; + + const m1 = mat4[1]; + const m5 = mat4[5]; + const m9 = mat4[9]; + const m13 = mat4[13]; + mat4[1] = _src[0] * m1 + _src[1] * m5 + _src[2] * m9 + _src[3] * m13; + mat4[5] = _src[4] * m1 + _src[5] * m5 + _src[6] * m9 + _src[7] * m13; + mat4[9] = _src[8] * m1 + _src[9] * m5 + _src[10] * m9 + _src[11] * m13; + mat4[13] = _src[12] * m1 + _src[13] * m5 + _src[14] * m9 + _src[15] * m13; + + const m2 = mat4[2]; + const m6 = mat4[6]; + const m10 = mat4[10]; + const m14 = mat4[14]; + mat4[2] = _src[0] * m2 + _src[1] * m6 + _src[2] * m10 + _src[3] * m14; + mat4[6] = _src[4] * m2 + _src[5] * m6 + _src[6] * m10 + _src[7] * m14; + mat4[10] = _src[8] * m2 + _src[9] * m6 + _src[10] * m10 + _src[11] * m14; + mat4[14] = _src[12] * m2 + _src[13] * m6 + _src[14] * m10 + _src[15] * m14; + + const m3 = mat4[3]; + const m7 = mat4[7]; + const m11 = mat4[11]; + const m15 = mat4[15]; + mat4[3] = _src[0] * m3 + _src[1] * m7 + _src[2] * m11 + _src[3] * m15; + mat4[7] = _src[4] * m3 + _src[5] * m7 + _src[6] * m11 + _src[7] * m15; + mat4[11] = _src[8] * m3 + _src[9] * m7 + _src[10] * m11 + _src[11] * m15; + mat4[15] = _src[12] * m3 + _src[13] * m7 + _src[14] * m11 + _src[15] * m15; + this._mat4 = nj.array(mat4); + return this; + } + + /** + * scales a MatrixNumjs by scalars or a vector + * @param {Vector|Float32Array|Number[]} s vector to scale by + * @chainable + */ + scale(x, y, z) { + if (x instanceof Vector) { + // x is a vector, extract the components from it. + y = x.y; + z = x.z; + x = x.x; // must be last + } else if (x instanceof Array) { + // x is an array, extract the components from it. + y = x[1]; + z = x[2]; + x = x[0]; // must be last + } + this._mat4 = this._mat4.flatten(); + const vect = nj.array([x, y, z, 1]); + this._mat4.set(0, x * this._mat4.get(0)); + this._mat4.set(1, x * this._mat4.get(1)); + this._mat4.set(2, x * this._mat4.get(2)); + this._mat4.set(3, x * this._mat4.get(3)); + this._mat4.set(4, y * this._mat4.get(4)); + this._mat4.set(5, y * this._mat4.get(5)); + this._mat4.set(6, y * this._mat4.get(6)); + this._mat4.set(7, y * this._mat4.get(7)); + this._mat4.set(8, z * this._mat4.get(8)); + this._mat4.set(9, z * this._mat4.get(9)); + this._mat4.set(10, z * this._mat4.get(10)); + this._mat4.set(11, z * this._mat4.get(11)); + return this; + } + + /** + * rotate our Matrix around an axis by the given angle. + * @param {Number} a The angle of rotation in radians + * @param {Vector|Number[]} axis the axis(es) to rotate around + * @chainable + * inspired by Toji's gl-matrix lib, mat4 rotation + */ + rotate(a, x, y, z) { + if (x instanceof Vector) { + // x is a vector, extract the components from it. + y = x.y; + z = x.z; + x = x.x; //must be last + } else if (x instanceof Array) { + // x is an array, extract the components from it. + y = x[1]; + z = x[2]; + x = x[0]; //must be last + } + + const len = Math.sqrt(x * x + y * y + z * z); + x *= 1 / len; + y *= 1 / len; + z *= 1 / len; + + // const aMat = this._mat4.reshape(4,4) + this._mat4 = this._mat4.flatten(); + const a00 = this._mat4.get(0); + const a01 = this._mat4.get(1); + const a02 = this._mat4.get(2); + const a03 = this._mat4.get(3); + const a10 = this._mat4.get(4); + const a11 = this._mat4.get(5); + const a12 = this._mat4.get(6); + const a13 = this._mat4.get(7); + const a20 = this._mat4.get(8); + const a21 = this._mat4.get(9); + const a22 = this._mat4.get(10); + const a23 = this._mat4.get(11); + + //sin,cos, and tan of respective angle + const sA = Math.sin(a); + const cA = Math.cos(a); + const tA = 1 - cA; + // Construct the elements of the rotation matrix + const b00 = x * x * tA + cA; + const b01 = y * x * tA + z * sA; + const b02 = z * x * tA - y * sA; + + const b10 = x * y * tA - z * sA; + const b11 = y * y * tA + cA; + const b12 = z * y * tA + x * sA; + + const b20 = x * z * tA + y * sA; + const b21 = y * z * tA - x * sA; + const b22 = z * z * tA + cA; + + // rotation-specific matrix multiplication + this._mat4.set(0, a00 * b00 + a10 * b01 + a20 * b02); + this._mat4.set(1, a01 * b00 + a11 * b01 + a21 * b02); + this._mat4.set(2, a02 * b00 + a12 * b01 + a22 * b02); + this._mat4.set(3, a03 * b00 + a13 * b01 + a23 * b02); + this._mat4.set(4, a00 * b10 + a10 * b11 + a20 * b12); + this._mat4.set(5, a01 * b10 + a11 * b11 + a21 * b12); + this._mat4.set(6, a02 * b10 + a12 * b11 + a22 * b12); + this._mat4.set(7, a03 * b10 + a13 * b11 + a23 * b12); + this._mat4.set(8, a00 * b20 + a10 * b21 + a20 * b22); + this._mat4.set(9, a01 * b20 + a11 * b21 + a21 * b22); + this._mat4.set(10, a02 * b20 + a12 * b21 + a22 * b22); + this._mat4.set(11, a03 * b20 + a13 * b21 + a23 * b22); + return this; + } + + /** + * @todo finish implementing this method! + * translates + * @param {Number[]} v vector to translate by + * @chainable + */ + translate(v) { + const x = v[0], + y = v[1], + z = v[2] || 0; + this._mat4 = this._mat4.flatten(); + this._mat4.set( + 12, + this._mat4.get(0) * x + + this._mat4.get(4) * y + + this._mat4.get(8) * z + + this._mat4.get(12) + ); + this._mat4.set( + 13, + this._mat4.get(1) * x + + this._mat4.get(5) * y + + this._mat4.get(9) * z + + this._mat4.get(13) + ); + this._mat4.set( + 14, + this._mat4.get(2) * x + + this._mat4.get(6) * y + + this._mat4.get(10) * z + + this._mat4.get(14) + ); + this._mat4.set( + 15, + this._mat4.get(3) * x + + this._mat4.get(7) * y + + this._mat4.get(11) * z + + this._mat4.get(15) + ); + } + + rotateX(a) { + this.rotate(a, 1, 0, 0); + } + rotateY(a) { + this.rotate(a, 0, 1, 0); + } + rotateZ(a) { + this.rotate(a, 0, 0, 1); + } + + /** + * sets the perspective matrix + * @param {Number} fovy [description] + * @param {Number} aspect [description] + * @param {Number} near near clipping plane + * @param {Number} far far clipping plane + * @chainable + */ + perspective(fovy, aspect, near, far) { + const f = 1.0 / Math.tan(fovy / 2), + nf = 1 / (near - far); + + this._mat4 = this._mat4.flatten(); + this._mat4.set(0, f / aspect); + this._mat4.set(1, 0); + this._mat4.set(2, 0); + this._mat4.set(3, 0); + this._mat4.set(4, 0); + this._mat4.set(5, f); + this._mat4.set(6, 0); + this._mat4.set(7, 0); + this._mat4.set(8, 0); + this._mat4.set(9, 0); + this._mat4.set(10, (far + near) * nf); + this._mat4.set(11, -1); + this._mat4.set(12, 0); + this._mat4.set(13, 0); + this._mat4.set(14, 2 * far * near * nf); + this._mat4.set(15, 0); + + return this; + } + + /** + * sets the ortho matrix + * @param {Number} left [description] + * @param {Number} right [description] + * @param {Number} bottom [description] + * @param {Number} top [description] + * @param {Number} near near clipping plane + * @param {Number} far far clipping plane + * @chainable + */ + ortho(left, right, bottom, top, near, far) { + const lr = 1 / (left - right), + bt = 1 / (bottom - top), + nf = 1 / (near - far); + this._mat4 = this._mat4.flatten(); + this._mat4.set(0, -2 * lr); + this._mat4.set(1, 0); + this._mat4.set(2, 0); + this._mat4.set(3, 0); + this._mat4.set(4, 0); + this._mat4.set(5, -2 * bt); + this._mat4.set(6, 0); + this._mat4.set(7, 0); + this._mat4.set(8, 0); + this._mat4.set(9, 0); + this._mat4.set(10, 2 * nf); + this._mat4.set(11, 0); + this._mat4.set(12, (left + right) * lr); + this._mat4.set(13, (top + bottom) * bt); + this._mat4.set(14, (far + near) * nf); + this._mat4.set(15, 1); + + return this; + } + + /** + * apply a matrix to a vector with x,y,z,w components + * get the results in the form of an array + * @param {Number} + * @return {Number[]} + */ + multiplyVec4(x, y, z, w) { + const result = new Array(4); + const m = this._mat4; + + result[0] = m.get(0) * x + m.get(4) * y + m.get(8) * z + m.get(12) * w; + result[1] = m.get(1) * x + m.get(5) * y + m.get(9) * z + m.get(13) * w; + result[2] = m.get(2) * x + m.get(6) * y + m.get(10) * z + m.get(14) * w; + result[3] = m.get(3) * x + m.get(7) * y + m.get(11) * z + m.get(15) * w; + + return result; + } + + /** + * Applies a matrix to a vector. + * The fourth component is set to 1. + * Returns a vector consisting of the first + * through third components of the result. + * + * @param {Vector} + * @return {Vector} + */ + multiplyPoint({ x, y, z }) { + const array = this.multiplyVec4(x, y, z, 1); + return new Vector(array[0], array[1], array[2]); + } + + /** + * Applies a matrix to a vector. + * The fourth component is set to 1. + * Returns the result of dividing the 1st to 3rd components + * of the result by the 4th component as a vector. + * + * @param {Vector} + * @return {Vector} + */ + multiplyAndNormalizePoint({ x, y, z }) { + const array = this.multiplyVec4(x, y, z, 1); + array[0] /= array[3]; + array[1] /= array[3]; + array[2] /= array[3]; + return new Vector(array[0], array[1], array[2]); + } + + /** + * Applies a matrix to a vector. + * The fourth component is set to 0. + * Returns a vector consisting of the first + * through third components of the result. + * + * @param {Vector} + * @return {Vector} + */ + multiplyDirection({ x, y, z }) { + const array = this.multiplyVec4(x, y, z, 0); + return new Vector(array[0], array[1], array[2]); + } + + /** + * This function is only for 3x3 matrices. + * multiply two mat3s. It is an operation to multiply the 3x3 matrix of + * the argument from the right. Arguments can be a 3x3 MatrixNumjs, + * a Float32Array of length 9, or a javascript array of length 9. + * In addition, it can also be done by enumerating 9 numbers. + * + * @param {MatrixNumjs|Float32Array|Number[]} multMatrix The matrix + * we want to multiply by + * @chainable + */ + mult3x3(multMatrix) { + let _src; + let tempMatrix = multMatrix; + if (multMatrix === this || multMatrix === this._mat3) { + // mat3; // only need to allocate in this rare case + } else if (multMatrix instanceof MatrixNumjs) { + _src = multMatrix.mat3; + } else if (isMatrixArray(multMatrix)) { + multMatrix._mat3 = nj.array(arguments); + } else if (arguments.length === 9) { + tempMatrix = new MatrixNumjs(3, Array.from(arguments)); + } else { + return; // nothing to do. + } + let a = this._mat3.reshape(3, 3); + a = a.dot(tempMatrix._mat3.reshape(3, 3)).flatten(); + this._mat3 = a; + return this; + } + + /** + * This function is only for 3x3 matrices. + * A function that returns a column vector of a 3x3 matrix. + * + * @param {Number} columnIndex matrix column number + * @return {Vector} + */ + column(columnIndex) { + // let temp = this._mat3.reshape(3,3) + let vect = new Vector( + this._mat3.tolist()[3 * columnIndex], + this._mat3.tolist()[3 * columnIndex + 1], + this._mat3.tolist()[3 * columnIndex + 2] + ); + return vect; + } + + /** + * This function is only for 3x3 matrices. + * A function that returns a row vector of a 3x3 matrix. + * + * @param {Number} rowIndex matrix row number + * @return {Vector} + */ + row(rowIndex) { + return new Vector( + this._mat3.tolist()[rowIndex], + this._mat3.tolist()[rowIndex + 3], + this._mat3.tolist()[rowIndex + 6] + ); + } + + /** + * Returns the diagonal elements of the matrix in the form of an array. + * A 3x3 matrix will return an array of length 3. + * A 4x4 matrix will return an array of length 4. + * + * @return {Number[]} An array obtained by arranging the diagonal elements + * of the matrix in ascending order of index + */ + diagonal() { + if (this._mat3 !== undefined) { + return this._mat3.reshape(3, 3).diag().tolist(); + } + return this._mat4.reshape(4, 4).diag().tolist(); + } + + /** + * This function is only for 3x3 matrices. + * Takes a vector and returns the vector resulting from multiplying to + * that vector by this matrix from left. + * + * @param {Vector} multVector the vector to which this matrix applies + * @param {Vector} [target] The vector to receive the result + * @return {Vector} + */ + multiplyVec3(multVector, target) { + if (target === undefined) { + target = multVector.copy(); + } + target.x = this.row(0).dot(multVector); + target.y = this.row(1).dot(multVector); + target.z = this.row(2).dot(multVector); + return target; + } + + /** + * This function is only for 4x4 matrices. + * Creates a 3x3 matrix whose entries are the top left 3x3 part and returns it. + * + * @return {MatrixNumjs} + */ + createSubMatrix3x3() { + const result = new MatrixNumjs(3); + result._mat3 = result._mat3.flatten(); + result._mat3.set(0, this._mat4.get(0)); + result._mat3.set(1, this._mat4.get(1)); + result._mat3.set(2, this._mat4.get(2)); + result._mat3.set(3, this._mat4.get(4)); + result._mat3.set(4, this._mat4.get(5)); + result._mat3.set(5, this._mat4.get(6)); + result._mat3.set(6, this._mat4.get(8)); + result._mat3.set(7, this._mat4.get(9)); + result._mat3.set(8, this._mat4.get(10)); + return result; + } + + /** + * PRIVATE + */ + // matrix methods adapted from: + // https://developer.mozilla.org/en-US/docs/Web/WebGL/ + // gluPerspective + // + // function _makePerspective(fovy, aspect, znear, zfar){ + // const ymax = znear * Math.tan(fovy * Math.PI / 360.0); + // const ymin = -ymax; + // const xmin = ymin * aspect; + // const xmax = ymax * aspect; + // return _makeFrustum(xmin, xmax, ymin, ymax, znear, zfar); + // } + + //// + //// glFrustum + //// + //function _makeFrustum(left, right, bottom, top, znear, zfar){ + // const X = 2*znear/(right-left); + // const Y = 2*znear/(top-bottom); + // const A = (right+left)/(right-left); + // const B = (top+bottom)/(top-bottom); + // const C = -(zfar+znear)/(zfar-znear); + // const D = -2*zfar*znear/(zfar-znear); + // const frustrumMatrix =[ + // X, 0, A, 0, + // 0, Y, B, 0, + // 0, 0, C, D, + // 0, 0, -1, 0 + //]; + //return frustrumMatrix; + // } + + // function _setMVPMatrices(){ + ////an identity matrix + ////@TODO use the MatrixNumjs class to abstract away our MV matrices and + ///other math + //const _mvMatrix = + //[ + // 1.0,0.0,0.0,0.0, + // 0.0,1.0,0.0,0.0, + // 0.0,0.0,1.0,0.0, + // 0.0,0.0,0.0,1.0 + //]; +} + diff --git a/src/math/math.js b/src/math/math.js index 55a88b4541..c3e9103da4 100644 --- a/src/math/math.js +++ b/src/math/math.js @@ -93,7 +93,7 @@ function math(p5, fn){ * * */ - fn.createVector = function(x, y, z) { + fn.createVector = function (x, y, z) { if (this instanceof p5) { return new p5.Vector( this._fromRadians.bind(this), @@ -104,6 +104,32 @@ function math(p5, fn){ return new p5.Vector(x, y, z); } }; + + /** + * Creates a new p5.Matrix object. + * + * A matrix is a mathematical concept that is useful in many fields, including + * computer graphics. In p5.js, matrices are used to perform transformations + * on shapes and images. + * + * @method createMatrix + * @return {p5.Matrix} new p5.Matrix object. + * + * @example + *
+ * + * function setup() { + * createCanvas(100, 100); + * let matrix = createMatrix(); + * console.log(matrix); + * describe('Logs a new p5.Matrix object to the console.'); + * } + * + *
+ */ + fn.createMatrix = function (...args) { + return new p5.Matrix(...args); + }; } export default math; diff --git a/src/math/p5.Matrix.js b/src/math/p5.Matrix.js new file mode 100644 index 0000000000..704544dc86 --- /dev/null +++ b/src/math/p5.Matrix.js @@ -0,0 +1,30 @@ +/** + * @requires constants + * @todo see methods below needing further implementation. + * future consideration: implement SIMD optimizations + * when browser compatibility becomes available + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/ + * Reference/Global_Objects/SIMD + */ +import { Matrix } from './Matrices/Matrix' +// import { MatrixNumjs as Matrix } from './Matrices/MatrixNumjs' + + + +function matrix(p5, fn){ + /** + * A class to describe a 4×4 matrix + * for model and view matrix manipulation in the p5js webgl renderer. + * @class p5.Matrix + * @private + * @param {Array} [mat4] column-major array literal of our 4×4 matrix + */ + p5.Matrix = Matrix +} + +export default matrix; +export { Matrix }; + +if(typeof p5 !== 'undefined'){ + matrix(p5, p5.prototype); +} diff --git a/src/math/p5.Vector.js b/src/math/p5.Vector.js index 738b966f8d..9b983acfc6 100644 --- a/src/math/p5.Vector.js +++ b/src/math/p5.Vector.js @@ -34,23 +34,223 @@ class Vector { // This is how it comes in with createVector() // This check if the first argument is a function constructor(...args) { - let x, y, z; - if (typeof args[0] === 'function') { + let dimensions = args.length; // TODO: make default 3 if no arguments + let values = args.map((arg) => arg || 0); + if (typeof args[0] === "function") { this.isPInst = true; this._fromRadians = args[0]; this._toRadians = args[1]; - x = args[2] || 0; - y = args[3] || 0; - z = args[4] || 0; - // This is what we'll get with new Vector() + values = args.slice(2).map((arg) => arg || 0); + } + if (dimensions === 0) { + this.dimensions = 2; + this._values = [0, 0, 0]; + } else { + this.dimensions = dimensions; + this._values = values; + } + } + + /** + * Gets the values of the vector. + * + * This method returns an array of numbers that represent the vector. + * Each number in the array corresponds to a different component of the vector, + * like its position in different directions (e.g., x, y, z). + * + * @returns {Array} The array of values representing the vector. + */ + get values() { + return this._values; + } + + /** + * Sets the values of the vector. + * + * This method allows you to update the entire vector with a new set of values. + * You need to provide an array of numbers, where each number represents a component + * of the vector (e.g., x, y, z). The length of the array should match the number of + * dimensions of the vector. If the array is shorter, the missing components will be + * set to 0. If the array is longer, the extra values will be ignored. + * + * @param {Array} newValues - An array of numbers representing the new values for the vector. + * + */ + set values(newValues) { + let dimensions = newValues.length; + if (dimensions === 0) { + this.dimensions = 2; + this._values = [0, 0, 0]; + } else { + this.dimensions = dimensions; + this._values = newValues.slice(); + } + } + + /** + * Gets the x component of the vector. + * + * This method returns the value of the x component of the vector. + * Think of the x component as the horizontal position or the first number in the vector. + * If the x component is not defined, it will return 0. + * + * @returns {number} The x component of the vector. Returns 0 if the value is not defined. + */ + get x() { + return this._values[0] || 0; + } + + /** + * Retrieves the value at the specified index from the vector. + * + * This method allows you to get the value of a specific component of the vector + * by providing its index. Think of the vector as a list of numbers, where each + * number represents a different direction (like x, y, or z). The index is just + * the position of the number in that list. + * + * For example, if you have a vector with values 10, 20, 30 the index 0 would + * give you the first value 10, index 1 would give you the second value 20, + * and so on. + * + * @param {number} index - The position of the value you want to get from the vector. + * @returns {number} The value at the specified position in the vector. + * @throws Will throw an error if the index is out of bounds, meaning if you try to + * get a value from a position that doesn't exist in the vector. + */ + getValue(index) { + if (index < this._values.length) { + return this._values[index]; + } else { + p5._friendlyError( + "The index parameter is trying to set a value outside the bounds of the vector", + "p5.Vector.setValue" + ); + } + } + + /** + * Sets the value at the specified index of the vector. + * + * This method allows you to change a specific component of the vector by providing its index and the new value you want to set. + * Think of the vector as a list of numbers, where each number represents a different direction (like x, y, or z). + * The index is just the position of the number in that list. + * + * For example, if you have a vector with values [0, 20, 30], and you want to change the second value (20) to 50, + * you would use this method with index 1 (since indexes start at 0) and value 50. + * + * @param {number} index - The position in the vector where you want to set the new value. + * @param {number} value - The new value you want to set at the specified position. + * @throws Will throw an error if the index is outside the bounds of the vector, meaning if you try to set a value at a position that doesn't exist in the vector. + */ + + setValue(index, value) { + if (index < this._values.length) { + this._values[index] = value; } else { - x = args[0] || 0; - y = args[1] || 0; - z = args[2] || 0; + p5._friendlyError( + "The index parameter is trying to set a value outside the bounds of the vector", + "p5.Vector.setValue" + ); + } + } + + /** + * Gets the y component of the vector. + * + * This method returns the value of the y component of the vector. + * Think of the y component as the vertical position or the second number in the vector. + * If the y component is not defined, it will return 0. + * + * @returns {number} The y component of the vector. Returns 0 if the value is not defined. + */ + get y() { + return this._values[1] || 0; + } + + /** + * Gets the z component of the vector. + * + * This method returns the value of the z component of the vector. + * Think of the z component as the depth or the third number in the vector. + * If the z component is not defined, it will return 0. + * + * @returns {number} The z component of the vector. Returns 0 if the value is not defined. + */ + get z() { + return this._values[2] || 0; + } + + /** + * Gets the w component of the vector. + * + * This method returns the value of the w component of the vector. + * Think of the w component as the fourth number in the vector. + * If the w component is not defined, it will return 0. + * + * @returns {number} The w component of the vector. Returns 0 if the value is not defined. + */ + get w() { + return this._values[3] || 0; + } + + /** + * Sets the x component of the vector. + * + * This method allows you to change the x value of the vector. + * The x value is the first number in the vector, representing the horizontal position. + * By calling this method, you can update the x value to a new number. + * + * @param {number} xVal - The new value for the x component. + */ + set x(xVal) { + if (this._values.length > 1) { + this._values[0] = xVal; + } + } + + /** + * Sets the y component of the vector. + * + * This method allows you to change the y value of the vector. + * The y value is the second number in the vector, representing the vertical position. + * By calling this method, you can update the y value to a new number. + * + * @param {number} yVal - The new value for the y component. + */ + set y(yVal) { + if (this._values.length > 1) { + this._values[1] = yVal; + } + } + + /** + * Sets the z component of the vector. + * + * This method allows you to change the z value of the vector. + * The z value is the third number in the vector, representing the depth or the third dimension. + * By calling this method, you can update the z value to a new number. + * + * @param {number} zVal - The new value for the z component. + */ + set z(zVal) { + if (this._values.length > 2) { + this._values[2] = zVal; + } + } + + /** + * Sets the w component of the vector. + * + * This method allows you to change the w value of the vector. + * The w value is the fourth number in the vector, representing the fourth dimension. + * By calling this method, you can update the w value to a new number. + * + * @param {number} wVal - The new value for the w component. + */ + set w(wVal) { + if (this._values.length > 3) { + this._values[3] = wVal; } - this.x = x; - this.y = y; - this.z = z; } /** @@ -74,7 +274,7 @@ class Vector { * */ toString() { - return `p5.Vector Object : [${this.x}, ${this.y}, ${this.z}]`; + return `[${this.values.join(", ")}]`; } /** @@ -134,23 +334,15 @@ class Vector { * @param {p5.Vector|Number[]} value vector to set. * @chainable */ - set(x, y, z) { - if (x instanceof Vector) { - this.x = x.x || 0; - this.y = x.y || 0; - this.z = x.z || 0; - return this; - } - if (Array.isArray(x)) { - this.x = x[0] || 0; - this.y = x[1] || 0; - this.z = x[2] || 0; - return this; + set(...args) { + if (args[0] instanceof Vector) { + this.values = args[0].values.slice(); + } else if (Array.isArray(args[0])) { + this.values = args[0].map((arg) => arg || 0); + } else { + this.values = args.map((arg) => arg || 0); } - this.x = x || 0; - this.y = y || 0; - this.z = z || 0; - + this.dimensions = this.values.length; return this; } @@ -184,15 +376,9 @@ class Vector { */ copy() { if (this.isPInst) { - return new Vector( - this._fromRadians, - this._toRadians, - this.x, - this.y, - this.z - ); + return new Vector(this._fromRadians, this._toRadians, ...this.values); } else { - return new Vector(this.x, this.y, this.z); + return new Vector(...this.values); } } @@ -328,22 +514,15 @@ class Vector { * @param {p5.Vector|Number[]} value The vector to add * @chainable */ - add(x, y, z) { - if (x instanceof Vector) { - this.x += x.x || 0; - this.y += x.y || 0; - this.z += x.z || 0; - return this; + add(...args) { + if (args[0] instanceof Vector) { + args = args[0].values; + } else if (Array.isArray(args[0])) { + args = args[0]; } - if (Array.isArray(x)) { - this.x += x[0] || 0; - this.y += x[1] || 0; - this.z += x[2] || 0; - return this; - } - this.x += x || 0; - this.y += y || 0; - this.z += z || 0; + args.forEach((value, index) => { + this.values[index] = (this.values[index] || 0) + (value || 0); + }); return this; } @@ -481,7 +660,7 @@ class Vector { ); } } else if (Array.isArray(x)) { - if (x.every(element => Number.isFinite(element))) { + if (x.every((element) => Number.isFinite(element))) { if (x.length === 2) { return calculateRemainder2D.call(this, x[0], x[1]); } @@ -498,7 +677,7 @@ class Vector { } } else if (arguments.length === 2) { const vectorComponents = [...arguments]; - if (vectorComponents.every(element => Number.isFinite(element))) { + if (vectorComponents.every((element) => Number.isFinite(element))) { if (vectorComponents.length === 2) { return calculateRemainder2D.call( this, @@ -509,7 +688,7 @@ class Vector { } } else if (arguments.length === 3) { const vectorComponents = [...arguments]; - if (vectorComponents.every(element => Number.isFinite(element))) { + if (vectorComponents.every((element) => Number.isFinite(element))) { if (vectorComponents.length === 3) { return calculateRemainder3D.call( this, @@ -651,22 +830,20 @@ class Vector { * @param {p5.Vector|Number[]} value the vector to subtract * @chainable */ - sub(x, y, z) { - if (x instanceof Vector) { - this.x -= x.x || 0; - this.y -= x.y || 0; - this.z -= x.z || 0; - return this; - } - if (Array.isArray(x)) { - this.x -= x[0] || 0; - this.y -= x[1] || 0; - this.z -= x[2] || 0; - return this; + sub(...args) { + if (args[0] instanceof Vector) { + args[0].values.forEach((value, index) => { + this.values[index] -= value || 0; + }); + } else if (Array.isArray(args[0])) { + args[0].forEach((value, index) => { + this.values[index] -= value || 0; + }); + } else { + args.forEach((value, index) => { + this.values[index] -= value || 0; + }); } - this.x -= x || 0; - this.y -= y || 0; - this.z -= z || 0; return this; } @@ -862,82 +1039,44 @@ class Vector { * @param {p5.Vector} v vector to multiply with the components of the original vector. * @chainable */ - mult(x, y, z) { - if (x instanceof Vector) { - // new p5.Vector will check that values are valid upon construction but it's possible - // that someone could change the value of a component after creation, which is why we still - // perform this check - if ( - Number.isFinite(x.x) && - Number.isFinite(x.y) && - Number.isFinite(x.z) && - typeof x.x === 'number' && - typeof x.y === 'number' && - typeof x.z === 'number' - ) { - this.x *= x.x; - this.y *= x.y; - this.z *= x.z; - } else { - console.warn( - 'p5.Vector.prototype.mult:', - 'x contains components that are either undefined or not finite numbers' - ); + mult(...args) { + if (args.length === 1 && args[0] instanceof Vector) { + const v = args[0]; + const maxLen = Math.min(this.values.length, v.values.length); + for (let i = 0; i < maxLen; i++) { + if (Number.isFinite(v.values[i]) && typeof v.values[i] === "number") { + this._values[i] *= v.values[i]; + } else { + console.warn( + "p5.Vector.prototype.mult:", + "v contains components that are either undefined or not finite numbers" + ); + return this; + } } - return this; - } - if (Array.isArray(x)) { - if ( - x.every(element => Number.isFinite(element)) && - x.every(element => typeof element === 'number') - ) { - if (x.length === 1) { - this.x *= x[0]; - this.y *= x[0]; - this.z *= x[0]; - } else if (x.length === 2) { - this.x *= x[0]; - this.y *= x[1]; - } else if (x.length === 3) { - this.x *= x[0]; - this.y *= x[1]; - this.z *= x[2]; + } else if (args.length === 1 && Array.isArray(args[0])) { + const arr = args[0]; + const maxLen = Math.min(this.values.length, arr.length); + for (let i = 0; i < maxLen; i++) { + if (Number.isFinite(arr[i]) && typeof arr[i] === "number") { + this._values[i] *= arr[i]; + } else { + console.warn( + "p5.Vector.prototype.mult:", + "arr contains elements that are either undefined or not finite numbers" + ); + return this; } - } else { - console.warn( - 'p5.Vector.prototype.mult:', - 'x contains elements that are either undefined or not finite numbers' - ); } - return this; - } - - const vectorComponents = [...arguments]; - if ( - vectorComponents.every(element => Number.isFinite(element)) && - vectorComponents.every(element => typeof element === 'number') + } else if ( + args.length === 1 && + typeof args[0] === "number" && + Number.isFinite(args[0]) ) { - if (arguments.length === 1) { - this.x *= x; - this.y *= x; - this.z *= x; - } - if (arguments.length === 2) { - this.x *= x; - this.y *= y; + for (let i = 0; i < this._values.length; i++) { + this._values[i] *= args[0]; } - if (arguments.length === 3) { - this.x *= x; - this.y *= y; - this.z *= z; - } - } else { - console.warn( - 'p5.Vector.prototype.mult:', - 'x, y, or z arguments are either undefined or not a finite number' - ); } - return this; } @@ -1135,97 +1274,56 @@ class Vector { * @param {p5.Vector} v vector to divide the components of the original vector by. * @chainable */ - div(x, y, z) { - if (x instanceof Vector) { - // new p5.Vector will check that values are valid upon construction but it's possible - // that someone could change the value of a component after creation, which is why we still - // perform this check + div(...args) { + if (args.length === 0) return this; + if (args.length === 1 && args[0] instanceof Vector) { + const v = args[0]; if ( - Number.isFinite(x.x) && - Number.isFinite(x.y) && - Number.isFinite(x.z) && - typeof x.x === 'number' && - typeof x.y === 'number' && - typeof x.z === 'number' + v._values.every( + (val) => Number.isFinite(val) && typeof val === "number" + ) ) { - const isLikely2D = x.z === 0 && this.z === 0; - if (x.x === 0 || x.y === 0 || (!isLikely2D && x.z === 0)) { - console.warn('p5.Vector.prototype.div:', 'divide by 0'); + if (v._values.some((val) => val === 0)) { + console.warn("p5.Vector.prototype.div:", "divide by 0"); return this; } - this.x /= x.x; - this.y /= x.y; - if (!isLikely2D) { - this.z /= x.z; - } + this._values = this._values.map((val, i) => val / v._values[i]); } else { console.warn( - 'p5.Vector.prototype.div:', - 'x contains components that are either undefined or not finite numbers' + "p5.Vector.prototype.div:", + "vector contains components that are either undefined or not finite numbers" ); } return this; } - if (Array.isArray(x)) { - if ( - x.every(element => Number.isFinite(element)) && - x.every(element => typeof element === 'number') - ) { - if (x.some(element => element === 0)) { - console.warn('p5.Vector.prototype.div:', 'divide by 0'); - return this; - } - if (x.length === 1) { - this.x /= x[0]; - this.y /= x[0]; - this.z /= x[0]; - } else if (x.length === 2) { - this.x /= x[0]; - this.y /= x[1]; - } else if (x.length === 3) { - this.x /= x[0]; - this.y /= x[1]; - this.z /= x[2]; + if (args.length === 1 && Array.isArray(args[0])) { + const arr = args[0]; + if (arr.every((val) => Number.isFinite(val) && typeof val === "number")) { + if (arr.some((val) => val === 0)) { + console.warn("p5.Vector.prototype.div:", "divide by 0"); + return this; } + this._values = this._values.map((val, i) => val / arr[i]); } else { console.warn( - 'p5.Vector.prototype.div:', - 'x contains components that are either undefined or not finite numbers' + "p5.Vector.prototype.div:", + "array contains components that are either undefined or not finite numbers" ); } - return this; } - const vectorComponents = [...arguments]; - if ( - vectorComponents.every(element => Number.isFinite(element)) && - vectorComponents.every(element => typeof element === 'number') - ) { - if (vectorComponents.some(element => element === 0)) { - console.warn('p5.Vector.prototype.div:', 'divide by 0'); + if (args.every((val) => Number.isFinite(val) && typeof val === "number")) { + if (args.some((val) => val === 0)) { + console.warn("p5.Vector.prototype.div:", "divide by 0"); return this; } - - if (arguments.length === 1) { - this.x /= x; - this.y /= x; - this.z /= x; - } - if (arguments.length === 2) { - this.x /= x; - this.y /= y; - } - if (arguments.length === 3) { - this.x /= x; - this.y /= y; - this.z /= z; - } + this._values = this._values.map((val, i) => val / args[0]); } else { console.warn( - 'p5.Vector.prototype.div:', - 'x, y, or z arguments are either undefined or not a finite number' + "p5.Vector.prototype.div:", + "arguments contain components that are either undefined or not finite numbers" ); } @@ -1303,10 +1401,10 @@ class Vector { * */ magSq() { - const x = this.x; - const y = this.y; - const z = this.z; - return x * x + y * y + z * z; + return this._values.reduce( + (sum, component) => sum + component * component, + 0 + ); } /** @@ -1413,11 +1511,13 @@ class Vector { * @param {p5.Vector} v p5.Vector to be dotted. * @return {Number} */ - dot(x, y, z) { - if (x instanceof Vector) { - return this.dot(x.x, x.y, x.z); + dot(...args) { + if (args[0] instanceof Vector) { + return this.dot(...args[0]._values); } - return this.x * (x || 0) + this.y * (y || 0) + this.z * (z || 0); + return this._values.reduce((sum, component, index) => { + return sum + component * (args[index] || 0); + }, 0); } /** @@ -1584,10 +1684,7 @@ class Vector { * */ dist(v) { - return v - .copy() - .sub(this) - .mag(); + return v.copy().sub(this).mag(); } /** @@ -2658,8 +2755,12 @@ class Vector { */ slerp(v, amt) { // edge cases. - if (amt === 0) { return this; } - if (amt === 1) { return this.set(v); } + if (amt === 0) { + return this; + } + if (amt === 1) { + return this.set(v); + } // calculate magnitudes const selfMag = this.mag(); @@ -2708,7 +2809,7 @@ class Vector { // Since 'axis' is a unit vector, ey is a vector of the same length as 'this'. const ey = axis.cross(this); // interpolate the length with 'this' and 'v'. - const lerpedMagFactor = (1 - amt) + amt * vMag / selfMag; + const lerpedMagFactor = 1 - amt + (amt * vMag) / selfMag; // imagine a situation where 'axis', 'this', and 'ey' are pointing // along the z, x, and y axes, respectively. // rotates 'this' around 'axis' by amt * theta towards 'ey'. @@ -2928,22 +3029,22 @@ class Vector { * @param {p5.Vector|Array} value vector to compare. * @return {Boolean} */ - equals(x, y, z) { - let a, b, c; - if (x instanceof Vector) { - a = x.x || 0; - b = x.y || 0; - c = x.z || 0; - } else if (Array.isArray(x)) { - a = x[0] || 0; - b = x[1] || 0; - c = x[2] || 0; + equals(...args) { + let values; + if (args[0] instanceof Vector) { + values = args[0]._values; + } else if (Array.isArray(args[0])) { + values = args[0]; } else { - a = x || 0; - b = y || 0; - c = z || 0; + values = args; } - return this.x === a && this.y === b && this.z === c; + + for (let i = 0; i < this._values.length; i++) { + if (this._values[i] !== (values[i] || 0)) { + return false; + } + } + return true; } /** @@ -2960,9 +3061,9 @@ class Vector { * @chainable */ clampToZero() { - this.x = this._clampToZero(this.x); - this.y = this._clampToZero(this.y); - this.z = this._clampToZero(this.z); + for (let i = 0; i < this._values.length; i++) { + this._values[i] = this._clampToZero(this._values[i]); + } return this; } @@ -3047,14 +3148,10 @@ class Vector { * */ static fromAngle(angle, length) { - if (typeof length === 'undefined') { + if (typeof length === "undefined") { length = 1; } - return new Vector( - length * Math.cos(angle), - length * Math.sin(angle), - 0 - ); + return new Vector(length * Math.cos(angle), length * Math.sin(angle), 0); } /** @@ -3113,7 +3210,7 @@ class Vector { * */ static fromAngles(theta, phi, length) { - if (typeof length === 'undefined') { + if (typeof length === "undefined") { length = 1; } const cosPhi = Math.cos(phi); @@ -3246,8 +3343,8 @@ class Vector { target = v1.copy(); if (arguments.length === 3) { p5._friendlyError( - 'The target parameter is undefined, it should be of type p5.Vector', - 'p5.Vector.add' + "The target parameter is undefined, it should be of type p5.Vector", + "p5.Vector.add" ); } } else { @@ -3293,8 +3390,8 @@ class Vector { target = v1.copy(); if (arguments.length === 3) { p5._friendlyError( - 'The target parameter is undefined, it should be of type p5.Vector', - 'p5.Vector.sub' + "The target parameter is undefined, it should be of type p5.Vector", + "p5.Vector.sub" ); } } else { @@ -3337,8 +3434,8 @@ class Vector { target = v.copy(); if (arguments.length === 3) { p5._friendlyError( - 'The target parameter is undefined, it should be of type p5.Vector', - 'p5.Vector.mult' + "The target parameter is undefined, it should be of type p5.Vector", + "p5.Vector.mult" ); } } else { @@ -3363,8 +3460,8 @@ class Vector { } else { if (!(target instanceof Vector)) { p5._friendlyError( - 'The target parameter should be of type p5.Vector', - 'p5.Vector.rotate' + "The target parameter should be of type p5.Vector", + "p5.Vector.rotate" ); } target.set(v); @@ -3407,8 +3504,8 @@ class Vector { if (arguments.length === 3) { p5._friendlyError( - 'The target parameter is undefined, it should be of type p5.Vector', - 'p5.Vector.div' + "The target parameter is undefined, it should be of type p5.Vector", + "p5.Vector.div" ); } } else { @@ -3445,15 +3542,15 @@ class Vector { } /** - * Calculates the Euclidean distance between two points (considering a - * point as a vector object). - */ + * Calculates the Euclidean distance between two points (considering a + * point as a vector object). + */ /** - * @static - * @param {p5.Vector} v1 The first p5.Vector - * @param {p5.Vector} v2 The second p5.Vector - * @return {Number} The distance - */ + * @static + * @param {p5.Vector} v1 The first p5.Vector + * @param {p5.Vector} v2 The second p5.Vector + * @return {Number} The distance + */ static dist(v1, v2) { return v1.dist(v2); } @@ -3475,8 +3572,8 @@ class Vector { target = v1.copy(); if (arguments.length === 4) { p5._friendlyError( - 'The target parameter is undefined, it should be of type p5.Vector', - 'p5.Vector.lerp' + "The target parameter is undefined, it should be of type p5.Vector", + "p5.Vector.lerp" ); } } else { @@ -3505,8 +3602,8 @@ class Vector { target = v1.copy(); if (arguments.length === 4) { p5._friendlyError( - 'The target parameter is undefined, it should be of type p5.Vector', - 'p5.Vector.slerp' + "The target parameter is undefined, it should be of type p5.Vector", + "p5.Vector.slerp" ); } } else { @@ -3559,8 +3656,8 @@ class Vector { } else { if (!(target instanceof Vector)) { p5._friendlyError( - 'The target parameter should be of type p5.Vector', - 'p5.Vector.normalize' + "The target parameter should be of type p5.Vector", + "p5.Vector.normalize" ); } target.set(v); @@ -3585,8 +3682,8 @@ class Vector { } else { if (!(target instanceof Vector)) { p5._friendlyError( - 'The target parameter should be of type p5.Vector', - 'p5.Vector.limit' + "The target parameter should be of type p5.Vector", + "p5.Vector.limit" ); } target.set(v); @@ -3611,8 +3708,8 @@ class Vector { } else { if (!(target instanceof Vector)) { p5._friendlyError( - 'The target parameter should be of type p5.Vector', - 'p5.Vector.setMag' + "The target parameter should be of type p5.Vector", + "p5.Vector.setMag" ); } target.set(v); @@ -3667,8 +3764,8 @@ class Vector { } else { if (!(target instanceof Vector)) { p5._friendlyError( - 'The target parameter should be of type p5.Vector', - 'p5.Vector.reflect' + "The target parameter should be of type p5.Vector", + "p5.Vector.reflect" ); } target.set(incidentVector); @@ -3708,8 +3805,8 @@ class Vector { v = new Vector().set(v1); } else { p5._friendlyError( - 'The v1 parameter should be of type Array or p5.Vector', - 'p5.Vector.equals' + "The v1 parameter should be of type Array or p5.Vector", + "p5.Vector.equals" ); } return v.equals(v2); diff --git a/src/webgl/3d_primitives.js b/src/webgl/3d_primitives.js index 8f8a4f5252..a2af7f3476 100644 --- a/src/webgl/3d_primitives.js +++ b/src/webgl/3d_primitives.js @@ -10,7 +10,7 @@ import * as constants from '../core/constants'; import { RendererGL } from './p5.RendererGL'; import { Vector } from '../math/p5.Vector'; import { Geometry } from './p5.Geometry'; -import { Matrix } from './p5.Matrix'; +import { Matrix } from '../math/p5.Matrix'; function primitives3D(p5, fn){ /** diff --git a/src/webgl/GeometryBuilder.js b/src/webgl/GeometryBuilder.js index 4dd66c8fdd..9d5783dbf9 100644 --- a/src/webgl/GeometryBuilder.js +++ b/src/webgl/GeometryBuilder.js @@ -1,5 +1,5 @@ import * as constants from '../core/constants'; -import { Matrix } from './p5.Matrix'; +import { Matrix } from '../math/p5.Matrix'; import { Geometry } from './p5.Geometry'; /** @@ -11,8 +11,8 @@ class GeometryBuilder { constructor(renderer) { this.renderer = renderer; renderer._pInst.push(); - this.identityMatrix = new Matrix(); - renderer.states.uModelMatrix = new Matrix(); + this.identityMatrix = new Matrix(4); + renderer.states.uModelMatrix = new Matrix(4); this.geometry = new Geometry(undefined, undefined, undefined, this.renderer); this.geometry.gid = `_p5_GeometryBuilder_${GeometryBuilder.nextGeometryId}`; GeometryBuilder.nextGeometryId++; @@ -37,7 +37,7 @@ class GeometryBuilder { if (!this.hasTransform) return normals; return normals.map( - v => this.renderer.states.uNMatrix.multiplyVec3(v) + v => this.renderer.states.uNMatrix.multiplyVec(v) // this is a vec3 ); } @@ -51,7 +51,7 @@ class GeometryBuilder { .every((v, i) => v === this.identityMatrix.mat4[i]); if (this.hasTransform) { - this.renderer.states.uNMatrix.inverseTranspose(this.renderer.states.uModelMatrix); + this.renderer.states.uNMatrix.inverseTranspose4x4(this.renderer.states.uModelMatrix); } let startIdx = this.geometry.vertices.length; diff --git a/src/webgl/index.js b/src/webgl/index.js index adcf04631c..c2515fce5a 100644 --- a/src/webgl/index.js +++ b/src/webgl/index.js @@ -6,7 +6,7 @@ import material from './material'; import text from './text'; import renderBuffer from './p5.RenderBuffer'; import quat from './p5.Quat'; -import matrix from './p5.Matrix'; +import matrix from '../math/p5.Matrix'; import geometry from './p5.Geometry'; import framebuffer from './p5.Framebuffer'; import dataArray from './p5.DataArray'; diff --git a/src/webgl/p5.Camera.js b/src/webgl/p5.Camera.js index 7a906bc83c..c85d14f335 100644 --- a/src/webgl/p5.Camera.js +++ b/src/webgl/p5.Camera.js @@ -4,7 +4,7 @@ * @requires core */ -import { Matrix } from './p5.Matrix'; +import { Matrix } from '../math/p5.Matrix'; import { Vector } from '../math/p5.Vector'; import { Quat } from './p5.Quat'; import { RendererGL } from './p5.RendererGL'; @@ -15,8 +15,8 @@ class Camera { this.cameraType = 'default'; this.useLinePerspective = true; - this.cameraMatrix = new Matrix(); - this.projMatrix = new Matrix(); + this.cameraMatrix = new Matrix(4); + this.projMatrix = new Matrix(4); this.yScale = 1; } /** @@ -1236,7 +1236,7 @@ class Camera { this.cameraNear = near; this.cameraFar = far; - this.projMatrix = Matrix.identity(); + this.projMatrix = new Matrix(4); const f = 1.0 / Math.tan(this.cameraFOV / 2); const nf = 1.0 / (this.cameraNear - this.cameraFar); @@ -1428,7 +1428,7 @@ class Camera { const tx = -(right + left) / w; const ty = -(top + bottom) / h; const tz = -(far + near) / d; - this.projMatrix = Matrix.identity(); + this.projMatrix = new Matrix(4); /* eslint-disable indent */ this.projMatrix.set(x, 0, 0, 0, 0, -y, 0, 0, @@ -1564,7 +1564,7 @@ class Camera { const ty = (top + bottom) / h; const tz = -(far + near) / d; - this.projMatrix = Matrix.identity(); + this.projMatrix = new Matrix(4); /* eslint-disable indent */ this.projMatrix.set(x, 0, 0, 0, @@ -1599,8 +1599,8 @@ class Camera { centerY -= this.eyeY; centerZ -= this.eyeZ; - const rotation = Matrix.identity(this._renderer._pInst); - rotation.rotate(this._renderer._pInst._toRadians(a), x, y, z); + const rotation = new Matrix(4); // TODO Maybe pass p5 + rotation.rotate4x4(this._renderer._pInst._toRadians(a), x, y, z); /* eslint-disable max-len */ const rotatedCenter = [ @@ -2566,12 +2566,16 @@ class Camera { // and interpolate the elements of the projection matrix. // Use logarithmic interpolation for interpolation. if (this.projMatrix.mat4[15] !== 0) { - this.projMatrix.mat4[0] = - cam0.projMatrix.mat4[0] * - Math.pow(cam1.projMatrix.mat4[0] / cam0.projMatrix.mat4[0], amt); - this.projMatrix.mat4[5] = - cam0.projMatrix.mat4[5] * - Math.pow(cam1.projMatrix.mat4[5] / cam0.projMatrix.mat4[5], amt); + this.projMatrix.setElement( + 0, + cam0.projMatrix.mat4[0] * + Math.pow(cam1.projMatrix.mat4[0] / cam0.projMatrix.mat4[0], amt) + ); + this.projMatrix.setElement( + 5, + cam0.projMatrix.mat4[5] * + Math.pow(cam1.projMatrix.mat4[5] / cam0.projMatrix.mat4[5], amt) + ); // If the camera is active, make uPMatrix reflect changes in projMatrix. if (this._isActive()) { this._renderer.states.uPMatrix.mat4 = this.projMatrix.mat4.slice(); @@ -2640,7 +2644,7 @@ class Camera { // and multiply it to mat1 from the right. // This matrix represents the difference between the two. // 'deltaRot' means 'difference of rotation matrices'. - const deltaRot = rotMat1.mult3x3(rotMat0.copy().transpose3x3()); + const deltaRot = rotMat1.mult(rotMat0.copy().transpose()); // mat1 is 3x3 // Calculate the trace and from it the cos value of the angle. // An orthogonal matrix is just an orthonormal basis. If this is not the identity @@ -2716,7 +2720,8 @@ class Camera { const ab = a * b; const bc = b * c; const ca = c * a; - const lerpedRotMat = new Matrix('mat3', [ + // 3x3 + const lerpedRotMat = new Matrix( [ cosAngle + oneMinusCosAngle * a * a, oneMinusCosAngle * ab + sinAngle * c, oneMinusCosAngle * ca - sinAngle * b, @@ -2730,12 +2735,12 @@ class Camera { // Multiply this to mat0 from left to get the interpolated front vector. // calculate newEye, newCenter with newFront vector. - lerpedRotMat.multiplyVec3(front0, newFront); + lerpedRotMat.multiplyVec(front0, newFront); // this is vec3 newEye.set(newFront).mult(ratio * lerpedDist).add(lerpedMedium); newCenter.set(newFront).mult((ratio - 1) * lerpedDist).add(lerpedMedium); - lerpedRotMat.multiplyVec3(up0, newUp); + lerpedRotMat.multiplyVec(up0, newUp); // this is vec3 // We also get the up vector in the same way and set the camera. // The eye position and center position are calculated based on the front vector. diff --git a/src/webgl/p5.Matrix.js b/src/webgl/p5.Matrix.js deleted file mode 100644 index b358dd3098..0000000000 --- a/src/webgl/p5.Matrix.js +++ /dev/null @@ -1,994 +0,0 @@ -/** - * @requires constants - * @todo see methods below needing further implementation. - * future consideration: implement SIMD optimizations - * when browser compatibility becomes available - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/ - * Reference/Global_Objects/SIMD - */ - -import { Vector } from '../math/p5.Vector'; - -let GLMAT_ARRAY_TYPE = Array; -let isMatrixArray = x => Array.isArray(x); -if (typeof Float32Array !== 'undefined') { - GLMAT_ARRAY_TYPE = Float32Array; - isMatrixArray = x => Array.isArray(x) || x instanceof Float32Array; -} - -class Matrix { - constructor(...args){ - - // This is default behavior when object - // instantiated using createMatrix() - // @todo implement createMatrix() in core/math.js - // if (args.length && args[args.length - 1] instanceof p5) { - // this.p5 = args[args.length - 1]; - // } - - if (args[0] === 'mat3') { - this.mat3 = Array.isArray(args[1]) - ? args[1] - : new GLMAT_ARRAY_TYPE([1, 0, 0, 0, 1, 0, 0, 0, 1]); - } else { - this.mat4 = Array.isArray(args[0]) - ? args[0] - : new GLMAT_ARRAY_TYPE( - [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); - } - return this; - } - - reset() { - if (this.mat3) { - this.mat3.set([1, 0, 0, 0, 1, 0, 0, 0, 1]); - } else if (this.mat4) { - this.mat4.set([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); - } - return this; - } - - /** - * Replace the entire contents of a 4x4 matrix. - * If providing an array or a p5.Matrix, the values will be copied without - * referencing the source object. - * Can also provide 16 numbers as individual arguments. - * - * @param {p5.Matrix|Float32Array|Number[]} [inMatrix] the input p5.Matrix or - * an Array of length 16 - * @chainable - */ - /** - * @param {Number[]} elements 16 numbers passed by value to avoid - * array copying. - * @chainable - */ - set(inMatrix) { - let refArray = arguments; - if (inMatrix instanceof Matrix) { - refArray = inMatrix.mat4; - } else if (isMatrixArray(inMatrix)) { - refArray = inMatrix; - } - if (refArray.length !== 16) { - p5._friendlyError( - `Expected 16 values but received ${refArray.length}.`, - 'p5.Matrix.set' - ); - return this; - } - for (let i = 0; i < 16; i++) { - this.mat4[i] = refArray[i]; - } - return this; - } - - /** - * Gets a copy of the vector, returns a p5.Matrix object. - * - * @return {p5.Matrix} the copy of the p5.Matrix object - */ - get() { - return new Matrix(this.mat4, this.p5); - } - - /** - * return a copy of this matrix. - * If this matrix is 4x4, a 4x4 matrix with exactly the same entries will be - * generated. The same is true if this matrix is 3x3. - * - * @return {p5.Matrix} the result matrix - */ - copy() { - if (this.mat3 !== undefined) { - const copied3x3 = new Matrix('mat3', this.p5); - copied3x3.mat3[0] = this.mat3[0]; - copied3x3.mat3[1] = this.mat3[1]; - copied3x3.mat3[2] = this.mat3[2]; - copied3x3.mat3[3] = this.mat3[3]; - copied3x3.mat3[4] = this.mat3[4]; - copied3x3.mat3[5] = this.mat3[5]; - copied3x3.mat3[6] = this.mat3[6]; - copied3x3.mat3[7] = this.mat3[7]; - copied3x3.mat3[8] = this.mat3[8]; - return copied3x3; - } - const copied = new Matrix(this.p5); - copied.mat4[0] = this.mat4[0]; - copied.mat4[1] = this.mat4[1]; - copied.mat4[2] = this.mat4[2]; - copied.mat4[3] = this.mat4[3]; - copied.mat4[4] = this.mat4[4]; - copied.mat4[5] = this.mat4[5]; - copied.mat4[6] = this.mat4[6]; - copied.mat4[7] = this.mat4[7]; - copied.mat4[8] = this.mat4[8]; - copied.mat4[9] = this.mat4[9]; - copied.mat4[10] = this.mat4[10]; - copied.mat4[11] = this.mat4[11]; - copied.mat4[12] = this.mat4[12]; - copied.mat4[13] = this.mat4[13]; - copied.mat4[14] = this.mat4[14]; - copied.mat4[15] = this.mat4[15]; - return copied; - } - - clone() { - return this.copy(); - } - - /** - * return an identity matrix - * @return {p5.Matrix} the result matrix - */ - static identity(pInst){ - return new Matrix(pInst); - } - - /** - * transpose according to a given matrix - * @param {p5.Matrix|Float32Array|Number[]} a the matrix to be - * based on to transpose - * @chainable - */ - transpose(a) { - let a01, a02, a03, a12, a13, a23; - if (a instanceof Matrix) { - a01 = a.mat4[1]; - a02 = a.mat4[2]; - a03 = a.mat4[3]; - a12 = a.mat4[6]; - a13 = a.mat4[7]; - a23 = a.mat4[11]; - - this.mat4[0] = a.mat4[0]; - this.mat4[1] = a.mat4[4]; - this.mat4[2] = a.mat4[8]; - this.mat4[3] = a.mat4[12]; - this.mat4[4] = a01; - this.mat4[5] = a.mat4[5]; - this.mat4[6] = a.mat4[9]; - this.mat4[7] = a.mat4[13]; - this.mat4[8] = a02; - this.mat4[9] = a12; - this.mat4[10] = a.mat4[10]; - this.mat4[11] = a.mat4[14]; - this.mat4[12] = a03; - this.mat4[13] = a13; - this.mat4[14] = a23; - this.mat4[15] = a.mat4[15]; - } else if (isMatrixArray(a)) { - a01 = a[1]; - a02 = a[2]; - a03 = a[3]; - a12 = a[6]; - a13 = a[7]; - a23 = a[11]; - - this.mat4[0] = a[0]; - this.mat4[1] = a[4]; - this.mat4[2] = a[8]; - this.mat4[3] = a[12]; - this.mat4[4] = a01; - this.mat4[5] = a[5]; - this.mat4[6] = a[9]; - this.mat4[7] = a[13]; - this.mat4[8] = a02; - this.mat4[9] = a12; - this.mat4[10] = a[10]; - this.mat4[11] = a[14]; - this.mat4[12] = a03; - this.mat4[13] = a13; - this.mat4[14] = a23; - this.mat4[15] = a[15]; - } - return this; - } - - /** - * invert matrix according to a give matrix - * @param {p5.Matrix|Float32Array|Number[]} a the matrix to be - * based on to invert - * @chainable - */ - invert(a) { - let a00, a01, a02, a03, a10, a11, a12, a13; - let a20, a21, a22, a23, a30, a31, a32, a33; - if (a instanceof Matrix) { - a00 = a.mat4[0]; - a01 = a.mat4[1]; - a02 = a.mat4[2]; - a03 = a.mat4[3]; - a10 = a.mat4[4]; - a11 = a.mat4[5]; - a12 = a.mat4[6]; - a13 = a.mat4[7]; - a20 = a.mat4[8]; - a21 = a.mat4[9]; - a22 = a.mat4[10]; - a23 = a.mat4[11]; - a30 = a.mat4[12]; - a31 = a.mat4[13]; - a32 = a.mat4[14]; - a33 = a.mat4[15]; - } else if (isMatrixArray(a)) { - 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]; - a30 = a[12]; - a31 = a[13]; - a32 = a[14]; - a33 = a[15]; - } - const b00 = a00 * a11 - a01 * a10; - const b01 = a00 * a12 - a02 * a10; - const b02 = a00 * a13 - a03 * a10; - const b03 = a01 * a12 - a02 * a11; - const b04 = a01 * a13 - a03 * a11; - const b05 = a02 * a13 - a03 * a12; - const b06 = a20 * a31 - a21 * a30; - const b07 = a20 * a32 - a22 * a30; - const b08 = a20 * a33 - a23 * a30; - const b09 = a21 * a32 - a22 * a31; - const b10 = a21 * a33 - a23 * a31; - const b11 = a22 * a33 - a23 * a32; - - // Calculate the determinant - let det = - b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; - - if (!det) { - return null; - } - det = 1.0 / det; - - this.mat4[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; - this.mat4[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; - this.mat4[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; - this.mat4[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; - this.mat4[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; - this.mat4[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; - this.mat4[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; - this.mat4[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; - this.mat4[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; - this.mat4[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; - this.mat4[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; - this.mat4[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; - this.mat4[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; - this.mat4[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; - this.mat4[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; - this.mat4[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; - - return this; - } - - /** - * Inverts a 3×3 matrix - * @chainable - */ - invert3x3() { - const a00 = this.mat3[0]; - const a01 = this.mat3[1]; - const a02 = this.mat3[2]; - const a10 = this.mat3[3]; - const a11 = this.mat3[4]; - const a12 = this.mat3[5]; - const a20 = this.mat3[6]; - const a21 = this.mat3[7]; - const a22 = this.mat3[8]; - const b01 = a22 * a11 - a12 * a21; - const b11 = -a22 * a10 + a12 * a20; - const b21 = a21 * a10 - a11 * a20; - - // Calculate the determinant - let det = a00 * b01 + a01 * b11 + a02 * b21; - if (!det) { - return null; - } - det = 1.0 / det; - this.mat3[0] = b01 * det; - this.mat3[1] = (-a22 * a01 + a02 * a21) * det; - this.mat3[2] = (a12 * a01 - a02 * a11) * det; - this.mat3[3] = b11 * det; - this.mat3[4] = (a22 * a00 - a02 * a20) * det; - this.mat3[5] = (-a12 * a00 + a02 * a10) * det; - this.mat3[6] = b21 * det; - this.mat3[7] = (-a21 * a00 + a01 * a20) * det; - this.mat3[8] = (a11 * a00 - a01 * a10) * det; - return this; - } - - /** - * This function is only for 3x3 matrices. - * transposes a 3×3 p5.Matrix by a mat3 - * If there is an array of arguments, the matrix obtained by transposing - * the 3x3 matrix generated based on that array is set. - * If no arguments, it transposes itself and returns it. - * - * @param {Number[]} mat3 1-dimensional array - * @chainable - */ - transpose3x3(mat3) { - if (mat3 === undefined) { - mat3 = this.mat3; - } - const a01 = mat3[1]; - const a02 = mat3[2]; - const a12 = mat3[5]; - this.mat3[0] = mat3[0]; - this.mat3[1] = mat3[3]; - this.mat3[2] = mat3[6]; - this.mat3[3] = a01; - this.mat3[4] = mat3[4]; - this.mat3[5] = mat3[7]; - this.mat3[6] = a02; - this.mat3[7] = a12; - this.mat3[8] = mat3[8]; - - return this; - } - - /** - * converts a 4×4 matrix to its 3×3 inverse transform - * commonly used in MVMatrix to NMatrix conversions. - * @param {p5.Matrix} mat4 the matrix to be based on to invert - * @chainable - * @todo finish implementation - */ - inverseTranspose({ mat4 }) { - if (this.mat3 === undefined) { - p5._friendlyError('sorry, this function only works with mat3'); - } else { - //convert mat4 -> mat3 - this.mat3[0] = mat4[0]; - this.mat3[1] = mat4[1]; - this.mat3[2] = mat4[2]; - this.mat3[3] = mat4[4]; - this.mat3[4] = mat4[5]; - this.mat3[5] = mat4[6]; - this.mat3[6] = mat4[8]; - this.mat3[7] = mat4[9]; - this.mat3[8] = mat4[10]; - } - - const inverse = this.invert3x3(); - // check inverse succeeded - if (inverse) { - inverse.transpose3x3(this.mat3); - } else { - // in case of singularity, just zero the matrix - for (let i = 0; i < 9; i++) { - this.mat3[i] = 0; - } - } - return this; - } - - /** - * inspired by Toji's mat4 determinant - * @return {Number} Determinant of our 4×4 matrix - */ - determinant() { - const d00 = this.mat4[0] * this.mat4[5] - this.mat4[1] * this.mat4[4], - d01 = this.mat4[0] * this.mat4[6] - this.mat4[2] * this.mat4[4], - d02 = this.mat4[0] * this.mat4[7] - this.mat4[3] * this.mat4[4], - d03 = this.mat4[1] * this.mat4[6] - this.mat4[2] * this.mat4[5], - d04 = this.mat4[1] * this.mat4[7] - this.mat4[3] * this.mat4[5], - d05 = this.mat4[2] * this.mat4[7] - this.mat4[3] * this.mat4[6], - d06 = this.mat4[8] * this.mat4[13] - this.mat4[9] * this.mat4[12], - d07 = this.mat4[8] * this.mat4[14] - this.mat4[10] * this.mat4[12], - d08 = this.mat4[8] * this.mat4[15] - this.mat4[11] * this.mat4[12], - d09 = this.mat4[9] * this.mat4[14] - this.mat4[10] * this.mat4[13], - d10 = this.mat4[9] * this.mat4[15] - this.mat4[11] * this.mat4[13], - d11 = this.mat4[10] * this.mat4[15] - this.mat4[11] * this.mat4[14]; - - // Calculate the determinant - return d00 * d11 - d01 * d10 + d02 * d09 + - d03 * d08 - d04 * d07 + d05 * d06; - } - - /** - * multiply two mat4s - * @param {p5.Matrix|Float32Array|Number[]} multMatrix The matrix - * we want to multiply by - * @chainable - */ - mult(multMatrix) { - let _src; - - if (multMatrix === this || multMatrix === this.mat4) { - _src = this.copy().mat4; // only need to allocate in this rare case - } else if (multMatrix instanceof Matrix) { - _src = multMatrix.mat4; - } else if (isMatrixArray(multMatrix)) { - _src = multMatrix; - } else if (arguments.length === 16) { - _src = arguments; - } else { - return; // nothing to do. - } - - // each row is used for the multiplier - let b0 = this.mat4[0], - b1 = this.mat4[1], - b2 = this.mat4[2], - b3 = this.mat4[3]; - this.mat4[0] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12]; - this.mat4[1] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13]; - this.mat4[2] = b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14]; - this.mat4[3] = b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15]; - - b0 = this.mat4[4]; - b1 = this.mat4[5]; - b2 = this.mat4[6]; - b3 = this.mat4[7]; - this.mat4[4] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12]; - this.mat4[5] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13]; - this.mat4[6] = b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14]; - this.mat4[7] = b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15]; - - b0 = this.mat4[8]; - b1 = this.mat4[9]; - b2 = this.mat4[10]; - b3 = this.mat4[11]; - this.mat4[8] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12]; - this.mat4[9] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13]; - this.mat4[10] = b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14]; - this.mat4[11] = b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15]; - - b0 = this.mat4[12]; - b1 = this.mat4[13]; - b2 = this.mat4[14]; - b3 = this.mat4[15]; - this.mat4[12] = b0 * _src[0] + b1 * _src[4] + b2 * _src[8] + b3 * _src[12]; - this.mat4[13] = b0 * _src[1] + b1 * _src[5] + b2 * _src[9] + b3 * _src[13]; - this.mat4[14] = b0 * _src[2] + b1 * _src[6] + b2 * _src[10] + b3 * _src[14]; - this.mat4[15] = b0 * _src[3] + b1 * _src[7] + b2 * _src[11] + b3 * _src[15]; - - return this; - } - - apply(multMatrix) { - let _src; - - if (multMatrix === this || multMatrix === this.mat4) { - _src = this.copy().mat4; // only need to allocate in this rare case - } else if (multMatrix instanceof Matrix) { - _src = multMatrix.mat4; - } else if (isMatrixArray(multMatrix)) { - _src = multMatrix; - } else if (arguments.length === 16) { - _src = arguments; - } else { - return; // nothing to do. - } - - const mat4 = this.mat4; - - // each row is used for the multiplier - const m0 = mat4[0]; - const m4 = mat4[4]; - const m8 = mat4[8]; - const m12 = mat4[12]; - mat4[0] = _src[0] * m0 + _src[1] * m4 + _src[2] * m8 + _src[3] * m12; - mat4[4] = _src[4] * m0 + _src[5] * m4 + _src[6] * m8 + _src[7] * m12; - mat4[8] = _src[8] * m0 + _src[9] * m4 + _src[10] * m8 + _src[11] * m12; - mat4[12] = _src[12] * m0 + _src[13] * m4 + _src[14] * m8 + _src[15] * m12; - - const m1 = mat4[1]; - const m5 = mat4[5]; - const m9 = mat4[9]; - const m13 = mat4[13]; - mat4[1] = _src[0] * m1 + _src[1] * m5 + _src[2] * m9 + _src[3] * m13; - mat4[5] = _src[4] * m1 + _src[5] * m5 + _src[6] * m9 + _src[7] * m13; - mat4[9] = _src[8] * m1 + _src[9] * m5 + _src[10] * m9 + _src[11] * m13; - mat4[13] = _src[12] * m1 + _src[13] * m5 + _src[14] * m9 + _src[15] * m13; - - const m2 = mat4[2]; - const m6 = mat4[6]; - const m10 = mat4[10]; - const m14 = mat4[14]; - mat4[2] = _src[0] * m2 + _src[1] * m6 + _src[2] * m10 + _src[3] * m14; - mat4[6] = _src[4] * m2 + _src[5] * m6 + _src[6] * m10 + _src[7] * m14; - mat4[10] = _src[8] * m2 + _src[9] * m6 + _src[10] * m10 + _src[11] * m14; - mat4[14] = _src[12] * m2 + _src[13] * m6 + _src[14] * m10 + _src[15] * m14; - - const m3 = mat4[3]; - const m7 = mat4[7]; - const m11 = mat4[11]; - const m15 = mat4[15]; - mat4[3] = _src[0] * m3 + _src[1] * m7 + _src[2] * m11 + _src[3] * m15; - mat4[7] = _src[4] * m3 + _src[5] * m7 + _src[6] * m11 + _src[7] * m15; - mat4[11] = _src[8] * m3 + _src[9] * m7 + _src[10] * m11 + _src[11] * m15; - mat4[15] = _src[12] * m3 + _src[13] * m7 + _src[14] * m11 + _src[15] * m15; - - return this; - } - - /** - * scales a p5.Matrix by scalars or a vector - * @param {p5.Vector|Float32Array|Number[]} s vector to scale by - * @chainable - */ - scale(x, y, z) { - if (x instanceof Vector) { - // x is a vector, extract the components from it. - y = x.y; - z = x.z; - x = x.x; // must be last - } else if (x instanceof Array) { - // x is an array, extract the components from it. - y = x[1]; - z = x[2]; - x = x[0]; // must be last - } - - this.mat4[0] *= x; - this.mat4[1] *= x; - this.mat4[2] *= x; - this.mat4[3] *= x; - this.mat4[4] *= y; - this.mat4[5] *= y; - this.mat4[6] *= y; - this.mat4[7] *= y; - this.mat4[8] *= z; - this.mat4[9] *= z; - this.mat4[10] *= z; - this.mat4[11] *= z; - - return this; - } - - /** - * rotate our Matrix around an axis by the given angle. - * @param {Number} a The angle of rotation in radians - * @param {p5.Vector|Number[]} axis the axis(es) to rotate around - * @chainable - * inspired by Toji's gl-matrix lib, mat4 rotation - */ - rotate(a, x, y, z) { - if (x instanceof Vector) { - // x is a vector, extract the components from it. - y = x.y; - z = x.z; - x = x.x; //must be last - } else if (x instanceof Array) { - // x is an array, extract the components from it. - y = x[1]; - z = x[2]; - x = x[0]; //must be last - } - - const len = Math.sqrt(x * x + y * y + z * z); - x *= 1 / len; - y *= 1 / len; - z *= 1 / len; - - const a00 = this.mat4[0]; - const a01 = this.mat4[1]; - const a02 = this.mat4[2]; - const a03 = this.mat4[3]; - const a10 = this.mat4[4]; - const a11 = this.mat4[5]; - const a12 = this.mat4[6]; - const a13 = this.mat4[7]; - const a20 = this.mat4[8]; - const a21 = this.mat4[9]; - const a22 = this.mat4[10]; - const a23 = this.mat4[11]; - - //sin,cos, and tan of respective angle - const sA = Math.sin(a); - const cA = Math.cos(a); - const tA = 1 - cA; - // Construct the elements of the rotation matrix - const b00 = x * x * tA + cA; - const b01 = y * x * tA + z * sA; - const b02 = z * x * tA - y * sA; - const b10 = x * y * tA - z * sA; - const b11 = y * y * tA + cA; - const b12 = z * y * tA + x * sA; - const b20 = x * z * tA + y * sA; - const b21 = y * z * tA - x * sA; - const b22 = z * z * tA + cA; - - // rotation-specific matrix multiplication - this.mat4[0] = a00 * b00 + a10 * b01 + a20 * b02; - this.mat4[1] = a01 * b00 + a11 * b01 + a21 * b02; - this.mat4[2] = a02 * b00 + a12 * b01 + a22 * b02; - this.mat4[3] = a03 * b00 + a13 * b01 + a23 * b02; - this.mat4[4] = a00 * b10 + a10 * b11 + a20 * b12; - this.mat4[5] = a01 * b10 + a11 * b11 + a21 * b12; - this.mat4[6] = a02 * b10 + a12 * b11 + a22 * b12; - this.mat4[7] = a03 * b10 + a13 * b11 + a23 * b12; - this.mat4[8] = a00 * b20 + a10 * b21 + a20 * b22; - this.mat4[9] = a01 * b20 + a11 * b21 + a21 * b22; - this.mat4[10] = a02 * b20 + a12 * b21 + a22 * b22; - this.mat4[11] = a03 * b20 + a13 * b21 + a23 * b22; - - return this; - } - - /** - * @todo finish implementing this method! - * translates - * @param {Number[]} v vector to translate by - * @chainable - */ - translate(v) { - const x = v[0], - y = v[1], - z = v[2] || 0; - this.mat4[12] += this.mat4[0] * x + this.mat4[4] * y + this.mat4[8] * z; - this.mat4[13] += this.mat4[1] * x + this.mat4[5] * y + this.mat4[9] * z; - this.mat4[14] += this.mat4[2] * x + this.mat4[6] * y + this.mat4[10] * z; - this.mat4[15] += this.mat4[3] * x + this.mat4[7] * y + this.mat4[11] * z; - } - - rotateX(a) { - this.rotate(a, 1, 0, 0); - } - rotateY(a) { - this.rotate(a, 0, 1, 0); - } - rotateZ(a) { - this.rotate(a, 0, 0, 1); - } - - /** - * sets the perspective matrix - * @param {Number} fovy [description] - * @param {Number} aspect [description] - * @param {Number} near near clipping plane - * @param {Number} far far clipping plane - * @chainable - */ - perspective(fovy, aspect, near, far) { - const f = 1.0 / Math.tan(fovy / 2), - nf = 1 / (near - far); - - this.mat4[0] = f / aspect; - this.mat4[1] = 0; - this.mat4[2] = 0; - this.mat4[3] = 0; - this.mat4[4] = 0; - this.mat4[5] = f; - this.mat4[6] = 0; - this.mat4[7] = 0; - this.mat4[8] = 0; - this.mat4[9] = 0; - this.mat4[10] = (far + near) * nf; - this.mat4[11] = -1; - this.mat4[12] = 0; - this.mat4[13] = 0; - this.mat4[14] = 2 * far * near * nf; - this.mat4[15] = 0; - - return this; - } - - /** - * sets the ortho matrix - * @param {Number} left [description] - * @param {Number} right [description] - * @param {Number} bottom [description] - * @param {Number} top [description] - * @param {Number} near near clipping plane - * @param {Number} far far clipping plane - * @chainable - */ - ortho(left, right, bottom, top, near, far) { - const lr = 1 / (left - right), - bt = 1 / (bottom - top), - nf = 1 / (near - far); - this.mat4[0] = -2 * lr; - this.mat4[1] = 0; - this.mat4[2] = 0; - this.mat4[3] = 0; - this.mat4[4] = 0; - this.mat4[5] = -2 * bt; - this.mat4[6] = 0; - this.mat4[7] = 0; - this.mat4[8] = 0; - this.mat4[9] = 0; - this.mat4[10] = 2 * nf; - this.mat4[11] = 0; - this.mat4[12] = (left + right) * lr; - this.mat4[13] = (top + bottom) * bt; - this.mat4[14] = (far + near) * nf; - this.mat4[15] = 1; - - return this; - } - - /** - * apply a matrix to a vector with x,y,z,w components - * get the results in the form of an array - * @param {Number} - * @return {Number[]} - */ - multiplyVec4(x, y, z, w) { - const result = new Array(4); - const m = this.mat4; - - result[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w; - result[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w; - result[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w; - result[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w; - - return result; - } - - /** - * Applies a matrix to a vector. - * The fourth component is set to 1. - * Returns a vector consisting of the first - * through third components of the result. - * - * @param {p5.Vector} - * @return {p5.Vector} - */ - multiplyPoint({ x, y, z }) { - const array = this.multiplyVec4(x, y, z, 1); - return new Vector(array[0], array[1], array[2]); - } - - /** - * Applies a matrix to a vector. - * The fourth component is set to 1. - * Returns the result of dividing the 1st to 3rd components - * of the result by the 4th component as a vector. - * - * @param {p5.Vector} - * @return {p5.Vector} - */ - multiplyAndNormalizePoint({ x, y, z }) { - const array = this.multiplyVec4(x, y, z, 1); - array[0] /= array[3]; - array[1] /= array[3]; - array[2] /= array[3]; - return new Vector(array[0], array[1], array[2]); - } - - /** - * Applies a matrix to a vector. - * The fourth component is set to 0. - * Returns a vector consisting of the first - * through third components of the result. - * - * @param {p5.Vector} - * @return {p5.Vector} - */ - multiplyDirection({ x, y, z }) { - const array = this.multiplyVec4(x, y, z, 0); - return new Vector(array[0], array[1], array[2]); - } - - /** - * This function is only for 3x3 matrices. - * multiply two mat3s. It is an operation to multiply the 3x3 matrix of - * the argument from the right. Arguments can be a 3x3 p5.Matrix, - * a Float32Array of length 9, or a javascript array of length 9. - * In addition, it can also be done by enumerating 9 numbers. - * - * @param {p5.Matrix|Float32Array|Number[]} multMatrix The matrix - * we want to multiply by - * @chainable - */ - mult3x3(multMatrix) { - let _src; - - if (multMatrix === this || multMatrix === this.mat3) { - _src = this.copy().mat3; // only need to allocate in this rare case - } else if (multMatrix instanceof Matrix) { - _src = multMatrix.mat3; - } else if (isMatrixArray(multMatrix)) { - _src = multMatrix; - } else if (arguments.length === 9) { - _src = arguments; - } else { - return; // nothing to do. - } - - // each row is used for the multiplier - let b0 = this.mat3[0]; - let b1 = this.mat3[1]; - let b2 = this.mat3[2]; - this.mat3[0] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6]; - this.mat3[1] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7]; - this.mat3[2] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8]; - - b0 = this.mat3[3]; - b1 = this.mat3[4]; - b2 = this.mat3[5]; - this.mat3[3] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6]; - this.mat3[4] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7]; - this.mat3[5] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8]; - - b0 = this.mat3[6]; - b1 = this.mat3[7]; - b2 = this.mat3[8]; - this.mat3[6] = b0 * _src[0] + b1 * _src[3] + b2 * _src[6]; - this.mat3[7] = b0 * _src[1] + b1 * _src[4] + b2 * _src[7]; - this.mat3[8] = b0 * _src[2] + b1 * _src[5] + b2 * _src[8]; - - return this; - } - - /** - * This function is only for 3x3 matrices. - * A function that returns a column vector of a 3x3 matrix. - * - * @param {Number} columnIndex matrix column number - * @return {p5.Vector} - */ - column(columnIndex) { - return new Vector( - this.mat3[3 * columnIndex], - this.mat3[3 * columnIndex + 1], - this.mat3[3 * columnIndex + 2] - ); - } - - /** - * This function is only for 3x3 matrices. - * A function that returns a row vector of a 3x3 matrix. - * - * @param {Number} rowIndex matrix row number - * @return {p5.Vector} - */ - row(rowIndex) { - return new Vector( - this.mat3[rowIndex], - this.mat3[rowIndex + 3], - this.mat3[rowIndex + 6] - ); - } - - /** - * Returns the diagonal elements of the matrix in the form of an array. - * A 3x3 matrix will return an array of length 3. - * A 4x4 matrix will return an array of length 4. - * - * @return {Number[]} An array obtained by arranging the diagonal elements - * of the matrix in ascending order of index - */ - diagonal() { - if (this.mat3 !== undefined) { - return [this.mat3[0], this.mat3[4], this.mat3[8]]; - } - return [this.mat4[0], this.mat4[5], this.mat4[10], this.mat4[15]]; - } - - /** - * This function is only for 3x3 matrices. - * Takes a vector and returns the vector resulting from multiplying to - * that vector by this matrix from left. - * - * @param {p5.Vector} multVector the vector to which this matrix applies - * @param {p5.Vector} [target] The vector to receive the result - * @return {p5.Vector} - */ - multiplyVec3(multVector, target) { - if (target === undefined) { - target = multVector.copy(); - } - target.x = this.row(0).dot(multVector); - target.y = this.row(1).dot(multVector); - target.z = this.row(2).dot(multVector); - return target; - } - - /** - * This function is only for 4x4 matrices. - * Creates a 3x3 matrix whose entries are the top left 3x3 part and returns it. - * - * @return {p5.Matrix} - */ - createSubMatrix3x3() { - const result = new Matrix('mat3'); - result.mat3[0] = this.mat4[0]; - result.mat3[1] = this.mat4[1]; - result.mat3[2] = this.mat4[2]; - result.mat3[3] = this.mat4[4]; - result.mat3[4] = this.mat4[5]; - result.mat3[5] = this.mat4[6]; - result.mat3[6] = this.mat4[8]; - result.mat3[7] = this.mat4[9]; - result.mat3[8] = this.mat4[10]; - return result; - } - - /** - * PRIVATE - */ - // matrix methods adapted from: - // https://developer.mozilla.org/en-US/docs/Web/WebGL/ - // gluPerspective - // - // function _makePerspective(fovy, aspect, znear, zfar){ - // const ymax = znear * Math.tan(fovy * Math.PI / 360.0); - // const ymin = -ymax; - // const xmin = ymin * aspect; - // const xmax = ymax * aspect; - // return _makeFrustum(xmin, xmax, ymin, ymax, znear, zfar); - // } - - //// - //// glFrustum - //// - //function _makeFrustum(left, right, bottom, top, znear, zfar){ - // const X = 2*znear/(right-left); - // const Y = 2*znear/(top-bottom); - // const A = (right+left)/(right-left); - // const B = (top+bottom)/(top-bottom); - // const C = -(zfar+znear)/(zfar-znear); - // const D = -2*zfar*znear/(zfar-znear); - // const frustrumMatrix =[ - // X, 0, A, 0, - // 0, Y, B, 0, - // 0, 0, C, D, - // 0, 0, -1, 0 - //]; - //return frustrumMatrix; - // } - -// function _setMVPMatrices(){ -////an identity matrix -////@TODO use the p5.Matrix class to abstract away our MV matrices and -///other math -//const _mvMatrix = -//[ -// 1.0,0.0,0.0,0.0, -// 0.0,1.0,0.0,0.0, -// 0.0,0.0,1.0,0.0, -// 0.0,0.0,0.0,1.0 -//]; -}; - -function matrix(p5, fn){ - /** - * A class to describe a 4×4 matrix - * for model and view matrix manipulation in the p5js webgl renderer. - * @class p5.Matrix - * @private - * @param {Array} [mat4] column-major array literal of our 4×4 matrix - */ - p5.Matrix = Matrix -} - -export default matrix; -export { Matrix }; - -if(typeof p5 !== 'undefined'){ - matrix(p5, p5.prototype); -} diff --git a/src/webgl/p5.RendererGL.js b/src/webgl/p5.RendererGL.js index 6756ebc62d..c2d97f1a01 100644 --- a/src/webgl/p5.RendererGL.js +++ b/src/webgl/p5.RendererGL.js @@ -1,7 +1,7 @@ import * as constants from "../core/constants"; import GeometryBuilder from "./GeometryBuilder"; import { Renderer } from "../core/p5.Renderer"; -import { Matrix } from "./p5.Matrix"; +import { Matrix } from "../math/p5.Matrix"; import { Camera } from "./p5.Camera"; import { Vector } from "../math/p5.Vector"; import { RenderBuffer } from "./p5.RenderBuffer"; @@ -175,12 +175,12 @@ class RendererGL extends Renderer { this.geometryBuilder = undefined; // Push/pop state - this.states.uModelMatrix = new Matrix(); - this.states.uViewMatrix = new Matrix(); - this.states.uMVMatrix = new Matrix(); - this.states.uPMatrix = new Matrix(); - this.states.uNMatrix = new Matrix("mat3"); - this.states.curMatrix = new Matrix("mat3"); + this.states.uModelMatrix = new Matrix(4); + this.states.uViewMatrix = new Matrix(4); + this.states.uMVMatrix = new Matrix(4); + this.states.uPMatrix = new Matrix(4); + this.states.uNMatrix = new Matrix(3); + this.states.curMatrix = new Matrix(3); this.states.curCamera = new Camera(this); @@ -1609,6 +1609,7 @@ class RendererGL extends Renderer { applyMatrix(a, b, c, d, e, f) { if (arguments.length === 16) { + // this.states.uModelMatrix.apply(arguments); Matrix.prototype.apply.apply(this.states.uModelMatrix, arguments); } else { this.states.uModelMatrix.apply([ @@ -1668,7 +1669,7 @@ class RendererGL extends Renderer { if (typeof axis === "undefined") { return this.rotateZ(rad); } - Matrix.prototype.rotate.apply(this.states.uModelMatrix, arguments); + Matrix.prototype.rotate4x4.apply(this.states.uModelMatrix, arguments); return this; } @@ -1738,14 +1739,11 @@ class RendererGL extends Renderer { if (!this.sphereMapping) { this.sphereMapping = this._pInst.createFilterShader(sphereMapping); } - this.states.uNMatrix.inverseTranspose(this.states.uViewMatrix); - this.states.uNMatrix.invert3x3(this.states.uNMatrix); + this.states.uNMatrix.inverseTranspose4x4(this.states.uViewMatrix); + this.states.uNMatrix.invert(this.states.uNMatrix); // uNMMatrix is 3x3 this.sphereMapping.setUniform("uFovY", this.states.curCamera.cameraFOV); this.sphereMapping.setUniform("uAspect", this.states.curCamera.aspectRatio); - this.sphereMapping.setUniform( - "uNewNormalMatrix", - this.states.uNMatrix.mat3, - ); + this.sphereMapping.setUniform("uNewNormalMatrix", this.states.uNMatrix.mat3); this.sphereMapping.setUniform("uSampler", img); return this.sphereMapping; } @@ -2212,11 +2210,11 @@ class RendererGL extends Renderer { modelViewProjectionMatrix.mat4, ); if (shader.uniforms.uNormalMatrix) { - this.states.uNMatrix.inverseTranspose(this.states.uMVMatrix); + this.states.uNMatrix.inverseTranspose4x4(this.states.uMVMatrix); shader.setUniform("uNormalMatrix", this.states.uNMatrix.mat3); } if (shader.uniforms.uCameraRotation) { - this.states.curMatrix.inverseTranspose(this.states.uViewMatrix); + this.states.curMatrix.inverseTranspose4x4(this.states.uViewMatrix); shader.setUniform("uCameraRotation", this.states.curMatrix.mat3); } shader.setUniform("uViewport", this._viewport); diff --git a/test/unit/core/param_errors.js b/test/unit/core/param_errors.js index 6456ceb6d3..03fe4e5117 100644 --- a/test/unit/core/param_errors.js +++ b/test/unit/core/param_errors.js @@ -195,7 +195,7 @@ suite('Validate Params', function () { }); }); - suite('validateParams: web API objects', function () { + suite('validateParams: web API objects', function () { // TODO: fix this p5 error const audioContext = new AudioContext(); const gainNode = audioContext.createGain(); diff --git a/test/unit/math/p5.Matrix.js b/test/unit/math/p5.Matrix.js new file mode 100644 index 0000000000..51e684db18 --- /dev/null +++ b/test/unit/math/p5.Matrix.js @@ -0,0 +1,528 @@ +import { describe, it, expect, beforeAll, afterAll, test } from "vitest"; +import p5 from "../../../src/app.js"; + +const toArray = (typedArray) => Array.from(typedArray); +/* eslint-disable indent */ +var mat4 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + +var other = [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16]; + +var mat3 = [1, 2, 3, 4, 5, 6, 7, 8, 9]; +/* eslint-enable indent */ + +suite("p5.Matrix", function () { + var myp5; + + beforeAll(function () { + new p5(function (p) { + p.setup = function () { + myp5 = p; + }; + }); + }); + + afterAll(function () { + myp5.remove(); + }); + + suite("construction", function () { + test("new p5.Matrix(4)", function () { + var m = new p5.Matrix(4); + assert.instanceOf(m, p5.Matrix); + assert.isUndefined(m.mat3); + /* eslint-disable indent */ + assert.deepEqual( + [].slice.call(m.mat4), + [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + ); + /* eslint-enable indent */ + }); + + test("new p5.Matrix(array)", function () { + var m = new p5.Matrix(mat4); + assert.instanceOf(m, p5.Matrix); + assert.isUndefined(m.mat3); + expect(m.mat4).toEqual(mat4); + }); + + test("new p5.Matrix(mat3)", function () { + var m = new p5.Matrix(mat3); + assert.instanceOf(m, p5.Matrix); + assert.isUndefined(m.mat4); + assert.deepEqual([].slice.call(m.mat3), mat3); + }); + + test("identity()", function () { + var m = new p5.Matrix(4); + assert.instanceOf(m, p5.Matrix); + assert.isUndefined(m.mat3); + /* eslint-disable indent */ + expect(toArray(m.mat4)).toEqual([ + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, + ]); + /* eslint-enable indent */ + }); + }); + + describe("reset", function () { + it("should reset a 4x4 matrix to the identity matrix", function () { + const m = new p5.Matrix([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + ]); + m.reset(); + expect(toArray(m.mat4)).toEqual([ + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, + ]); + }); + + it("should reset a 3x3 matrix to the identity matrix", function () { + const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + m.reset(); + expect(toArray(m.mat3)).toEqual([1, 0, 0, 0, 1, 0, 0, 0, 1]); + }); + }); + + suite("set", function () { + test("p5.Matrix", function () { + var m = new p5.Matrix(4); + m.set(new p5.Matrix(mat4)); + expect(m.mat4).toEqual(mat4); + // assert.deepEqual([].slice.call(m.mat4), mat4); + }); + + test("array", function () { + var m = new p5.Matrix(4); + m.set(mat4); + assert.deepEqual([].slice.call(m.mat4), mat4); + }); + + test("arguments", function () { + var m = new p5.Matrix(4); + m.set(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6); + expect(m.mat4).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6]); + }); + }); + + it("should clone a 4x4 matrix correctly", () => { + const original = new p5.Matrix([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + ]); + const clone = original.clone(); + + expect(clone).not.toBe(original); + expect(toArray(clone.mat4)).toEqual(toArray(original.mat4)); + }); + + it("should clone a 3x3 matrix correctly", () => { + const original = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + const clone = original.clone(); + + expect(clone).not.toBe(original); + expect(toArray(clone.mat3)).toEqual(original.mat3); + }); + + it("should clone an identity matrix correctly", () => { + const original = new p5.Matrix(4); + const clone = original.clone(); + + expect(clone).not.toBe(original); + expect(toArray(clone.mat4)).toEqual(toArray(original.mat4)); + }); + + suite("get / copy", function () { + test("get", function () { + var m = new p5.Matrix(mat4); + var m2 = m.get(); + assert.notEqual(m, m2); + expect(m.mat4).toEqual(m2.mat4); + }); + test("copy", function () { + var m = new p5.Matrix(mat4); + var m2 = m.copy(); + assert.notEqual(m, m2); + assert.notEqual(m.mat4, m2.mat4); + assert.deepEqual([].slice.call(m.mat4), [].slice.call(m2.mat4)); + }); + }); + + suite.todo("add", () => {}); + + suite("mult", function () { + /* eslint-disable indent */ + var mm = [ + 30, 70, 110, 150, 70, 174, 278, 382, 110, 278, 446, 614, 150, 382, 614, + 846, + ]; + /* eslint-enable indent */ + + test("self", function () { + var m = new p5.Matrix(mat4.slice()); + m.mult(m); + /* eslint-disable indent */ + assert.deepEqual( + [].slice.call(m.mat4), + [ + 90, 100, 110, 120, 202, 228, 254, 280, 314, 356, 398, 440, 426, 484, + 542, 600, + ] + ); + /* eslint-enable indent */ + }); + + test("p5.Matrix", function () { + var m1 = new p5.Matrix(mat4.slice()); + var m2 = new p5.Matrix(other); + m1.mult(m2); + assert.deepEqual([].slice.call(m1.mat4), mm); + }); + + test("array", function () { + var m = new p5.Matrix(mat4.slice()); + m.mult(other); + assert.deepEqual([].slice.call(m.mat4), mm); + }); + + test.todo("arguments", function () { + var m = new p5.Matrix(mat4.slice()); + m.mult.apply(m, other); + assert.deepEqual([].slice.call(m.mat4), mm); + }); + }); + + suite("apply", function () { + /* eslint-disable indent */ + var am = [ + 276, 304, 332, 360, 304, 336, 368, 400, 332, 368, 404, 440, 360, 400, 440, + 480, + ]; + /* eslint-enable indent */ + + test("self", function () { + var m = new p5.Matrix(mat4.slice()); + m.apply(m); + /* eslint-disable indent */ + assert.deepEqual( + [].slice.call(m.mat4), + [ + 90, 100, 110, 120, 202, 228, 254, 280, 314, 356, 398, 440, 426, 484, + 542, 600, + ] + ); + /* eslint-enable indent */ + }); + + test("p5.Matrix", function () { + var m1 = new p5.Matrix(mat4.slice()); + var m2 = new p5.Matrix(other); + m1.apply(m2); + assert.deepEqual([].slice.call(m1.mat4), am); + }); + + test("array", function () { + var m = new p5.Matrix(mat4.slice()); + m.apply(other); + assert.deepEqual([].slice.call(m.mat4), am); + }); + + test("arguments", function () { + var m = new p5.Matrix(mat4.slice()); + m.apply.apply(m, other); + assert.deepEqual([].slice.call(m.mat4), am); + }); + }); + + suite("scale", function () { + /* eslint-disable indent */ + var sm = [2, 4, 6, 8, 15, 18, 21, 24, 45, 50, 55, 60, 13, 14, 15, 16]; + /* eslint-enable indent */ + + var mat4 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + test("p5.Vector", function () { + var m = new p5.Matrix(mat4.slice()); + var v = myp5.createVector(2, 3, 5); + m.scale(v); + assert.notEqual(m.mat4, mat4); + assert.deepEqual([].slice.call(m.mat4), sm); + }); + + test("array", function () { + var m = new p5.Matrix(mat4.slice()); + m.scale([2, 3, 5]); + assert.notEqual(m.mat4, mat4); + assert.deepEqual([].slice.call(m.mat4), sm); + }); + + test("arguments", function () { + var m = new p5.Matrix(mat4.slice()); + m.scale(2, 3, 5); + assert.notEqual(m.mat4, mat4); + assert.deepEqual([].slice.call(m.mat4), sm); + }); + }); + + suite("rotate", function () { + /* eslint-disable max-len */ + var rm = [ + 1.433447866601989, 2.5241247073503885, 3.6148015480987885, + 4.7054783888471885, 6.460371405020393, 7.054586073938033, + 7.648800742855675, 8.243015411773316, 7.950398010346969, + 9.157598472697025, 10.36479893504708, 11.571999397397136, 13, 14, 15, 16, + ]; + /* eslint-enable max-len */ + + test("p5.Vector", function () { + var m = new p5.Matrix(mat4.slice()); + var v = myp5.createVector(2, 3, 5); + m.rotate4x4(45 * myp5.DEG_TO_RAD, v); + assert.deepEqual([].slice.call(m.mat4), rm); + }); + + test("array", function () { + var m = new p5.Matrix(mat4.slice()); + m.rotate4x4(45 * myp5.DEG_TO_RAD, [2, 3, 5]); + assert.deepEqual([].slice.call(m.mat4), rm); + }); + + test("arguments", function () { + var m = new p5.Matrix(mat4.slice()); + m.rotate4x4(45 * myp5.DEG_TO_RAD, 2, 3, 5); + assert.deepEqual([].slice.call(m.mat4), rm); + }); + }); + + suite("p5.Matrix3x3", function () { + test("apply copy() to 3x3Matrix", function () { + const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + const mCopy = m.copy(); + + // The matrix created by copying is different from the original matrix + assert.notEqual(m, mCopy); + assert.notEqual(m.mat3, mCopy.mat3); + + // The matrix created by copying has the same elements as the original matrix + assert.deepEqual([].slice.call(m.mat3), [].slice.call(mCopy.mat3)); + }); + test("transpose()", function () { + const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + const mTp = new p5.Matrix([1, 4, 7, 2, 5, 8, 3, 6, 9]); + + // If no arguments, transpose itself + m.transpose(); + assert.deepEqual([].slice.call(m.mat3), [].slice.call(mTp.mat3)); + + // // If there is an array of arguments, set it by transposing it + m.transpose([1, 2, 3, 10, 20, 30, 100, 200, 300]); + assert.deepEqual( + [].slice.call(m.mat3), + [1, 10, 100, 2, 20, 200, 3, 30, 300] + ); + }); + + test("mult a 3x3 matrix with matrix as argument", function () { + const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + const multMatrix = new p5.Matrix([1, 1, 1, 0, 1, 1, 1, 0, 1]); + // When taking a matrix as an argument + m.mult(multMatrix); + expect(m.mat3).toEqual([ 4, 3, 6, 10, 9, 15, 16, 15, 24 ]); + }); + + test("mult a 3x3 matrix with array as argument", function () { + const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + m.mult([1, 1, 1, 0, 1, 1, 1, 0, 1]) + expect(m.mat3).toEqual([ 4, 3, 6, 10, 9, 15, 16, 15, 24 ]); + }); + + test("mult a 3x3 matrix with arguments non array", function () { + const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + m.mult(1, 1, 1, 0, 1, 1, 1, 0, 1) + expect(m.mat3).toEqual([ 4, 3, 6, 10, 9, 15, 16, 15, 24 ]); + }); + + test("column() and row()", function () { + // The matrix data is stored column-major, so each line below is + // a column rather than a row. Imagine you are looking at the + // transpose of the matrix in the source code. + const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + const column0 = m.column(0); + const column1 = m.column(1); + const column2 = m.column(2); + expect(column0.array()).toStrictEqual([1, 2, 3]); + expect(column1.array()).toStrictEqual([4, 5, 6]); + expect(column2.array()).toStrictEqual([7, 8, 9]); + const row0 = m.row(0); + const row1 = m.row(1); + const row2 = m.row(2); + expect(row0.array()).toStrictEqual([1, 4, 7]); + expect(row1.array()).toStrictEqual([2, 5, 8]); + expect(row2.array()).toStrictEqual([3, 6, 9]); + }); + test("diagonal()", function () { + const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + const m4x4 = new p5.Matrix([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + ]); + assert.deepEqual(m.diagonal(), [1, 5, 9]); + assert.deepEqual(m4x4.diagonal(), [1, 6, 11, 16]); + }); + test("multiplyVec version 3", function () { + const m = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + const multVector = new p5.Vector(3, 2, 1); + const result = m.multiplyVec(multVector); + assert.deepEqual(result.array(), [18, 24, 30]); + // If there is a target, set result and return that. + const target = new p5.Vector(); + m.multiplyVec(multVector, target); + assert.deepEqual(target.array(), [18, 24, 30]); + }); + test("createSubMatrix3x3", function () { + const m4x4 = new p5.Matrix([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + ]); + const result = new p5.Matrix([1, 2, 3, 5, 6, 7, 9, 10, 11]); + const subMatrix3x3 = m4x4.createSubMatrix3x3(); + assert.deepEqual( + [].slice.call(result.mat3), + [].slice.call(subMatrix3x3.mat3) + ); + }); + }); + + /// + describe("transpose", () => { + it("should transpose a 4x4 matrix correctly", () => { + const mat = new p5.Matrix([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + ]); + + mat.transpose(mat); + + expect(mat.mat4).toEqual([ + 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16, + ]); + }); + + it("should transpose a 4x4 matrix from an array correctly", () => { + const mat = new p5.Matrix([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + ]); + + mat.transpose([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + + expect(mat.mat4).toEqual([ + 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16, + ]); + }); + + // TODO: matrix transpose This needs to be added to the legacy tests + it.skip("should transpose a 3x3 matrix correctly", () => { + const mat = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + mat.transpose(mat); + expect(mat.mat3).toEqual([1, 4, 7, 2, 5, 8, 3, 6, 9]); + }); + + // TODO: matrix transpose This needs to be added to the legacy tests + it.skip("should transpose a 3x3 matrix from an array correctly", () => { + const mat = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + + mat.transpose([1, 2, 3, 4, 5, 6, 7, 8, 9]); + + expect(mat.mat3).toEqual([1, 4, 7, 2, 5, 8, 3, 6, 9]); + }); + }); + describe.skip("Determinant", () => { // TODO: Cristian, when this is public we'll add tests + it("should calculate the determinant of a 4x4 matrix", () => { + const mat4 = new p5.Matrix([ + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, + ]); + const det = mat4.determinant4x4(); + expect(det).toBeCloseTo(1); + }); + + it("should return 0 for a singular 4x4 matrix", () => { + const mat4 = new p5.Matrix([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + ]); + const det = mat4.determinant4x4(); + expect(det).toBeCloseTo(0); + }); + }); + + describe("invert", () => { + it("should correctly invert a 4x4 matrix", () => { + const matrix = new p5.Matrix([ + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, + ]); + + const invertedMatrix = matrix.invert(matrix); + + expect(invertedMatrix.mat4).toEqual([ + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, + ]); + }); + + it("should return null for a non-invertible matrix", () => { + const matrix = new p5.Matrix([ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + + const invertedMatrix = matrix.invert(matrix); + + expect(invertedMatrix).toBeNull(); + }); + + it("should correctly invert a non-identity 4x4 matrix", () => { + const matrix = new p5.Matrix([ + 1, 1, 1, 1, 1, -1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, + ]); + + const invertedMatrix = matrix.invert(matrix); + + expect(invertedMatrix.mat4).toEqual([ + 0, 0, 0, 1, 0, 0, 1, -1, 0, 1, 1, -2, 1, -1, -2, 2, + ]); + }); + }); + // + describe("invert", () => { + it("should correctly invert a 3x3 matrix", () => { + const matrix = new p5.Matrix([1, 2, 3, 0, 1, 4, 5, 6, 0]); + const invertedMatrix = matrix.invert(); + + expect(invertedMatrix.mat3).toEqual([-24, 18, 5, 20, -15, -4, -5, 4, 1]); + }); + + it("should return null for a non-invertible 3x3 matrix", () => { + const matrix = new p5.Matrix([1, 2, 3, 4, 5, 6, 7, 8, 9]); + const invertedMatrix = matrix.invert(); + + expect(invertedMatrix).toBeNull(); + }); + + it("should return the identity matrix when inverting the identity matrix", () => { + const matrix = new p5.Matrix([1, 0, 0, 0, 1, 0, 0, 0, 1]); + const invertedMatrix = matrix.invert(); + + expect(invertedMatrix.mat3).toEqual([1, 0, 0, 0, 1, 0, 0, 0, 1]); + }); + }); + + describe("mat set element", () => { + it("should set element of mat4 matrix", () => { + const matrix = new p5.Matrix([ + 1, 2, 3, 5, 0, 1, 4, 5, 5, 6, 0, 5, 5, 6, 0, 5, + ]); + const invertedMatrix = matrix.setElement(2, 0); + + expect(invertedMatrix.mat4).toEqual([ + 1, 2, 0, 5, 0, 1, 4, 5, 5, 6, 0, 5, 5, 6, 0, 5, + ]); + }); + + it("should set element of mat3 matrix", () => { + const matrix = new p5.Matrix([1, 2, 3, 0, 1, 4, 5, 6, 0]); + const invertedMatrix = matrix.setElement(2, 0); + + expect(invertedMatrix.mat3).toEqual([1, 2, 0, 0, 1, 4, 5, 6, 0]); + }); + }); +}); diff --git a/test/unit/math/p5.Vector.js b/test/unit/math/p5.Vector.js index 020f7a60b4..a2dc405716 100644 --- a/test/unit/math/p5.Vector.js +++ b/test/unit/math/p5.Vector.js @@ -1,124 +1,123 @@ -import vector from '../../../src/math/p5.Vector.js'; -import { vi } from 'vitest'; +import vector from "../../../src/math/p5.Vector.js"; +import { vi } from "vitest"; -suite('p5.Vector', function() { +suite("p5.Vector", function () { var v; const mockP5 = { - _validateParameters: vi.fn() + _validateParameters: vi.fn(), }; const mockP5Prototype = {}; - beforeEach(async function() { + beforeEach(async function () { vector(mockP5, mockP5Prototype); }); - afterEach(function() { - }); + afterEach(function () {}); - suite.todo('p5.prototype.setHeading() RADIANS', function() { - beforeEach(function() { + suite.todo("p5.prototype.setHeading() RADIANS", function () { + beforeEach(function () { mockP5Prototype.angleMode(mockP5.RADIANS); v = mockP5Prototype.createVector(1, 1); v.setHeading(1); }); - test('should have heading() value of 1 (RADIANS)', function() { + test("should have heading() value of 1 (RADIANS)", function () { assert.closeTo(v.heading(), 1, 0.001); }); }); - suite.todo('p5.prototype.setHeading() DEGREES', function() { - beforeEach(function() { + suite.todo("p5.prototype.setHeading() DEGREES", function () { + beforeEach(function () { mockP5Prototype.angleMode(mockP5.DEGREES); v = mockP5Prototype.createVector(1, 1); v.setHeading(1); }); - test('should have heading() value of 1 (DEGREES)', function() { + test("should have heading() value of 1 (DEGREES)", function () { assert.closeTo(v.heading(), 1, 0.001); }); }); // NOTE: test this in a separate file or move `createVector` to p5.Vector file // Prefer latter - suite.todo('p5.prototype.createVector()', function() { - beforeEach(function() { + suite.todo("p5.prototype.createVector()", function () { + beforeEach(function () { v = mockP5Prototype.createVector(); }); - test('should create instance of p5.Vector', function() { + test("should create instance of p5.Vector", function () { assert.instanceOf(v, mockP5.Vector); }); - test('should have x, y, z be initialized to 0', function() { + test("should have x, y, z be initialized to 0", function () { assert.equal(v.x, 0); assert.equal(v.y, 0); assert.equal(v.z, 0); }); }); - suite.todo('p5.prototype.createVector(1, 2, 3)', function() { - beforeEach(function() { + suite.todo("p5.prototype.createVector(1, 2, 3)", function () { + beforeEach(function () { v = mockP5Prototype.createVector(1, 2, 3); }); - test('should have x, y, z be initialized to 1,2,3', function() { + test("should have x, y, z be initialized to 1,2,3", function () { assert.equal(v.x, 1); assert.equal(v.y, 2); assert.equal(v.z, 3); }); }); - suite('new p5.Vector()', function() { - beforeEach(function() { + suite("new p5.Vector()", function () { + beforeEach(function () { v = new mockP5.Vector(); }); - test('should set constant to DEGREES', function() { + test("should set constant to DEGREES", function () { assert.instanceOf(v, mockP5.Vector); }); - test('should have x, y, z be initialized to 0', function() { + test("should have x, y, z be initialized to 0", function () { assert.equal(v.x, 0); assert.equal(v.y, 0); assert.equal(v.z, 0); }); }); - suite('new p5.Vector(1, 2, 3)', function() { - beforeEach(function() { + suite("new p5.Vector(1, 2, 3)", function () { + beforeEach(function () { v = new mockP5.Vector(1, 2, 3); }); - test('should have x, y, z be initialized to 1,2,3', function() { + test("should have x, y, z be initialized to 1,2,3", function () { assert.equal(v.x, 1); assert.equal(v.y, 2); assert.equal(v.z, 3); }); }); - suite('new p5.Vector(1,2,undefined)', function() { - beforeEach(function() { + suite("new p5.Vector(1,2,undefined)", function () { + beforeEach(function () { v = new mockP5.Vector(1, 2, undefined); }); - test('should have x, y, z be initialized to 1,2,0', function() { + test("should have x, y, z be initialized to 1,2,0", function () { assert.equal(v.x, 1); assert.equal(v.y, 2); assert.equal(v.z, 0); }); }); - suite('rotate', function() { - suite('p5.Vector.prototype.rotate() [INSTANCE]', function() { - test('should return the same object', function() { + suite("rotate", function () { + suite("p5.Vector.prototype.rotate() [INSTANCE]", function () { + test("should return the same object", function () { v = new mockP5.Vector(0, 1); expect(v.rotate(Math.PI)).to.eql(v); }); - suite.todo('radians', function() { - beforeEach(function() { + suite.todo("radians", function () { + beforeEach(function () { mockP5Prototype.angleMode(mockP5.RADIANS); }); - test('should rotate the vector [0, 1, 0] by pi radians to [0, -1, 0]', function() { + test("should rotate the vector [0, 1, 0] by pi radians to [0, -1, 0]", function () { v = mockP5Prototype.createVector(0, 1, 0); v.rotate(Math.PI); expect(v.x).to.be.closeTo(0, 0.01); @@ -126,7 +125,7 @@ suite('p5.Vector', function() { expect(v.z).to.be.closeTo(0, 0.01); }); - test('should rotate the vector [1, 0, 0] by -pi/2 radians to [0, -1, 0]', function() { + test("should rotate the vector [1, 0, 0] by -pi/2 radians to [0, -1, 0]", function () { v = mockP5Prototype.createVector(1, 0, 0); v.rotate(-Math.PI / 2); expect(v.x).to.be.closeTo(0, 0.01); @@ -134,7 +133,7 @@ suite('p5.Vector', function() { expect(v.z).to.be.closeTo(0, 0.01); }); - test('should rotate the vector [1, 0, 0] by pi radians to [-1, 0, 0]', function() { + test("should rotate the vector [1, 0, 0] by pi radians to [-1, 0, 0]", function () { v = mockP5Prototype.createVector(1, 0, 0); v.rotate(Math.PI); expect(v.x).to.be.closeTo(-1, 0.01); @@ -143,12 +142,12 @@ suite('p5.Vector', function() { }); }); - suite.todo('degrees', function() { - beforeEach(function() { + suite.todo("degrees", function () { + beforeEach(function () { mockP5Prototype.angleMode(mockP5.DEGREES); }); - test('should rotate the vector [0, 1, 0] by 180 degrees to [0, -1, 0]', function() { + test("should rotate the vector [0, 1, 0] by 180 degrees to [0, -1, 0]", function () { v = mockP5Prototype.createVector(0, 1, 0); v.rotate(180); expect(v.x).to.be.closeTo(0, 0.01); @@ -156,7 +155,7 @@ suite('p5.Vector', function() { expect(v.z).to.be.closeTo(0, 0.01); }); - test('should rotate the vector [1, 0, 0] by -90 degrees to [0, -1, 0]', function() { + test("should rotate the vector [1, 0, 0] by -90 degrees to [0, -1, 0]", function () { v = mockP5Prototype.createVector(1, 0, 0); v.rotate(-90); expect(v.x).to.be.closeTo(0, 0.01); @@ -166,12 +165,12 @@ suite('p5.Vector', function() { }); }); - suite.todo('p5.Vector.rotate() [CLASS]', function() { - beforeEach(function() { + suite.todo("p5.Vector.rotate() [CLASS]", function () { + beforeEach(function () { mockP5Prototype.angleMode(mockP5.RADIANS); }); - test('should not change the original object', function() { + test("should not change the original object", function () { v = mockP5Prototype.createVector(1, 0, 0); mockP5.Vector.rotate(v, Math.PI / 2); expect(v.x).to.equal(1); @@ -179,7 +178,7 @@ suite('p5.Vector', function() { expect(v.z).to.equal(0); }); - test('should rotate the vector [0, 1, 0] by pi radians to [0, -1, 0]', function() { + test("should rotate the vector [0, 1, 0] by pi radians to [0, -1, 0]", function () { v = mockP5Prototype.createVector(0, 1, 0); const v1 = mockP5.Vector.rotate(v, Math.PI); expect(v1.x).to.be.closeTo(0, 0.01); @@ -187,7 +186,7 @@ suite('p5.Vector', function() { expect(v1.z).to.be.closeTo(0, 0.01); }); - test('should rotate the vector [1, 0, 0] by -pi/2 radians to [0, -1, 0]', function() { + test("should rotate the vector [1, 0, 0] by -pi/2 radians to [0, -1, 0]", function () { v = mockP5Prototype.createVector(1, 0, 0); const v1 = mockP5.Vector.rotate(v, -Math.PI / 2); expect(v1.x).to.be.closeTo(0, 0.01); @@ -197,20 +196,20 @@ suite('p5.Vector', function() { }); }); - suite('angleBetween', function() { + suite("angleBetween", function () { let v1, v2; - beforeEach(function() { + beforeEach(function () { v1 = new mockP5.Vector(1, 0, 0); v2 = new mockP5.Vector(2, 2, 0); }); - suite('p5.Vector.prototype.angleBetween() [INSTANCE]', function() { - test('should return a Number', function() { + suite("p5.Vector.prototype.angleBetween() [INSTANCE]", function () { + test("should return a Number", function () { const res = v1.angleBetween(v2); - expect(typeof res).to.eql('number'); + expect(typeof res).to.eql("number"); }); - test('should not trip on rounding issues in 2D space', function() { + test("should not trip on rounding issues in 2D space", function () { v1 = new mockP5.Vector(-11, -20); v2 = new mockP5.Vector(-5.5, -10); const v3 = new mockP5.Vector(5.5, 10); @@ -219,47 +218,50 @@ suite('p5.Vector', function() { expect(v1.angleBetween(v3)).to.be.closeTo(Math.PI, 0.00001); }); - test('should not trip on rounding issues in 3D space', function() { + test("should not trip on rounding issues in 3D space", function () { v1 = new mockP5.Vector(1, 1.1, 1.2); v2 = new mockP5.Vector(2, 2.2, 2.4); expect(v1.angleBetween(v2)).to.be.closeTo(0, 0.00001); }); - test('should return NaN for zero vector', function() { + test("should return NaN for zero vector", function () { v1 = new mockP5.Vector(0, 0, 0); v2 = new mockP5.Vector(2, 3, 4); expect(v1.angleBetween(v2)).to.be.NaN; expect(v2.angleBetween(v1)).to.be.NaN; }); - test.todo('between [1,0,0] and [1,0,0] should be 0 degrees', function() { + test.todo("between [1,0,0] and [1,0,0] should be 0 degrees", function () { mockP5Prototype.angleMode(mockP5.DEGREES); v1 = new mockP5.Vector(1, 0, 0); v2 = new mockP5.Vector(1, 0, 0); expect(v1.angleBetween(v2)).to.equal(0); }); - test.todo('between [0,3,0] and [0,-3,0] should be 180 degrees', function() { - mockP5Prototype.angleMode(mockP5.DEGREES); - v1 = new mockP5.Vector(0, 3, 0); - v2 = new mockP5.Vector(0, -3, 0); - expect(v1.angleBetween(v2)).to.be.closeTo(180, 0.01); - }); + test.todo( + "between [0,3,0] and [0,-3,0] should be 180 degrees", + function () { + mockP5Prototype.angleMode(mockP5.DEGREES); + v1 = new mockP5.Vector(0, 3, 0); + v2 = new mockP5.Vector(0, -3, 0); + expect(v1.angleBetween(v2)).to.be.closeTo(180, 0.01); + } + ); - test('between [1,0,0] and [2,2,0] should be 1/4 PI radians', function() { + test("between [1,0,0] and [2,2,0] should be 1/4 PI radians", function () { v1 = new mockP5.Vector(1, 0, 0); v2 = new mockP5.Vector(2, 2, 0); expect(v1.angleBetween(v2)).to.be.closeTo(Math.PI / 4, 0.01); - expect(v2.angleBetween(v1)).to.be.closeTo(-1 * Math.PI / 4, 0.01); + expect(v2.angleBetween(v1)).to.be.closeTo((-1 * Math.PI) / 4, 0.01); }); - test('between [2,0,0] and [-2,0,0] should be PI radians', function() { + test("between [2,0,0] and [-2,0,0] should be PI radians", function () { v1 = new mockP5.Vector(2, 0, 0); v2 = new mockP5.Vector(-2, 0, 0); expect(v1.angleBetween(v2)).to.be.closeTo(Math.PI, 0.01); }); - test('between [2,0,0] and [-2,-2,0] should be -3/4 PI radians ', function() { + test("between [2,0,0] and [-2,-2,0] should be -3/4 PI radians ", function () { v1 = new mockP5.Vector(2, 0, 0); v2 = new mockP5.Vector(-2, -2, 0); expect(v1.angleBetween(v2)).to.be.closeTo( @@ -268,7 +270,7 @@ suite('p5.Vector', function() { ); }); - test('between [-2,-2,0] and [2,0,0] should be 3/4 PI radians', function() { + test("between [-2,-2,0] and [2,0,0] should be 3/4 PI radians", function () { v1 = new mockP5.Vector(-2, -2, 0); v2 = new mockP5.Vector(2, 0, 0); expect(v1.angleBetween(v2)).to.be.closeTo( @@ -277,35 +279,38 @@ suite('p5.Vector', function() { ); }); - test('For the same vectors, the angle between them should always be 0.', function() { + test("For the same vectors, the angle between them should always be 0.", function () { v1 = new mockP5.Vector(288, 814); v2 = new mockP5.Vector(288, 814); expect(v1.angleBetween(v2)).to.equal(0); }); - test('The angle between vectors pointing in opposite is always PI.', function() { + test("The angle between vectors pointing in opposite is always PI.", function () { v1 = new mockP5.Vector(219, 560); v2 = new mockP5.Vector(-219, -560); expect(v1.angleBetween(v2)).to.be.closeTo(Math.PI, 0.0000001); }); }); - suite('p5.Vector.angleBetween() [CLASS]', function() { - test('should return NaN for zero vector', function() { + suite("p5.Vector.angleBetween() [CLASS]", function () { + test("should return NaN for zero vector", function () { v1 = new mockP5.Vector(0, 0, 0); v2 = new mockP5.Vector(2, 3, 4); expect(mockP5.Vector.angleBetween(v1, v2)).to.be.NaN; expect(mockP5.Vector.angleBetween(v2, v1)).to.be.NaN; }); - test.todo('between [1,0,0] and [0,-1,0] should be -90 degrees', function() { - mockP5Prototype.angleMode(mockP5.DEGREES); - v1 = new mockP5.Vector(1, 0, 0); - v2 = new mockP5.Vector(0, -1, 0); - expect(mockP5.Vector.angleBetween(v1, v2)).to.be.closeTo(-90, 0.01); - }); + test.todo( + "between [1,0,0] and [0,-1,0] should be -90 degrees", + function () { + mockP5Prototype.angleMode(mockP5.DEGREES); + v1 = new mockP5.Vector(1, 0, 0); + v2 = new mockP5.Vector(0, -1, 0); + expect(mockP5.Vector.angleBetween(v1, v2)).to.be.closeTo(-90, 0.01); + } + ); - test('between [0,3,0] and [0,-3,0] should be PI radians', function() { + test("between [0,3,0] and [0,-3,0] should be PI radians", function () { v1 = new mockP5.Vector(0, 3, 0); v2 = new mockP5.Vector(0, -3, 0); expect(mockP5.Vector.angleBetween(v1, v2)).to.be.closeTo(Math.PI, 0.01); @@ -313,9 +318,9 @@ suite('p5.Vector', function() { }); }); - suite('set()', function() { - suite('with p5.Vector', function() { - test("should have x, y, z be initialized to the vector's x, y, z", function() { + suite("set()", function () { + suite("with p5.Vector", function () { + test("should have x, y, z be initialized to the vector's x, y, z", function () { v.set(new mockP5.Vector(2, 5, 6)); expect(v.x).to.eql(2); expect(v.y).to.eql(5); @@ -323,15 +328,15 @@ suite('p5.Vector', function() { }); }); - suite('with Array', function() { - test('[2,4] should set x === 2, y === 4, z === 0', function() { + suite("with Array", function () { + test("[2,4] should set x === 2, y === 4, z === 0", function () { v.set([2, 4]); expect(v.x).to.eql(2); expect(v.y).to.eql(4); expect(v.z).to.eql(0); }); - test("should have x, y, z be initialized to the array's 0,1,2 index", function() { + test("should have x, y, z be initialized to the array's 0,1,2 index", function () { v.set([2, 5, 6]); expect(v.x).to.eql(2); expect(v.y).to.eql(5); @@ -339,8 +344,8 @@ suite('p5.Vector', function() { }); }); - suite('set(1,2,3)', function() { - test('should have x, y, z be initialized to the 1, 2, 3', function() { + suite("set(1,2,3)", function () { + test("should have x, y, z be initialized to the 1, 2, 3", function () { v.set(1, 2, 3); expect(v.x).to.eql(1); expect(v.y).to.eql(2); @@ -349,18 +354,18 @@ suite('p5.Vector', function() { }); }); - suite('copy', function() { - beforeEach(function() { + suite("copy", function () { + beforeEach(function () { v = new mockP5.Vector(2, 3, 4); }); - suite('p5.Vector.prototype.copy() [INSTANCE]', function() { - test('should not return the same instance', function() { + suite("p5.Vector.prototype.copy() [INSTANCE]", function () { + test("should not return the same instance", function () { var newObject = v.copy(); expect(newObject).to.not.equal(v); }); - test("should return the calling object's x, y, z", function() { + test("should return the calling object's x, y, z", function () { var newObject = v.copy(); expect(newObject.x).to.eql(2); expect(newObject.y).to.eql(3); @@ -368,13 +373,13 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.copy() [CLASS]', function() { - test('should not return the same instance', function() { + suite("p5.Vector.copy() [CLASS]", function () { + test("should not return the same instance", function () { var newObject = mockP5.Vector.copy(v); expect(newObject).to.not.equal(v); }); - test("should return the passed object's x, y, z", function() { + test("should return the passed object's x, y, z", function () { var newObject = mockP5.Vector.copy(v); expect(newObject.x).to.eql(2); expect(newObject.y).to.eql(3); @@ -383,13 +388,13 @@ suite('p5.Vector', function() { }); }); - suite('add()', function() { - beforeEach(function() { + suite("add()", function () { + beforeEach(function () { v = new mockP5.Vector(); }); - suite('with p5.Vector', function() { - test('should add x, y, z from the vector argument', function() { + suite("with p5.Vector", function () { + test("should add x, y, z from the vector argument", function () { v.add(new mockP5.Vector(1, 5, 6)); expect(v.x).to.eql(1); expect(v.y).to.eql(5); @@ -397,9 +402,9 @@ suite('p5.Vector', function() { }); }); - suite('with Array', function() { - suite('add([2, 4])', function() { - test('should add the x and y components', function() { + suite("with Array", function () { + suite("add([2, 4])", function () { + test("should add the x and y components", function () { v.add([2, 4]); expect(v.x).to.eql(2); expect(v.y).to.eql(4); @@ -407,7 +412,7 @@ suite('p5.Vector', function() { }); }); - test("should add the array's 0,1,2 index", function() { + test("should add the array's 0,1,2 index", function () { v.add([2, 5, 6]); expect(v.x).to.eql(2); expect(v.y).to.eql(5); @@ -415,8 +420,8 @@ suite('p5.Vector', function() { }); }); - suite('add(3,5)', function() { - test('should add the x and y components', function() { + suite("add(3,5)", function () { + test("should add the x and y components", function () { v.add(3, 5); expect(v.x).to.eql(3); expect(v.y).to.eql(5); @@ -424,8 +429,8 @@ suite('p5.Vector', function() { }); }); - suite('add(2,3,4)', function() { - test('should add the x, y, z components', function() { + suite("add(2,3,4)", function () { + test("should add the x, y, z components", function () { v.add(5, 5, 5); expect(v.x).to.eql(5); expect(v.y).to.eql(5); @@ -433,20 +438,28 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.add(v1, v2)', function() { + suite("add(2,3,4)", function () { + test("should add the x, y, z components", function () { + v.add([1, 2, 3]); + expect(v.x).to.eql(1); + expect(v.y).to.eql(2); + }); + }); + + suite("p5.Vector.add(v1, v2)", function () { var v1, v2, res; - beforeEach(function() { + beforeEach(function () { v1 = new mockP5.Vector(2, 0, 3); v2 = new mockP5.Vector(0, 1, 3); res = mockP5.Vector.add(v1, v2); }); - test('should return neither v1 nor v2', function() { + test("should return neither v1 nor v2", function () { expect(res).to.not.eql(v1); expect(res).to.not.eql(v2); }); - test('should be sum of the two p5.Vectors', function() { + test("should be sum of the two p5.Vectors", function () { expect(res.x).to.eql(v1.x + v2.x); expect(res.y).to.eql(v1.y + v2.y); expect(res.z).to.eql(v1.z + v2.z); @@ -454,62 +467,62 @@ suite('p5.Vector', function() { }); }); - suite('rem()', function() { - beforeEach(function() { + suite("rem()", function () { + beforeEach(function () { v = new mockP5.Vector(3, 4, 5); }); - test('should give same vector if nothing passed as parameter', function() { + test("should give same vector if nothing passed as parameter", function () { v.rem(); expect(v.x).to.eql(3); expect(v.y).to.eql(4); expect(v.z).to.eql(5); }); - test('should give correct output if passed only one numeric value', function() { + test("should give correct output if passed only one numeric value", function () { v.rem(2); expect(v.x).to.eql(1); expect(v.y).to.eql(0); expect(v.z).to.eql(1); }); - test('should give correct output if passed two numeric value', function() { + test("should give correct output if passed two numeric value", function () { v.rem(2, 3); expect(v.x).to.eql(1); expect(v.y).to.eql(1); expect(v.z).to.eql(5); }); - test('should give correct output if passed three numeric value', function() { + test("should give correct output if passed three numeric value", function () { v.rem(2, 3, 4); expect(v.x).to.eql(1); expect(v.y).to.eql(1); expect(v.z).to.eql(1); }); - suite('with p5.Vector', function() { - test('should return correct output if only one component is non-zero', function() { + suite("with p5.Vector", function () { + test("should return correct output if only one component is non-zero", function () { v.rem(new mockP5.Vector(0, 0, 4)); expect(v.x).to.eql(3); expect(v.y).to.eql(4); expect(v.z).to.eql(1); }); - test('should return correct output if x component is zero', () => { + test("should return correct output if x component is zero", () => { v.rem(new mockP5.Vector(0, 3, 4)); expect(v.x).to.eql(3); expect(v.y).to.eql(1); expect(v.z).to.eql(1); }); - test('should return correct output if all components are non-zero', () => { + test("should return correct output if all components are non-zero", () => { v.rem(new mockP5.Vector(2, 3, 4)); expect(v.x).to.eql(1); expect(v.y).to.eql(1); expect(v.z).to.eql(1); }); - test('should return same vector if all components are zero', () => { + test("should return same vector if all components are zero", () => { v.rem(new mockP5.Vector(0, 0, 0)); expect(v.x).to.eql(3); expect(v.y).to.eql(4); @@ -517,12 +530,12 @@ suite('p5.Vector', function() { }); }); - suite('with negative vectors', function() { + suite("with negative vectors", function () { let v; - beforeEach(function() { + beforeEach(function () { v = new mockP5.Vector(-15, -5, -2); }); - test('should return correct output', () => { + test("should return correct output", () => { v.rem(new mockP5.Vector(2, 3, 3)); expect(v.x).to.eql(-1); expect(v.y).to.eql(-2); @@ -530,28 +543,28 @@ suite('p5.Vector', function() { }); }); - suite('with Arrays', function() { - test('should return remainder of vector components for 3D vector', function() { + suite("with Arrays", function () { + test("should return remainder of vector components for 3D vector", function () { v.rem([2, 3, 0]); expect(v.x).to.eql(1); expect(v.y).to.eql(1); expect(v.z).to.eql(5); }); - test('should return remainder of vector components for 2D vector', function() { + test("should return remainder of vector components for 2D vector", function () { v.rem([2, 3]); expect(v.x).to.eql(1); expect(v.y).to.eql(1); expect(v.z).to.eql(5); }); - test('should return correct output if x,y components are zero for 2D vector', () => { + test("should return correct output if x,y components are zero for 2D vector", () => { v.rem([0, 0]); expect(v.x).to.eql(3); expect(v.y).to.eql(4); expect(v.z).to.eql(5); }); - test('should return same vector if any vector component is non-finite number', () => { + test("should return same vector if any vector component is non-finite number", () => { v.rem([2, 3, Infinity]); expect(v.x).to.eql(3); expect(v.y).to.eql(4); @@ -559,20 +572,20 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.rem(v1,v2)', function() { + suite("p5.Vector.rem(v1,v2)", function () { let v1, v2, res; - beforeEach(function() { + beforeEach(function () { v1 = new mockP5.Vector(2, 3, 4); v2 = new mockP5.Vector(1, 2, 3); res = mockP5.Vector.rem(v1, v2); }); - test('should return neither v1 nor v2', function() { + test("should return neither v1 nor v2", function () { expect(res).to.not.eql(v1); expect(res).to.not.eql(v2); }); - test('should be v1 % v2', function() { + test("should be v1 % v2", function () { expect(res.x).to.eql(v1.x % v2.x); expect(res.y).to.eql(v1.y % v2.y); expect(res.z).to.eql(v1.z % v2.z); @@ -580,14 +593,14 @@ suite('p5.Vector', function() { }); }); - suite('sub()', function() { - beforeEach(function() { + suite("sub()", function () { + beforeEach(function () { v.x = 0; v.y = 0; v.z = 0; }); - suite('with p5.Vector', function() { - test('should sub x, y, z from the vector argument', function() { + suite("with p5.Vector", function () { + test("should sub x, y, z from the vector argument", function () { v.sub(new mockP5.Vector(2, 5, 6)); expect(v.x).to.eql(-2); expect(v.y).to.eql(-5); @@ -595,9 +608,9 @@ suite('p5.Vector', function() { }); }); - suite('with Array', function() { - suite('sub([2, 4])', function() { - test('should sub the x and y components', function() { + suite("with Array", function () { + suite("sub([2, 4])", function () { + test("should sub the x and y components", function () { v.sub([2, 4]); expect(v.x).to.eql(-2); expect(v.y).to.eql(-4); @@ -605,7 +618,7 @@ suite('p5.Vector', function() { }); }); - test("should subtract from the array's 0,1,2 index", function() { + test("should subtract from the array's 0,1,2 index", function () { v.sub([2, 5, 6]); expect(v.x).to.eql(-2); expect(v.y).to.eql(-5); @@ -613,8 +626,8 @@ suite('p5.Vector', function() { }); }); - suite('sub(3,5)', function() { - test('should subtract the x and y components', function() { + suite("sub(3,5)", function () { + test("should subtract the x and y components", function () { v.sub(3, 5); expect(v.x).to.eql(-3); expect(v.y).to.eql(-5); @@ -622,8 +635,8 @@ suite('p5.Vector', function() { }); }); - suite('sub(2,3,4)', function() { - test('should subtract the x, y, z components', function() { + suite("sub(2,3,4)", function () { + test("should subtract the x, y, z components", function () { v.sub(5, 5, 5); expect(v.x).to.eql(-5); expect(v.y).to.eql(-5); @@ -631,20 +644,20 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.sub(v1, v2)', function() { + suite("p5.Vector.sub(v1, v2)", function () { var v1, v2, res; - beforeEach(function() { + beforeEach(function () { v1 = new mockP5.Vector(2, 0, 3); v2 = new mockP5.Vector(0, 1, 3); res = mockP5.Vector.sub(v1, v2); }); - test('should return neither v1 nor v2', function() { + test("should return neither v1 nor v2", function () { expect(res).to.not.eql(v1); expect(res).to.not.eql(v2); }); - test('should be v1 - v2', function() { + test("should be v1 - v2", function () { expect(res.x).to.eql(v1.x - v2.x); expect(res.y).to.eql(v1.y - v2.y); expect(res.z).to.eql(v1.z - v2.z); @@ -652,31 +665,31 @@ suite('p5.Vector', function() { }); }); - suite('mult()', function() { - beforeEach(function() { + suite("mult()", function () { + beforeEach(function () { v = new mockP5.Vector(1, 1, 1); }); - test('should return the same object', function() { + test("should return the same object", function () { expect(v.mult(1)).to.eql(v); }); - test('should not change x, y, z if no argument is given', function() { + test("should not change x, y, z if no argument is given", function () { v.mult(); expect(v.x).to.eql(1); expect(v.y).to.eql(1); expect(v.z).to.eql(1); }); - test('should not change x, y, z if n is not a finite number', function() { + test("should not change x, y, z if n is not a finite number", function () { v.mult(NaN); expect(v.x).to.eql(1); expect(v.y).to.eql(1); expect(v.z).to.eql(1); }); - suite('with scalar', function() { - test('multiply the x, y, z with the scalar', function() { + suite("with scalar", function () { + test("multiply the x, y, z with the scalar", function () { v.mult(2); expect(v.x).to.eql(2); expect(v.y).to.eql(2); @@ -684,78 +697,78 @@ suite('p5.Vector', function() { }); }); - suite('v0.mult(v1)', function() { + suite("v0.mult(v1)", function () { var v0, v1; - beforeEach(function() { + beforeEach(function () { v0 = new mockP5.Vector(1, 2, 3); v1 = new mockP5.Vector(2, 3, 4); v0.mult(v1); }); - test('should do component wise multiplication', function() { + test("should do component wise multiplication", function () { expect(v0.x).to.eql(2); expect(v0.y).to.eql(6); expect(v0.z).to.eql(12); }); }); - suite('v0.mult(arr)', function() { + suite("v0.mult(arr)", function () { var v0, arr; - beforeEach(function() { + beforeEach(function () { v0 = new mockP5.Vector(1, 2, 3); arr = [2, 3, 4]; v0.mult(arr); }); - test('should do component wise multiplication from an array', function() { + test("should do component wise multiplication from an array", function () { expect(v0.x).to.eql(2); expect(v0.y).to.eql(6); expect(v0.z).to.eql(12); }); }); - suite('p5.Vector.mult(v, n)', function() { + suite("p5.Vector.mult(v, n)", function () { var v, res; - beforeEach(function() { + beforeEach(function () { v = new mockP5.Vector(1, 2, 3); res = mockP5.Vector.mult(v, 4); }); - test('should return a new p5.Vector', function() { + test("should return a new p5.Vector", function () { expect(res).to.not.eql(v); }); - test('should multiply the scalar', function() { + test("should multiply the scalar", function () { expect(res.x).to.eql(4); expect(res.y).to.eql(8); expect(res.z).to.eql(12); }); }); - suite('p5.Vector.mult(v, v', function() { + suite("p5.Vector.mult(v, v", function () { var v0, v1, res; - beforeEach(function() { + beforeEach(function () { v0 = new mockP5.Vector(1, 2, 3); v1 = new mockP5.Vector(2, 3, 4); res = mockP5.Vector.mult(v0, v1); }); - test('should return new vector from component wise multiplication', function() { + test("should return new vector from component wise multiplication", function () { expect(res.x).to.eql(2); expect(res.y).to.eql(6); expect(res.z).to.eql(12); }); }); - suite('p5.Vector.mult(v, arr', function() { + suite("p5.Vector.mult(v, arr", function () { var v0, arr, res; - beforeEach(function() { + beforeEach(function () { v0 = new mockP5.Vector(1, 2, 3); arr = [2, 3, 4]; res = mockP5.Vector.mult(v0, arr); }); - test('should return new vector from component wise multiplication with an array', function() { + test("should return new vector from component wise multiplication with an array", function () { expect(res.x).to.eql(2); expect(res.y).to.eql(6); expect(res.z).to.eql(12); @@ -763,38 +776,38 @@ suite('p5.Vector', function() { }); }); - suite('div()', function() { - beforeEach(function() { + suite("div()", function () { + beforeEach(function () { v = new mockP5.Vector(1, 1, 1); }); - test('should return the same object', function() { + test("should return the same object", function () { expect(v.div(1)).to.eql(v); }); - test('should not change x, y, z if no argument is given', function() { + test("should not change x, y, z if no argument is given", function () { v.div(); expect(v.x).to.eql(1); expect(v.y).to.eql(1); expect(v.z).to.eql(1); }); - test('should not change x, y, z if n is not a finite number', function() { + test("should not change x, y, z if n is not a finite number", function () { v.div(NaN); expect(v.x).to.eql(1); expect(v.y).to.eql(1); expect(v.z).to.eql(1); }); - suite('with scalar', function() { - test('divide the x, y, z with the scalar', function() { + suite("with scalar", function () { + test("divide the x, y, z with the scalar", function () { v.div(2); expect(v.x).to.be.closeTo(0.5, 0.01); expect(v.y).to.be.closeTo(0.5, 0.01); expect(v.z).to.be.closeTo(0.5, 0.01); }); - test('should not change x, y, z if n is 0', function() { + test("should not change x, y, z if n is 0", function () { v.div(0); expect(v.x).to.eql(1); expect(v.y).to.eql(1); @@ -802,31 +815,31 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.div(v, n)', function() { + suite("p5.Vector.div(v, n)", function () { var v, res; - beforeEach(function() { + beforeEach(function () { v = new mockP5.Vector(1, 1, 1); res = mockP5.Vector.div(v, 4); }); - test('should not be undefined', function() { + test("should not be undefined", function () { expect(res).to.not.eql(undefined); }); - test('should return a new p5.Vector', function() { + test("should return a new p5.Vector", function () { expect(res).to.not.eql(v); }); - test('should divide the scalar', function() { + test("should divide the scalar", function () { expect(res.x).to.eql(0.25); expect(res.y).to.eql(0.25); expect(res.z).to.eql(0.25); }); }); - suite('v0.div(v1)', function() { + suite("v0.div(v1)", function () { var v0, v1, v2, v3; - beforeEach(function() { + beforeEach(function () { v0 = new mockP5.Vector(2, 6, 9); v1 = new mockP5.Vector(2, 2, 3); v2 = new mockP5.Vector(1, 1, 1); @@ -835,20 +848,20 @@ suite('p5.Vector', function() { v0.div(v1); }); - test('should do component wise division', function() { + test("should do component wise division", function () { expect(v0.x).to.eql(1); expect(v0.y).to.eql(3); expect(v0.z).to.eql(3); }); - test('should not change x, y, z if v3 is all 0', function() { + test("should not change x, y, z if v3 is all 0", function () { v2.div(v3); expect(v2.x).to.eql(1); expect(v2.y).to.eql(1); expect(v2.z).to.eql(1); }); - test('should work on 2D vectors', function() { + test("should work on 2D vectors", function () { const v = new mockP5.Vector(1, 1); const divisor = new mockP5.Vector(2, 2); v.div(divisor); @@ -857,7 +870,7 @@ suite('p5.Vector', function() { expect(v.z).to.eql(0); }); - test('should work when the dividend has 0', function() { + test("should work when the dividend has 0", function () { const v = new mockP5.Vector(1, 0); const divisor = new mockP5.Vector(2, 2); v.div(divisor); @@ -866,7 +879,7 @@ suite('p5.Vector', function() { expect(v.z).to.eql(0); }); - test('should do nothing when the divisor has 0', function() { + test("should do nothing when the divisor has 0", function () { const v = new mockP5.Vector(1, 1); const divisor = new mockP5.Vector(0, 2); v.div(divisor); @@ -876,22 +889,22 @@ suite('p5.Vector', function() { }); }); - suite('v0.div(arr)', function() { + suite("v0.div(arr)", function () { var v0, v1, arr; - beforeEach(function() { + beforeEach(function () { v0 = new mockP5.Vector(2, 6, 9); v1 = new mockP5.Vector(1, 1, 1); arr = [2, 2, 3]; v0.div(arr); }); - test('should do component wise division with an array', function() { + test("should do component wise division with an array", function () { expect(v0.x).to.eql(1); expect(v0.y).to.eql(3); expect(v0.z).to.eql(3); }); - test('should not change x, y, z if array contains 0', function() { + test("should not change x, y, z if array contains 0", function () { v1.div([0, 0, 0]); expect(v1.x).to.eql(1); expect(v1.y).to.eql(1); @@ -899,30 +912,30 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.div(v, v', function() { + suite("p5.Vector.div(v, v", function () { var v0, v1, res; - beforeEach(function() { + beforeEach(function () { v0 = new mockP5.Vector(2, 6, 9); v1 = new mockP5.Vector(2, 2, 3); res = mockP5.Vector.div(v0, v1); }); - test('should return new vector from component wise division', function() { + test("should return new vector from component wise division", function () { expect(res.x).to.eql(1); expect(res.y).to.eql(3); expect(res.z).to.eql(3); }); }); - suite('p5.Vector.div(v, arr', function() { + suite("p5.Vector.div(v, arr", function () { var v0, arr, res; - beforeEach(function() { + beforeEach(function () { v0 = new mockP5.Vector(2, 6, 9); arr = [2, 2, 3]; res = mockP5.Vector.div(v0, arr); }); - test('should return new vector from component wise division with an array', function() { + test("should return new vector from component wise division with an array", function () { expect(res.x).to.eql(1); expect(res.y).to.eql(3); expect(res.z).to.eql(3); @@ -930,65 +943,65 @@ suite('p5.Vector', function() { }); }); - suite('dot', function() { - beforeEach(function() { + suite("dot", function () { + beforeEach(function () { v.x = 1; v.y = 1; v.z = 1; }); - test('should return a number', function() { - expect(typeof v.dot(new mockP5.Vector()) === 'number').to.eql(true); + test("should return a number", function () { + expect(typeof v.dot(new mockP5.Vector()) === "number").to.eql(true); }); - suite('with p5.Vector', function() { - test('should be the dot product of the vector', function() { + suite("with p5.Vector", function () { + test("should be the dot product of the vector", function () { expect(v.dot(new mockP5.Vector(2, 2))).to.eql(4); }); }); - suite('with x, y, z', function() { - test('should be the dot product with x, y', function() { + suite("with x, y, z", function () { + test("should be the dot product with x, y", function () { expect(v.dot(2, 2)).to.eql(4); }); - test('should be the dot product with x, y, z', function() { + test("should be the dot product with x, y, z", function () { expect(v.dot(2, 2, 2)).to.eql(6); }); }); - suite('p5.Vector.dot(v, n)', function() { + suite("p5.Vector.dot(v, n)", function () { var v1, v2, res; - beforeEach(function() { + beforeEach(function () { v1 = new mockP5.Vector(1, 1, 1); v2 = new mockP5.Vector(2, 3, 4); res = mockP5.Vector.dot(v1, v2); }); - test('should return a number', function() { - expect(typeof res === 'number').to.eql(true); + test("should return a number", function () { + expect(typeof res === "number").to.eql(true); }); - test('should be the dot product of the two vectors', function() { + test("should be the dot product of the two vectors", function () { expect(res).to.eql(9); }); }); }); - suite('cross', function() { + suite("cross", function () { var res; - beforeEach(function() { + beforeEach(function () { v.x = 1; v.y = 1; v.z = 1; }); - test('should return a new product', function() { + test("should return a new product", function () { expect(v.cross(new mockP5.Vector())).to.not.eql(v); }); - suite('with p5.Vector', function() { - test('should cross x, y, z from the vector argument', function() { + suite("with p5.Vector", function () { + test("should cross x, y, z from the vector argument", function () { res = v.cross(new mockP5.Vector(2, 5, 6)); expect(res.x).to.eql(1); //this.y * v.z - this.z * v.y expect(res.y).to.eql(-4); //this.z * v.x - this.x * v.z @@ -996,24 +1009,24 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.cross(v1, v2)', function() { + suite("p5.Vector.cross(v1, v2)", function () { var v1, v2, res; - beforeEach(function() { + beforeEach(function () { v1 = new mockP5.Vector(3, 6, 9); v2 = new mockP5.Vector(1, 1, 1); res = mockP5.Vector.cross(v1, v2); }); - test('should not be undefined', function() { + test("should not be undefined", function () { expect(res).to.not.eql(undefined); }); - test('should return neither v1 nor v2', function() { + test("should return neither v1 nor v2", function () { expect(res).to.not.eql(v1); expect(res).to.not.eql(v2); }); - test('should the cross product of v1 and v2', function() { + test("should the cross product of v1 and v2", function () { expect(res.x).to.eql(-3); expect(res.y).to.eql(6); expect(res.z).to.eql(-3); @@ -1021,58 +1034,58 @@ suite('p5.Vector', function() { }); }); - suite('dist', function() { + suite("dist", function () { var b, c; - beforeEach(function() { + beforeEach(function () { v = new mockP5.Vector(0, 0, 1); b = new mockP5.Vector(0, 0, 5); c = new mockP5.Vector(3, 4, 1); }); - test('should return a number', function() { - expect(typeof v.dist(b) === 'number').to.eql(true); + test("should return a number", function () { + expect(typeof v.dist(b) === "number").to.eql(true); }); - test('should return distance between two vectors', function() { + test("should return distance between two vectors", function () { expect(v.dist(b)).to.eql(4); }); - test('should return distance between two vectors', function() { + test("should return distance between two vectors", function () { expect(v.dist(c)).to.eql(5); }); - test('should be commutative', function() { + test("should be commutative", function () { expect(b.dist(c)).to.eql(c.dist(b)); }); }); - suite('p5.Vector.dist(v1, v2)', function() { + suite("p5.Vector.dist(v1, v2)", function () { var v1, v2; - beforeEach(function() { + beforeEach(function () { v1 = new mockP5.Vector(0, 0, 0); v2 = new mockP5.Vector(0, 3, 4); }); - test('should return a number', function() { - expect(typeof mockP5.Vector.dist(v1, v2) === 'number').to.eql(true); + test("should return a number", function () { + expect(typeof mockP5.Vector.dist(v1, v2) === "number").to.eql(true); }); - test('should be commutative', function() { + test("should be commutative", function () { expect(mockP5.Vector.dist(v1, v2)).to.eql(mockP5.Vector.dist(v2, v1)); }); }); - suite('normalize', function() { - suite('p5.Vector.prototype.normalize() [INSTANCE]', function() { - beforeEach(function() { + suite("normalize", function () { + suite("p5.Vector.prototype.normalize() [INSTANCE]", function () { + beforeEach(function () { v = new mockP5.Vector(1, 1, 1); }); - test('should return the same object', function() { + test("should return the same object", function () { expect(v.normalize()).to.eql(v); }); - test('unit vector should not change values', function() { + test("unit vector should not change values", function () { v.x = 1; v.y = 0; v.z = 0; @@ -1082,7 +1095,7 @@ suite('p5.Vector', function() { expect(v.z).to.eql(0); }); - test('2,2,1 should normalize to ~0.66,0.66,0.33', function() { + test("2,2,1 should normalize to ~0.66,0.66,0.33", function () { v.x = 2; v.y = 2; v.z = 1; @@ -1093,28 +1106,28 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.normalize(v) [CLASS]', function() { + suite("p5.Vector.normalize(v) [CLASS]", function () { var res; - beforeEach(function() { + beforeEach(function () { v = new mockP5.Vector(1, 0, 0); res = mockP5.Vector.normalize(v); }); - test('should not be undefined', function() { + test("should not be undefined", function () { expect(res).to.not.eql(undefined); }); - test('should not return same object v', function() { + test("should not return same object v", function () { expect(res).to.not.equal(v); }); - test('unit vector 1,0,0 should normalize to 1,0,0', function() { + test("unit vector 1,0,0 should normalize to 1,0,0", function () { expect(res.x).to.eql(1); expect(res.y).to.eql(0); expect(res.z).to.eql(0); }); - test('2,2,1 should normalize to ~0.66,0.66,0.33', function() { + test("2,2,1 should normalize to ~0.66,0.66,0.33", function () { v.x = 2; v.y = 2; v.z = 1; @@ -1126,20 +1139,20 @@ suite('p5.Vector', function() { }); }); - suite('limit', function() { + suite("limit", function () { let v; - beforeEach(function() { + beforeEach(function () { v = new mockP5.Vector(5, 5, 5); }); - suite('p5.Vector.prototype.limit() [INSTANCE]', function() { - test('should return the same object', function() { + suite("p5.Vector.prototype.limit() [INSTANCE]", function () { + test("should return the same object", function () { expect(v.limit()).to.equal(v); }); - suite('with a vector larger than the limit', function() { - test('should limit the vector', function() { + suite("with a vector larger than the limit", function () { + test("should limit the vector", function () { v.limit(1); expect(v.x).to.be.closeTo(0.5773, 0.01); expect(v.y).to.be.closeTo(0.5773, 0.01); @@ -1147,8 +1160,8 @@ suite('p5.Vector', function() { }); }); - suite('with a vector smaller than the limit', function() { - test('should not limit the vector', function() { + suite("with a vector smaller than the limit", function () { + test("should not limit the vector", function () { v.limit(8.67); expect(v.x).to.eql(5); expect(v.y).to.eql(5); @@ -1157,13 +1170,13 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.limit() [CLASS]', function() { - test('should not return the same object', function() { + suite("p5.Vector.limit() [CLASS]", function () { + test("should not return the same object", function () { expect(mockP5.Vector.limit(v)).to.not.equal(v); }); - suite('with a vector larger than the limit', function() { - test('should limit the vector', function() { + suite("with a vector larger than the limit", function () { + test("should limit the vector", function () { const res = mockP5.Vector.limit(v, 1); expect(res.x).to.be.closeTo(0.5773, 0.01); expect(res.y).to.be.closeTo(0.5773, 0.01); @@ -1171,8 +1184,8 @@ suite('p5.Vector', function() { }); }); - suite('with a vector smaller than the limit', function() { - test('should not limit the vector', function() { + suite("with a vector smaller than the limit", function () { + test("should not limit the vector", function () { const res = mockP5.Vector.limit(v, 8.67); expect(res.x).to.eql(5); expect(res.y).to.eql(5); @@ -1180,8 +1193,8 @@ suite('p5.Vector', function() { }); }); - suite('when given a target vector', function() { - test('should store limited vector in the target', function() { + suite("when given a target vector", function () { + test("should store limited vector in the target", function () { const target = new mockP5.Vector(0, 0, 0); mockP5.Vector.limit(v, 1, target); expect(target.x).to.be.closeTo(0.5773, 0.01); @@ -1192,26 +1205,26 @@ suite('p5.Vector', function() { }); }); - suite('setMag', function() { + suite("setMag", function () { let v; - beforeEach(function() { + beforeEach(function () { v = new mockP5.Vector(1, 0, 0); }); - suite('p5.Vector.setMag() [INSTANCE]', function() { - test('should return the same object', function() { + suite("p5.Vector.setMag() [INSTANCE]", function () { + test("should return the same object", function () { expect(v.setMag(2)).to.equal(v); }); - test('should set the magnitude of the vector', function() { + test("should set the magnitude of the vector", function () { v.setMag(4); expect(v.x).to.eql(4); expect(v.y).to.eql(0); expect(v.z).to.eql(0); }); - test('should set the magnitude of the vector', function() { + test("should set the magnitude of the vector", function () { v.x = 2; v.y = 3; v.z = -6; @@ -1222,19 +1235,19 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.prototype.setMag() [CLASS]', function() { - test('should not return the same object', function() { + suite("p5.Vector.prototype.setMag() [CLASS]", function () { + test("should not return the same object", function () { expect(mockP5.Vector.setMag(v, 2)).to.not.equal(v); }); - test('should set the magnitude of the vector', function() { + test("should set the magnitude of the vector", function () { const res = mockP5.Vector.setMag(v, 4); expect(res.x).to.eql(4); expect(res.y).to.eql(0); expect(res.z).to.eql(0); }); - test('should set the magnitude of the vector', function() { + test("should set the magnitude of the vector", function () { v.x = 2; v.y = 3; v.z = -6; @@ -1244,8 +1257,8 @@ suite('p5.Vector', function() { expect(res.z).to.eql(-12); }); - suite('when given a target vector', function() { - test('should set the magnitude on the target', function() { + suite("when given a target vector", function () { + test("should set the magnitude on the target", function () { const target = new mockP5.Vector(0, 1, 0); const res = mockP5.Vector.setMag(v, 4, target); expect(target).to.equal(res); @@ -1257,57 +1270,57 @@ suite('p5.Vector', function() { }); }); - suite('heading', function() { - beforeEach(function() { + suite("heading", function () { + beforeEach(function () { v = new mockP5.Vector(); }); - suite('p5.Vector.prototype.heading() [INSTANCE]', function() { - test('should return a number', function() { - expect(typeof v.heading() === 'number').to.eql(true); + suite("p5.Vector.prototype.heading() [INSTANCE]", function () { + test("should return a number", function () { + expect(typeof v.heading() === "number").to.eql(true); }); - test('heading for vector pointing right is 0', function() { + test("heading for vector pointing right is 0", function () { v.x = 1; v.y = 0; v.z = 0; expect(v.heading()).to.be.closeTo(0, 0.01); }); - test('heading for vector pointing down is PI/2', function() { + test("heading for vector pointing down is PI/2", function () { v.x = 0; v.y = 1; v.z = 0; expect(v.heading()).to.be.closeTo(Math.PI / 2, 0.01); }); - test('heading for vector pointing left is PI', function() { + test("heading for vector pointing left is PI", function () { v.x = -1; v.y = 0; v.z = 0; expect(v.heading()).to.be.closeTo(Math.PI, 0.01); }); - suite.todo('with `angleMode(DEGREES)`', function() { - beforeEach(function() { + suite.todo("with `angleMode(DEGREES)`", function () { + beforeEach(function () { mockP5Prototype.angleMode(mockP5.DEGREES); }); - test('heading for vector pointing right is 0', function() { + test("heading for vector pointing right is 0", function () { v.x = 1; v.y = 0; v.z = 0; expect(v.heading()).to.equal(0); }); - test('heading for vector pointing down is 90', function() { + test("heading for vector pointing down is 90", function () { v.x = 0; v.y = 1; v.z = 0; expect(v.heading()).to.equal(90); }); - test('heading for vector pointing left is 180', function() { + test("heading for vector pointing left is 180", function () { v.x = -1; v.y = 0; v.z = 0; @@ -1316,26 +1329,26 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.heading() [CLASS]', function() { - test('should return a number', function() { - expect(typeof mockP5.Vector.heading(v) === 'number').to.eql(true); + suite("p5.Vector.heading() [CLASS]", function () { + test("should return a number", function () { + expect(typeof mockP5.Vector.heading(v) === "number").to.eql(true); }); - test('heading for vector pointing right is 0', function() { + test("heading for vector pointing right is 0", function () { v.x = 1; v.y = 0; v.z = 0; expect(mockP5.Vector.heading(v)).to.be.closeTo(0, 0.01); }); - test('heading for vector pointing down is PI/2', function() { + test("heading for vector pointing down is PI/2", function () { v.x = 0; v.y = 1; v.z = 0; expect(mockP5.Vector.heading(v)).to.be.closeTo(Math.PI / 2, 0.01); }); - test('heading for vector pointing left is PI', function() { + test("heading for vector pointing left is PI", function () { v.x = -1; v.y = 0; v.z = 0; @@ -1344,8 +1357,8 @@ suite('p5.Vector', function() { }); }); - suite('lerp', function() { - test('should return the same object', function() { + suite("lerp", function () { + test("should return the same object", function () { expect(v.lerp()).to.eql(v); }); @@ -1358,29 +1371,29 @@ suite('p5.Vector', function() { // }); // }); - suite('with x, y, z, amt', function() { - beforeEach(function() { + suite("with x, y, z, amt", function () { + beforeEach(function () { v.x = 0; v.y = 0; v.z = 0; v.lerp(2, 2, 2, 0.5); }); - test('should lerp x by amt', function() { + test("should lerp x by amt", function () { expect(v.x).to.eql(1); }); - test('should lerp y by amt', function() { + test("should lerp y by amt", function () { expect(v.y).to.eql(1); }); - test('should lerp z by amt', function() { + test("should lerp z by amt", function () { expect(v.z).to.eql(1); }); }); - suite('with no amt', function() { - test('should assume 0 amt', function() { + suite("with no amt", function () { + test("should assume 0 amt", function () { v.x = 0; v.y = 0; v.z = 0; @@ -1392,56 +1405,56 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.lerp(v1, v2, amt)', function() { + suite("p5.Vector.lerp(v1, v2, amt)", function () { var res, v1, v2; - beforeEach(function() { + beforeEach(function () { v1 = new mockP5.Vector(0, 0, 0); v2 = new mockP5.Vector(2, 2, 2); res = mockP5.Vector.lerp(v1, v2, 0.5); }); - test('should not be undefined', function() { + test("should not be undefined", function () { expect(res).to.not.eql(undefined); }); - test('should be a p5.Vector', function() { + test("should be a p5.Vector", function () { expect(res).to.be.an.instanceof(mockP5.Vector); }); - test('should return neither v1 nor v2', function() { + test("should return neither v1 nor v2", function () { expect(res).to.not.eql(v1); expect(res).to.not.eql(v2); }); - test('should res to be [1, 1, 1]', function() { + test("should res to be [1, 1, 1]", function () { expect(res.x).to.eql(1); expect(res.y).to.eql(1); expect(res.z).to.eql(1); }); }); - suite('v.slerp(w, amt)', function() { + suite("v.slerp(w, amt)", function () { var w; - beforeEach(function() { + beforeEach(function () { v.set(1, 2, 3); w = new mockP5.Vector(4, 6, 8); }); - test('if amt is 0, returns original vector', function() { + test("if amt is 0, returns original vector", function () { v.slerp(w, 0); expect(v.x).to.eql(1); expect(v.y).to.eql(2); expect(v.z).to.eql(3); }); - test('if amt is 1, returns argument vector', function() { + test("if amt is 1, returns argument vector", function () { v.slerp(w, 1); expect(v.x).to.eql(4); expect(v.y).to.eql(6); expect(v.z).to.eql(8); }); - test('if both v and w are 2D, then result will also be 2D.', function() { + test("if both v and w are 2D, then result will also be 2D.", function () { v.set(2, 3, 0); w.set(3, -2, 0); v.slerp(w, 0.3); @@ -1453,7 +1466,7 @@ suite('p5.Vector', function() { expect(v.z).to.eql(0); }); - test('if one side is a zero vector, linearly interpolate.', function() { + test("if one side is a zero vector, linearly interpolate.", function () { v.set(0, 0, 0); w.set(2, 4, 6); v.slerp(w, 0.5); @@ -1462,7 +1475,7 @@ suite('p5.Vector', function() { expect(v.z).to.eql(3); }); - test('If they are pointing in the same direction, linearly interpolate.', function() { + test("If they are pointing in the same direction, linearly interpolate.", function () { v.set(5, 11, 16); w.set(15, 33, 48); v.slerp(w, 0.5); @@ -1472,121 +1485,126 @@ suite('p5.Vector', function() { }); }); - suite('p5.Vector.slerp(v1, v2, amt)', function() { + suite("p5.Vector.slerp(v1, v2, amt)", function () { var res, v1, v2; - beforeEach(function() { + beforeEach(function () { v1 = new mockP5.Vector(1, 0, 0); v2 = new mockP5.Vector(0, 0, 1); - res = mockP5.Vector.slerp(v1, v2, 1/3); + res = mockP5.Vector.slerp(v1, v2, 1 / 3); }); - test('should not be undefined', function() { + test("should not be undefined", function () { expect(res).to.not.eql(undefined); }); - test('should be a p5.Vector', function() { + test("should be a p5.Vector", function () { expect(res).to.be.an.instanceof(mockP5.Vector); }); - test('should return neither v1 nor v2', function() { + test("should return neither v1 nor v2", function () { expect(res).to.not.eql(v1); expect(res).to.not.eql(v2); }); - test('Make sure the interpolation in 1/3 is correct', function() { - expect(res.x).to.be.closeTo(Math.cos(Math.PI/6), 0.00001); + test("Make sure the interpolation in 1/3 is correct", function () { + expect(res.x).to.be.closeTo(Math.cos(Math.PI / 6), 0.00001); expect(res.y).to.be.closeTo(0, 0.00001); - expect(res.z).to.be.closeTo(Math.sin(Math.PI/6), 0.00001); + expect(res.z).to.be.closeTo(Math.sin(Math.PI / 6), 0.00001); }); - test('Make sure the interpolation in -1/3 is correct', function() { - mockP5.Vector.slerp(v1, v2, -1/3, res); - expect(res.x).to.be.closeTo(Math.cos(-Math.PI/6), 0.00001); + test("Make sure the interpolation in -1/3 is correct", function () { + mockP5.Vector.slerp(v1, v2, -1 / 3, res); + expect(res.x).to.be.closeTo(Math.cos(-Math.PI / 6), 0.00001); expect(res.y).to.be.closeTo(0, 0.00001); - expect(res.z).to.be.closeTo(Math.sin(-Math.PI/6), 0.00001); + expect(res.z).to.be.closeTo(Math.sin(-Math.PI / 6), 0.00001); }); - test('Make sure the interpolation in 5/3 is correct', function() { - mockP5.Vector.slerp(v1, v2, 5/3, res); - expect(res.x).to.be.closeTo(Math.cos(5*Math.PI/6), 0.00001); + test("Make sure the interpolation in 5/3 is correct", function () { + mockP5.Vector.slerp(v1, v2, 5 / 3, res); + expect(res.x).to.be.closeTo(Math.cos((5 * Math.PI) / 6), 0.00001); expect(res.y).to.be.closeTo(0, 0.00001); - expect(res.z).to.be.closeTo(Math.sin(5*Math.PI/6), 0.00001); + expect(res.z).to.be.closeTo(Math.sin((5 * Math.PI) / 6), 0.00001); }); }); - suite('p5.Vector.fromAngle(angle)', function() { + suite("p5.Vector.fromAngle(angle)", function () { var res, angle; - beforeEach(function() { + beforeEach(function () { angle = Math.PI / 2; res = mockP5.Vector.fromAngle(angle); }); - test('should be a p5.Vector with values (0,1)', function() { + test("should be a p5.Vector with values (0,1)", function () { expect(res.x).to.be.closeTo(0, 0.01); expect(res.y).to.be.closeTo(1, 0.01); }); }); - suite('p5.Vector.random2D()', function() { + suite("p5.Vector.random2D()", function () { var res; - beforeEach(function() { + beforeEach(function () { res = mockP5.Vector.random2D(); }); - test('should be a unit p5.Vector', function() { + test("should be a unit p5.Vector", function () { expect(res.mag()).to.be.closeTo(1, 0.01); }); }); - suite('p5.Vector.random3D()', function() { + suite("p5.Vector.random3D()", function () { var res; - beforeEach(function() { + beforeEach(function () { res = mockP5.Vector.random3D(); }); - test('should be a unit p5.Vector', function() { + test("should be a unit p5.Vector", function () { expect(res.mag()).to.be.closeTo(1, 0.01); }); }); - suite('array', function() { - beforeEach(function() { + suite("array", function () { + beforeEach(function () { v = new mockP5.Vector(1, 23, 4); }); - suite('p5.Vector.prototype.array() [INSTANCE]', function() { - test('should return an array', function() { + suite("p5.Vector.prototype.array() [INSTANCE]", function () { + test("should return an array", function () { expect(v.array()).to.be.instanceof(Array); }); - test('should return an with the x y and z components', function() { + test("should return an with the x y and z components", function () { expect(v.array()).to.eql([1, 23, 4]); }); }); - suite('p5.Vector.array() [CLASS]', function() { - test('should return an array', function() { + suite("p5.Vector.array() [CLASS]", function () { + test("should return an array", function () { expect(mockP5.Vector.array(v)).to.be.instanceof(Array); }); - test('should return an with the x y and z components', function() { + test("should return an with the x y and z components", function () { expect(mockP5.Vector.array(v)).to.eql([1, 23, 4]); }); }); }); - suite('reflect', function() { - suite('p5.Vector.prototype.reflect() [INSTANCE]', function() { + suite("reflect", function () { + suite("p5.Vector.prototype.reflect() [INSTANCE]", function () { let incoming_x, incoming_y, incoming_z, original_incoming; let x_normal, y_normal, z_normal; - let x_bounce_incoming, x_bounce_outgoing, - y_bounce_incoming, y_bounce_outgoing, - z_bounce_incoming, z_bounce_outgoing; - beforeEach(function() { + let x_bounce_incoming, + x_bounce_outgoing, + y_bounce_incoming, + y_bounce_outgoing, + z_bounce_incoming, + z_bounce_outgoing; + beforeEach(function () { incoming_x = 1; incoming_y = 1; incoming_z = 1; original_incoming = new mockP5.Vector( - incoming_x, incoming_y, incoming_z + incoming_x, + incoming_y, + incoming_z ); x_normal = new mockP5.Vector(3, 0, 0); @@ -1594,50 +1612,56 @@ suite('p5.Vector', function() { z_normal = new mockP5.Vector(0, 0, 3); x_bounce_incoming = new mockP5.Vector( - incoming_x, incoming_y, incoming_z + incoming_x, + incoming_y, + incoming_z ); x_bounce_outgoing = x_bounce_incoming.reflect(x_normal); y_bounce_incoming = new mockP5.Vector( - incoming_x, incoming_y, incoming_z + incoming_x, + incoming_y, + incoming_z ); y_bounce_outgoing = y_bounce_incoming.reflect(y_normal); z_bounce_incoming = new mockP5.Vector( - incoming_x, incoming_y, incoming_z + incoming_x, + incoming_y, + incoming_z ); z_bounce_outgoing = z_bounce_incoming.reflect(z_normal); }); - test('should return a p5.Vector', function() { + test("should return a p5.Vector", function () { expect(x_bounce_incoming).to.be.an.instanceof(mockP5.Vector); expect(y_bounce_incoming).to.be.an.instanceof(mockP5.Vector); expect(z_bounce_incoming).to.be.an.instanceof(mockP5.Vector); }); - test('should update this', function() { + test("should update this", function () { assert.equal(x_bounce_incoming, x_bounce_outgoing); assert.equal(y_bounce_incoming, y_bounce_outgoing); assert.equal(z_bounce_incoming, z_bounce_outgoing); }); - test('x-normal should flip incoming x component and maintain y,z components', function() { + test("x-normal should flip incoming x component and maintain y,z components", function () { expect(x_bounce_outgoing.x).to.be.closeTo(-1, 0.01); expect(x_bounce_outgoing.y).to.be.closeTo(1, 0.01); expect(x_bounce_outgoing.z).to.be.closeTo(1, 0.01); }); - test('y-normal should flip incoming y component and maintain x,z components', function() { + test("y-normal should flip incoming y component and maintain x,z components", function () { expect(y_bounce_outgoing.x).to.be.closeTo(1, 0.01); expect(y_bounce_outgoing.y).to.be.closeTo(-1, 0.01); expect(y_bounce_outgoing.z).to.be.closeTo(1, 0.01); }); - test('z-normal should flip incoming z component and maintain x,y components', function() { + test("z-normal should flip incoming z component and maintain x,y components", function () { expect(z_bounce_outgoing.x).to.be.closeTo(1, 0.01); expect(z_bounce_outgoing.y).to.be.closeTo(1, 0.01); expect(z_bounce_outgoing.z).to.be.closeTo(-1, 0.01); }); - test('angle of incidence should match angle of reflection', function() { + test("angle of incidence should match angle of reflection", function () { expect( Math.abs(x_normal.angleBetween(original_incoming)) ).to.be.closeTo( @@ -1657,7 +1681,7 @@ suite('p5.Vector', function() { 0.01 ); }); - test('should not update surface normal', function() { + test("should not update surface normal", function () { const tolerance = 0.001; assert.closeTo(x_normal.x, 3, tolerance); assert.closeTo(x_normal.y, 0, tolerance); @@ -1671,23 +1695,27 @@ suite('p5.Vector', function() { assert.closeTo(z_normal.y, 0, tolerance); assert.closeTo(z_normal.z, 3, tolerance); }); - }); - suite('p5.Vector.reflect() [CLASS]', function() { + suite("p5.Vector.reflect() [CLASS]", function () { let incoming_x, incoming_y, incoming_z, original_incoming; let x_target, y_target, z_target; let x_normal, y_normal, z_normal; - let x_bounce_incoming, x_bounce_outgoing, - y_bounce_incoming, y_bounce_outgoing, - z_bounce_incoming, z_bounce_outgoing; - - beforeEach(function() { + let x_bounce_incoming, + x_bounce_outgoing, + y_bounce_incoming, + y_bounce_outgoing, + z_bounce_incoming, + z_bounce_outgoing; + + beforeEach(function () { incoming_x = 1; incoming_y = 1; incoming_z = 1; original_incoming = new mockP5.Vector( - incoming_x, incoming_y, incoming_z + incoming_x, + incoming_y, + incoming_z ); x_target = new mockP5.Vector(); y_target = new mockP5.Vector(); @@ -1698,7 +1726,9 @@ suite('p5.Vector', function() { z_normal = new mockP5.Vector(0, 0, 3); x_bounce_incoming = new mockP5.Vector( - incoming_x, incoming_y, incoming_z + incoming_x, + incoming_y, + incoming_z ); x_bounce_outgoing = mockP5.Vector.reflect( x_bounce_incoming, @@ -1707,7 +1737,9 @@ suite('p5.Vector', function() { ); y_bounce_incoming = new mockP5.Vector( - incoming_x, incoming_y, incoming_z + incoming_x, + incoming_y, + incoming_z ); y_bounce_outgoing = mockP5.Vector.reflect( y_bounce_incoming, @@ -1716,7 +1748,9 @@ suite('p5.Vector', function() { ); z_bounce_incoming = new mockP5.Vector( - incoming_x, incoming_y, incoming_z + incoming_x, + incoming_y, + incoming_z ); z_bounce_outgoing = mockP5.Vector.reflect( z_bounce_incoming, @@ -1725,19 +1759,19 @@ suite('p5.Vector', function() { ); }); - test('should return a p5.Vector', function() { + test("should return a p5.Vector", function () { expect(x_bounce_incoming).to.be.an.instanceof(mockP5.Vector); expect(y_bounce_incoming).to.be.an.instanceof(mockP5.Vector); expect(z_bounce_incoming).to.be.an.instanceof(mockP5.Vector); }); - test('should not update this', function() { + test("should not update this", function () { expect(x_bounce_incoming).to.not.equal(x_bounce_outgoing); expect(y_bounce_incoming).to.not.equal(y_bounce_outgoing); expect(z_bounce_incoming).to.not.equal(z_bounce_outgoing); }); - test('should not update surface normal', function() { + test("should not update surface normal", function () { const tolerance = 0.001; assert.closeTo(x_normal.x, 3, tolerance); assert.closeTo(x_normal.y, 0, tolerance); @@ -1752,30 +1786,29 @@ suite('p5.Vector', function() { assert.closeTo(z_normal.z, 3, tolerance); }); - - test('should update target', function() { + test("should update target", function () { assert.equal(x_target, x_bounce_outgoing); assert.equal(y_target, y_bounce_outgoing); assert.equal(z_target, z_bounce_outgoing); }); - test('x-normal should flip incoming x component and maintain y,z components', function() { + test("x-normal should flip incoming x component and maintain y,z components", function () { expect(x_bounce_outgoing.x).to.be.closeTo(-1, 0.01); expect(x_bounce_outgoing.y).to.be.closeTo(1, 0.01); expect(x_bounce_outgoing.z).to.be.closeTo(1, 0.01); }); - test('y-normal should flip incoming y component and maintain x,z components', function() { + test("y-normal should flip incoming y component and maintain x,z components", function () { expect(y_bounce_outgoing.x).to.be.closeTo(1, 0.01); expect(y_bounce_outgoing.y).to.be.closeTo(-1, 0.01); expect(y_bounce_outgoing.z).to.be.closeTo(1, 0.01); }); - test('z-normal should flip incoming z component and maintain x,y components', function() { + test("z-normal should flip incoming z component and maintain x,y components", function () { expect(z_bounce_outgoing.x).to.be.closeTo(1, 0.01); expect(z_bounce_outgoing.y).to.be.closeTo(1, 0.01); expect(z_bounce_outgoing.z).to.be.closeTo(-1, 0.01); }); - test('angle of incidence should match angle of reflection', function() { + test("angle of incidence should match angle of reflection", function () { expect( Math.abs(x_normal.angleBetween(original_incoming)) ).to.be.closeTo( @@ -1798,61 +1831,61 @@ suite('p5.Vector', function() { }); }); - suite('mag', function() { + suite("mag", function () { const MAG = 3.7416573867739413; // sqrt(1*1 + 2*2 + 3*3) let v0; let v1; - beforeEach(function() { + beforeEach(function () { v0 = new mockP5.Vector(0, 0, 0); v1 = new mockP5.Vector(1, 2, 3); }); - suite('p5.Vector.prototype.mag() [INSTANCE]', function() { - test('should return the magnitude of the vector', function() { + suite("p5.Vector.prototype.mag() [INSTANCE]", function () { + test("should return the magnitude of the vector", function () { expect(v0.mag()).to.eql(0); expect(v1.mag()).to.eql(MAG); }); }); - suite('p5.Vector.mag() [CLASS]', function() { - test('should return the magnitude of the vector', function() { + suite("p5.Vector.mag() [CLASS]", function () { + test("should return the magnitude of the vector", function () { expect(mockP5.Vector.mag(v0)).to.eql(0); expect(mockP5.Vector.mag(v1)).to.eql(MAG); }); }); }); - suite('magSq', function() { + suite("magSq", function () { const MAG = 14; // 1*1 + 2*2 + 3*3 let v0; let v1; - beforeEach(function() { + beforeEach(function () { v0 = new mockP5.Vector(0, 0, 0); v1 = new mockP5.Vector(1, 2, 3); }); - suite('p5.Vector.prototype.magSq() [INSTANCE]', function() { - test('should return the magnitude of the vector', function() { + suite("p5.Vector.prototype.magSq() [INSTANCE]", function () { + test("should return the magnitude of the vector", function () { expect(v0.magSq()).to.eql(0); expect(v1.magSq()).to.eql(MAG); }); }); - suite('p5.Vector.magSq() [CLASS]', function() { - test('should return the magnitude of the vector', function() { + suite("p5.Vector.magSq() [CLASS]", function () { + test("should return the magnitude of the vector", function () { expect(mockP5.Vector.magSq(v0)).to.eql(0); expect(mockP5.Vector.magSq(v1)).to.eql(MAG); }); }); }); - suite('equals', function() { - suite('p5.Vector.prototype.equals() [INSTANCE]', function() { - test('should return false for parameters inequal to the vector', function() { + suite("equals", function () { + suite("p5.Vector.prototype.equals() [INSTANCE]", function () { + test("should return false for parameters inequal to the vector", function () { const v1 = new mockP5.Vector(0, -1, 1); const v2 = new mockP5.Vector(1, 2, 3); const a2 = [1, 2, 3]; @@ -1861,26 +1894,26 @@ suite('p5.Vector', function() { expect(v1.equals(1, 2, 3)).to.be.false; }); - test('should return true for equal vectors', function() { + test("should return true for equal vectors", function () { const v1 = new mockP5.Vector(0, -1, 1); const v2 = new mockP5.Vector(0, -1, 1); expect(v1.equals(v2)).to.be.true; }); - test('should return true for arrays equal to the vector', function() { + test("should return true for arrays equal to the vector", function () { const v1 = new mockP5.Vector(0, -1, 1); const a1 = [0, -1, 1]; expect(v1.equals(a1)).to.be.true; }); - test('should return true for arguments equal to the vector', function() { + test("should return true for arguments equal to the vector", function () { const v1 = new mockP5.Vector(0, -1, 1); expect(v1.equals(0, -1, 1)).to.be.true; }); }); - suite('p5.Vector.equals() [CLASS]', function() { - test('should return false for inequal parameters', function() { + suite("p5.Vector.equals() [CLASS]", function () { + test("should return false for inequal parameters", function () { const v1 = new mockP5.Vector(0, -1, 1); const v2 = new mockP5.Vector(1, 2, 3); const a2 = [1, 2, 3]; @@ -1889,24 +1922,147 @@ suite('p5.Vector', function() { expect(mockP5.Vector.equals(a2, v1)).to.be.false; }); - test('should return true for equal vectors', function() { + test("should return true for equal vectors", function () { const v1 = new mockP5.Vector(0, -1, 1); const v2 = new mockP5.Vector(0, -1, 1); expect(mockP5.Vector.equals(v1, v2)).to.be.true; }); - test('should return true for equal vectors and arrays', function() { + test("should return true for equal vectors and arrays", function () { const v1 = new mockP5.Vector(0, -1, 1); const a1 = [0, -1, 1]; expect(mockP5.Vector.equals(v1, a1)).to.be.true; expect(mockP5.Vector.equals(a1, v1)).to.be.true; }); - test('should return true for equal arrays', function() { + test("should return true for equal arrays", function () { const a1 = [0, -1, 1]; const a2 = [0, -1, 1]; expect(mockP5.Vector.equals(a1, a2)).to.be.true; }); }); }); + + suite("set values", function () { + beforeEach(function () { + v = new mockP5.Vector(); + }); + + test("should set values to [0,0,0] if values array is empty", function () { + v.values = []; + assert.equal(v.x, 0); + assert.equal(v.y, 0); + assert.equal(v.z, 0); + assert.equal(v.dimensions, 2); + }); + }); + suite("get value", function () { + test("should return element in range of a non empty vector", function () { + let vect = new mockP5.Vector(1, 2, 3, 4); + assert.equal(vect.getValue(0), 1); + assert.equal(vect.getValue(1), 2); + assert.equal(vect.getValue(2), 3); + assert.equal(vect.getValue(3), 4); + }); + + test.fails( + "should throw friendly error if attempting to get element outside lenght", + function () { + let vect = new mockP5.Vector(1, 2, 3, 4); + assert.equal(vect.getValue(5), 1); + } + ); + }); + + suite("set value", function () { + test("should set value of element in range", function () { + let vect = new mockP5.Vector(1, 2, 3, 4); + vect.setValue(0, 7); + assert.equal(vect.getValue(0), 7); + assert.equal(vect.getValue(1), 2); + assert.equal(vect.getValue(2), 3); + assert.equal(vect.getValue(3), 4); + }); + + test.fails( + "should throw friendly error if attempting to set element outside lenght", + function () { + let vect = new mockP5.Vector(1, 2, 3, 4); + vect.setValue(100, 7); + } + ); + }); + + describe("get w", () => { + it("should return the w component of the vector", () => { + v = new mockP5.Vector(1, 2, 3, 4); + expect(v.w).toBe(4); + }); + + it("should return 0 if w component is not set", () => { + v = new mockP5.Vector(1, 2, 3); + expect(v.w).toBe(0); + }); + }); + + describe("set w", () => { + it("should set 4th dimension of vector to w value if it exists", () => { + v = new mockP5.Vector(1, 2, 3, 4); + v.w = 7; + expect(v.x).toBe(1); + expect(v.y).toBe(2); + expect(v.z).toBe(3); + expect(v.w).toBe(7); + }); + + it("should throw error if trying to set w if vector dimensions is less than 4", () => { + v = new mockP5.Vector(1, 2); + v.w = 5; + console.log(v); + console.log(v.w); + expect(v.w).toBe(0); //TODO: Check this, maybe this should fail + }); + }); + + describe("vector to string", () => { + it("should return the string version of a vector", () => { + v = new mockP5.Vector(1, 2, 3, 4); + expect(v.toString()).toBe("[1, 2, 3, 4]"); + }); + }); + + describe("set heading", () => { + it("should rotate a 2D vector by specified angle without changing magnitude", () => { + v = new mockP5.Vector(0, 2); + const mag = v.mag(); + expect(v.setHeading(2 * Math.PI).mag()).toBe(mag); + expect(v.x).toBe(2); + expect(v.y).toBe(-4.898587196589413e-16); + }); + }); + + describe("clamp to zero", () => { + it("should clamp values cloze to zero to zero, with Number.epsilon value", () => { + v = new mockP5.Vector(0, 1, 0.5, 0.1, 0.0000000000000001); + expect(v.clampToZero().values).toEqual([0, 1, 0.5, 0.1, 0]); + }); + }); + + suite("p5.Vector.fromAngles()", function () { + it("should create a v3ctor froma pair of ISO spherical angles", () => { + let vect = mockP5.Vector.fromAngles(0, 0); + expect(vect.values).toEqual([0, -1, 0]); + }); + }); + + suite("p5.Vector.rotate()", function () { + it("should rotate the vector (only 2D vectors) by the given angle; magnitude remains the same.", () => { + v = new mockP5.Vector(0, 1, 2); + let target = new mockP5.Vector(); + mockP5.Vector.rotate(v, 1 * Math.PI, target); + expect(target.values).toEqual([ + -4.10759023698152e-16, -2.23606797749979, 2, + ]); + }); + }); }); diff --git a/test/unit/webgl/p5.Matrix.js b/test/unit/webgl/p5.Matrix.js deleted file mode 100644 index f1d1202763..0000000000 --- a/test/unit/webgl/p5.Matrix.js +++ /dev/null @@ -1,413 +0,0 @@ -import p5 from '../../../src/app.js'; - -/* eslint-disable indent */ -var mat4 = [ - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16 -]; - -var other = [ - 1, 5, 9, 13, - 2, 6, 10, 14, - 3, 7, 11, 15, - 4, 8, 12, 16 -]; - -var mat3 = [ - 1, 2, 3, - 4, 5, 6, - 7, 8, 9 -]; -/* eslint-enable indent */ - -suite('p5.Matrix', function() { - var myp5; - - beforeAll(function() { - new p5(function(p) { - p.setup = function() { - myp5 = p; - }; - }); - }); - - afterAll(function() { - myp5.remove(); - }); - - suite('construction', function() { - test('new p5.Matrix()', function() { - var m = new p5.Matrix(); - assert.instanceOf(m, p5.Matrix); - assert.isUndefined(m.mat3); - /* eslint-disable indent */ - assert.deepEqual([].slice.call(m.mat4), [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ]); - /* eslint-enable indent */ - }); - - test('new p5.Matrix(array)', function() { - var m = new p5.Matrix(mat4); - assert.instanceOf(m, p5.Matrix); - assert.isUndefined(m.mat3); - assert.deepEqual([].slice.call(m.mat4), mat4); - }); - - test('new p5.Matrix(mat3)', function() { - var m = new p5.Matrix('mat3', mat3); - assert.instanceOf(m, p5.Matrix); - assert.isUndefined(m.mat4); - assert.deepEqual([].slice.call(m.mat3), mat3); - }); - - test('identity()', function() { - var m = p5.Matrix.identity(); - assert.instanceOf(m, p5.Matrix); - assert.isUndefined(m.mat3); - /* eslint-disable indent */ - assert.deepEqual([].slice.call(m.mat4), [ - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - ]); - /* eslint-enable indent */ - }); - }); - - suite('set', function() { - test('p5.Matrix', function() { - var m = new p5.Matrix(); - m.set(new p5.Matrix(mat4)); - assert.deepEqual([].slice.call(m.mat4), mat4); - }); - - test('array', function() { - var m = new p5.Matrix(); - m.set(mat4); - assert.deepEqual([].slice.call(m.mat4), mat4); - }); - - test('arguments', function() { - var m = new p5.Matrix(); - m.set.apply(m, mat4); - assert.notEqual(m.mat4, mat4); - assert.deepEqual([].slice.call(m.mat4), mat4); - }); - }); - - suite('get / copy', function() { - test('get', function() { - var m = new p5.Matrix(mat4); - var m2 = m.get(); - assert.notEqual(m, m2); - assert.equal(m.mat4, m2.mat4); - }); - test('copy', function() { - var m = new p5.Matrix(mat4); - var m2 = m.copy(); - assert.notEqual(m, m2); - assert.notEqual(m.mat4, m2.mat4); - assert.deepEqual([].slice.call(m.mat4), [].slice.call(m2.mat4)); - }); - }); - - suite('mult', function() { - /* eslint-disable indent */ - var mm = [ - 30, 70, 110, 150, - 70, 174, 278, 382, - 110, 278, 446, 614, - 150, 382, 614, 846 - ]; - /* eslint-enable indent */ - - test('self', function() { - var m = new p5.Matrix(mat4.slice()); - m.mult(m); - /* eslint-disable indent */ - assert.deepEqual([].slice.call(m.mat4), [ - 90, 100, 110, 120, - 202, 228, 254, 280, - 314, 356, 398, 440, - 426, 484, 542, 600 - ]); - /* eslint-enable indent */ - }); - - test('p5.Matrix', function() { - var m1 = new p5.Matrix(mat4.slice()); - var m2 = new p5.Matrix(other); - m1.mult(m2); - assert.deepEqual([].slice.call(m1.mat4), mm); - }); - - test('array', function() { - var m = new p5.Matrix(mat4.slice()); - m.mult(other); - assert.deepEqual([].slice.call(m.mat4), mm); - }); - - test('arguments', function() { - var m = new p5.Matrix(mat4.slice()); - m.mult.apply(m, other); - assert.deepEqual([].slice.call(m.mat4), mm); - }); - }); - - suite('apply', function() { - /* eslint-disable indent */ - var am = [ - 276, 304, 332, 360, - 304, 336, 368, 400, - 332, 368, 404, 440, - 360, 400, 440, 480 - ]; - /* eslint-enable indent */ - - test('self', function() { - var m = new p5.Matrix(mat4.slice()); - m.apply(m); - /* eslint-disable indent */ - assert.deepEqual([].slice.call(m.mat4), [ - 90, 100, 110, 120, - 202, 228, 254, 280, - 314, 356, 398, 440, - 426, 484, 542, 600 - ]); - /* eslint-enable indent */ - }); - - test('p5.Matrix', function() { - var m1 = new p5.Matrix(mat4.slice()); - var m2 = new p5.Matrix(other); - m1.apply(m2); - assert.deepEqual([].slice.call(m1.mat4), am); - }); - - test('array', function() { - var m = new p5.Matrix(mat4.slice()); - m.apply(other); - assert.deepEqual([].slice.call(m.mat4), am); - }); - - test('arguments', function() { - var m = new p5.Matrix(mat4.slice()); - m.apply.apply(m, other); - assert.deepEqual([].slice.call(m.mat4), am); - }); - }); - - suite('scale', function() { - /* eslint-disable indent */ - var sm = [ - 2, 4, 6, 8, - 15, 18, 21, 24, - 45, 50, 55, 60, - 13, 14, 15, 16 - ]; - /* eslint-enable indent */ - - test('p5.Vector', function() { - var m = new p5.Matrix(mat4.slice()); - var v = myp5.createVector(2, 3, 5); - m.scale(v); - assert.notEqual(m.mat4, mat4); - assert.deepEqual([].slice.call(m.mat4), sm); - }); - - test('array', function() { - var m = new p5.Matrix(mat4.slice()); - m.scale([2, 3, 5]); - assert.notEqual(m.mat4, mat4); - assert.deepEqual([].slice.call(m.mat4), sm); - }); - - test('arguments', function() { - var m = new p5.Matrix(mat4.slice()); - m.scale(2, 3, 5); - assert.notEqual(m.mat4, mat4); - assert.deepEqual([].slice.call(m.mat4), sm); - }); - }); - - suite('rotate', function() { - /* eslint-disable max-len */ - var rm = [ - 1.433447866601989, 2.5241247073503885, 3.6148015480987885, 4.7054783888471885, - 6.460371405020393, 7.054586073938033, 7.648800742855675, 8.243015411773316, - 7.950398010346969, 9.157598472697025, 10.36479893504708, 11.571999397397136, - 13, 14, 15, 16 - ]; - /* eslint-enable max-len */ - - test('p5.Vector', function() { - var m = new p5.Matrix(mat4.slice()); - var v = myp5.createVector(2, 3, 5); - m.rotate(45 * myp5.DEG_TO_RAD, v); - assert.deepEqual([].slice.call(m.mat4), rm); - }); - - test('array', function() { - var m = new p5.Matrix(mat4.slice()); - m.rotate(45 * myp5.DEG_TO_RAD, [2, 3, 5]); - assert.deepEqual([].slice.call(m.mat4), rm); - }); - - test('arguments', function() { - var m = new p5.Matrix(mat4.slice()); - m.rotate(45 * myp5.DEG_TO_RAD, 2, 3, 5); - assert.deepEqual([].slice.call(m.mat4), rm); - }); - }); - - - suite('p5.Matrix3x3', function() { - test('apply copy() to 3x3Matrix', function() { - const m = new p5.Matrix('mat3', [ - 1, 2, 3, - 4, 5, 6, - 7, 8, 9 - ]); - const mCopy = m.copy(); - - // The matrix created by copying is different from the original matrix - assert.notEqual(m, mCopy); - assert.notEqual(m.mat3, mCopy.mat3); - - // The matrix created by copying has the same elements as the original matrix - assert.deepEqual([].slice.call(m.mat3), [].slice.call(mCopy.mat3)); - }); - test('transpose3x3()', function() { - const m = new p5.Matrix('mat3', [ - 1, 2, 3, - 4, 5, 6, - 7, 8, 9 - ]); - const mTp = new p5.Matrix('mat3', [ - 1, 4, 7, - 2, 5, 8, - 3, 6, 9 - ]); - - // If no arguments, transpose itself - m.transpose3x3(); - assert.deepEqual([].slice.call(m.mat3), [].slice.call(mTp.mat3)); - - // If there is an array of arguments, set it by transposing it - m.transpose3x3([ - 1, 2, 3, - 10, 20, 30, - 100, 200, 300 - ]); - assert.deepEqual([].slice.call(m.mat3), [ - 1, 10, 100, - 2, 20, 200, - 3, 30, 300 - ]); - }); - test('mult3x3()', function() { - const m = new p5.Matrix('mat3', [ - 1, 2, 3, - 4, 5, 6, - 7, 8, 9 - ]); - const m1 = m.copy(); - const m2 = m.copy(); - const multMatrix = new p5.Matrix('mat3', [ - 1, 1, 1, - 0, 1, 1, - 1, 0, 1 - ]); - - // When taking a matrix as an argument - m.mult3x3(multMatrix); - assert.deepEqual([].slice.call(m.mat3), [ - 4, 3, 6, - 10, 9, 15, - 16, 15, 24 - ]); - - // if the argument is an array or an enumerated number - m1.mult3x3(1, 1, 1, 0, 1, 1, 1, 0, 1); - m2.mult3x3([1, 1, 1, 0, 1, 1, 1, 0, 1]); - assert.deepEqual([].slice.call(m.mat3), [].slice.call(m1.mat3)); - assert.deepEqual([].slice.call(m.mat3), [].slice.call(m2.mat3)); - }); - test('column() and row()', function(){ - const m = new p5.Matrix('mat3', [ - // The matrix data is stored column-major, so each line below is - // a column rather than a row. Imagine you are looking at the - // transpose of the matrix in the source code. - 1, 2, 3, - 4, 5, 6, - 7, 8, 9 - ]); - const column0 = m.column(0); - const column1 = m.column(1); - const column2 = m.column(2); - assert.deepEqual(column0.array(), [1, 2, 3]); - assert.deepEqual(column1.array(), [4, 5, 6]); - assert.deepEqual(column2.array(), [7, 8, 9]); - const row0 = m.row(0); - const row1 = m.row(1); - const row2 = m.row(2); - assert.deepEqual(row0.array(), [1, 4, 7]); - assert.deepEqual(row1.array(), [2, 5, 8]); - assert.deepEqual(row2.array(), [3, 6, 9]); - }); - test('diagonal()', function() { - const m = new p5.Matrix('mat3', [ - 1, 2, 3, - 4, 5, 6, - 7, 8, 9 - ]); - const m4x4 = new p5.Matrix([ - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16 - ]); - assert.deepEqual(m.diagonal(), [1, 5, 9]); - assert.deepEqual(m4x4.diagonal(), [1, 6, 11, 16]); - }); - test('multiplyVec3', function() { - const m = new p5.Matrix('mat3', [ - 1, 2, 3, - 4, 5, 6, - 7, 8, 9 - ]); - const multVector = new p5.Vector(3, 2, 1); - const result = m.multiplyVec3(multVector); - assert.deepEqual(result.array(), [18, 24, 30]); - // If there is a target, set result and return that. - const target = new p5.Vector(); - m.multiplyVec3(multVector, target); - assert.deepEqual(target.array(), [18, 24, 30]); - }); - test('createSubMatrix3x3', function() { - const m4x4 = new p5.Matrix([ - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16 - ]); - const result = new p5.Matrix('mat3', [ - 1, 2, 3, - 5, 6, 7, - 9, 10, 11 - ]); - const subMatrix3x3 = m4x4.createSubMatrix3x3(); - assert.deepEqual( - [].slice.call(result.mat3), - [].slice.call(subMatrix3x3.mat3) - ); - }); - }); -}); diff --git a/test/unit/webgl/p5.RendererGL.js b/test/unit/webgl/p5.RendererGL.js index b3abec9cd4..a71a01b2fe 100644 --- a/test/unit/webgl/p5.RendererGL.js +++ b/test/unit/webgl/p5.RendererGL.js @@ -1,5 +1,6 @@ import p5 from '../../../src/app.js'; import '../../js/chai_helpers'; +const toArray = (typedArray) => Array.from(typedArray); suite('p5.RendererGL', function() { var myp5; @@ -928,7 +929,7 @@ suite('p5.RendererGL', function() { // cam1 is applied right now so technically this is redundant myp5.setCamera(cam1); const cam1Matrix = cam1.cameraMatrix.copy(); - assert.deepEqual(myp5._renderer.states.uViewMatrix.mat4, cam1Matrix.mat4); + assert.deepEqual(toArray(myp5._renderer.states.uViewMatrix.mat4), toArray(cam1Matrix.mat4)); // Translation only changes the model matrix myp5.translate(100, 0, 0); @@ -936,12 +937,12 @@ suite('p5.RendererGL', function() { myp5._renderer.states.uModelMatrix.mat4, origModelMatrix.mat4 ); - assert.deepEqual(myp5._renderer.states.uViewMatrix.mat4, cam1Matrix.mat4); + assert.deepEqual(toArray(myp5._renderer.states.uViewMatrix.mat4), toArray(cam1Matrix.mat4)); // Switchnig cameras only changes the view matrix const transformedModel = myp5._renderer.states.uModelMatrix.copy(); myp5.setCamera(cam2); - assert.deepEqual(myp5._renderer.states.uModelMatrix.mat4, transformedModel.mat4); + assert.deepEqual(toArray(myp5._renderer.states.uModelMatrix.mat4), toArray(transformedModel.mat4)); assert.notDeepEqual(myp5._renderer.states.uViewMatrix.mat4, cam1Matrix.mat4); }); });