@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
248 lines (203 loc) • 7.17 kB
JavaScript
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)];
}
}
}