UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

248 lines (203 loc) • 7.17 kB
import { assert } from "../../assert.js"; import { DataType2TypedArrayConstructorMapping } from "../../binary/type/DataType2TypedArrayConstructorMapping.js"; import { array_copy } from "../../collection/array/array_copy.js"; /** * Represents a square matrix (number of rows equals number of columns). * Data is stored in a typed array. */ export class SquareMatrix { /** * @param {number} size Side length of the matrix (e.g. '3' for a 3x3 matrix). * @param {BinaryDataType|string} type Data type of the elements. Must be a valid BinaryDataType (e.g., "uint8", "float32"). * @throws {Error} If the provided type is not supported. */ constructor(size, type) { assert.isNonNegativeInteger(size, 'size'); const TypedArray = DataType2TypedArrayConstructorMapping[type]; if (TypedArray === undefined) { throw new Error(`Unsupported type '${type}'`); } /** * Side length of the matrix. * @type {number} */ this.size = size; /** * Data type of matrix elements. * @type {BinaryDataType} */ this.type = type; /** * Matrix data, stored as a typed array. Column-major order. * @type {number[]} */ this.data = new TypedArray(size * size); } /** * @returns {number} Side length of the matrix. */ get n() { return this.size; } /** * @returns {number} Total number of elements in the matrix (size * size). */ get length() { return this.size * this.size; } /** * Returns direct reference to underlying data, modifying it WILL affect the matrix. * @returns {number[]} */ get val() { return this.data; } /** * Fills the entire matrix with the given value. * @param {number} v Value to fill the matrix with. * @returns {void} */ fill(v) { this.data.fill(v); } /** * Subtracts another matrix from this matrix (this = this - other). * @param {SquareMatrix} other The matrix to subtract. Must be of the same size. */ subtract(other) { this.subtractMatrices(this, other); } /** * Subtracts matrix 'b' from matrix 'a', storing the result in this matrix (this = a - b). * Component-wise operation. * @param {SquareMatrix} a The first matrix. * @param {SquareMatrix} b The second matrix. */ subtractMatrices(a, b) { const size = this.size; assert.equal(a.size, size, 'a is of wrong size'); assert.equal(b.size, size, 'b is of wrong size'); const data_length = size * size; const a_data = a.data; const b_data = b.data; const data = this.data; for (let i = 0; i < data_length; i++) { data[i] = a_data[i] - b_data[i]; } } /** * Negates all elements of the matrix (multiplies each element by -1). */ negate() { const data = this.data; const data_length = data.length; for (let i = 0; i < data_length; i++) { data[i] = -data[i]; } } /** * Sets all elements of the matrix to 0. */ clear() { this.data.fill(0); } /** * Set diagonal to 1 * NOTE: if the other cells are 0s - it will produce identity matrix, but those cells will not be written explicitly */ eye() { const size = this.size; for (let i = 0; i < size; i++) { this.data[i * (size + 1)] = 1; } } /** * Copies the elements of another matrix into this matrix. * @param {SquareMatrix} other The matrix to copy from. Must be the same size. */ copy(other) { assert.equal(this.size, other.size, 'difference sizes'); this.data.set(other.data); } /** * Creates a new matrix that is a copy of this matrix. * @returns {SquareMatrix} A new matrix with the same elements. */ clone() { const r = new SquareMatrix(this.size, this.type); r.copy(this); return r; } /** * Transposes the matrix in-place (swaps rows and columns). */ transpose() { const size = this.size; for (let y = 0; y < size; y++) { for (let x = y + 1; x < size; x++) { const v0 = this.getCellValue(x, y); const v1 = this.getCellValue(y, x); this.setCellValue(x, y, v1); this.setCellValue(y, x, v0); } } } /** * Populates matrix from a 1D array. * @param {number[]} arr Source array. Must have at least size*size elements. */ fromArray(arr) { this.data.set(arr); } /** * Copies matrix elements into a 1D array. * @param {number[]} [destination] Array to store matrix into. Creates a new array if not provided. * @param {number} [offset=0] Starting index in the destination array. * @returns {number[]} The array containing the matrix data. */ toArray( destination = new Array(this.length), offset = 0 ) { array_copy(this.data, 0, destination, offset, this.length); return destination; } /** * Sets the value of a cell in the matrix. * @param {number} row_index Row index (0-based). * @param {number} column_index Column index (0-based). * @param {number} value The value to set. */ setCellValue(row_index, column_index, value) { assert.isNonNegativeInteger(row_index, 'row_index'); assert.isNonNegativeInteger(column_index, 'row_index'); assert.lessThan(row_index, this.size, 'row overflow'); assert.lessThan(column_index, this.size, 'column overflow'); assert.isNumber(value, 'value'); this.data[this.size * column_index + row_index] = value; } /** * Retrieves the value of a cell in the matrix. * @param {number} row_index Row index (0-based). * @param {number} column_index Column index (0-based). * @returns {number} The value of the cell. */ getCellValue(row_index, column_index) { assert.isNonNegativeInteger(row_index, 'row_index'); assert.isNonNegativeInteger(column_index, 'row_index'); assert.lessThan(row_index, this.size, 'row overflow'); assert.lessThan(column_index, this.size, 'column overflow'); return this.data[this.size * column_index + row_index]; } /** * Read values at the diagonal, from left to right, top to bottom * @param {number[]|Float32Array|Float64Array} result Array to store the diagonal. */ readDiagonal(result) { const n = this.size; const source = this.data; for (let i = 0; i < n; i++) { result[i] = source[i * (n + 1)]; } } }