@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
252 lines (214 loc) • 6.57 kB
JavaScript
import { assert } from "../../assert.js";
import { UNIT_DIMENSION_COUNT } from "./UnitDimension.js";
/**
* Represents a physical unit as a vector of exponents over the 7 SI base dimensions.
*
* Composition rules:
* - multiplying two units adds their exponents component-wise (e.g. m * s = m·s, m * m = m^2)
* - dividing two units subtracts their exponents (e.g. m / s = m/s, m / m = dimensionless)
* - raising a unit to a power scales every exponent by that power (e.g. m^3 = volume)
*
* Storage uses a fixed-size {@link Float64Array} so fractional exponents (e.g. sqrt of a unit)
* are representable, and so that composition can be performed without per-call allocation.
*/
export class UnitMatrix {
constructor() {
/**
* Exponent for each SI base dimension, indexed by {@link UnitDimension}.
* @type {Float64Array}
*/
this.exponents = new Float64Array(UNIT_DIMENSION_COUNT);
}
/**
* Set the exponent of a single base dimension.
* @param {number} dimension Index from {@link UnitDimension}.
* @param {number} value
*/
set_exponent(dimension, value) {
assert.isNonNegativeInteger(dimension, 'dimension');
assert.lessThan(dimension, UNIT_DIMENSION_COUNT, 'dimension overflow');
assert.isNumber(value, 'value');
this.exponents[dimension] = value;
}
/**
* Read the exponent of a single base dimension.
* @param {number} dimension Index from {@link UnitDimension}.
* @returns {number}
*/
get_exponent(dimension) {
assert.isNonNegativeInteger(dimension, 'dimension');
assert.lessThan(dimension, UNIT_DIMENSION_COUNT, 'dimension overflow');
return this.exponents[dimension];
}
/**
* Bulk-set every dimension exponent in a single call.
* @param {number} length
* @param {number} mass
* @param {number} time
* @param {number} electric_current
* @param {number} temperature
* @param {number} amount_of_substance
* @param {number} luminous_intensity
* @returns {this}
*/
set(length, mass, time, electric_current, temperature, amount_of_substance, luminous_intensity) {
const e = this.exponents;
e[0] = length;
e[1] = mass;
e[2] = time;
e[3] = electric_current;
e[4] = temperature;
e[5] = amount_of_substance;
e[6] = luminous_intensity;
return this;
}
/**
* Reset to dimensionless (every exponent is 0).
*/
set_zero() {
this.exponents.fill(0);
}
/**
* Copy values from another unit matrix.
* @param {UnitMatrix} other
*/
copy(other) {
this.exponents.set(other.exponents);
}
/**
* Allocate a new unit matrix with the same exponents.
* @returns {UnitMatrix}
*/
clone() {
const r = new UnitMatrix();
r.copy(this);
return r;
}
/**
* Strict structural equality between two unit matrices.
* Two matrices are equal when every exponent matches exactly.
* @param {UnitMatrix} other
* @returns {boolean}
*/
equals(other) {
const a = this.exponents;
const b = other.exponents;
for (let i = 0; i < UNIT_DIMENSION_COUNT; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
/**
* Whether this matrix represents a dimensionless quantity (all exponents are 0).
* @returns {boolean}
*/
is_dimensionless() {
const e = this.exponents;
for (let i = 0; i < UNIT_DIMENSION_COUNT; i++) {
if (e[i] !== 0) {
return false;
}
}
return true;
}
/**
* Multiply this matrix by another in-place (this *= other).
* Adds exponents component-wise.
* @param {UnitMatrix} other
*/
multiply(other) {
const e = this.exponents;
const o = other.exponents;
for (let i = 0; i < UNIT_DIMENSION_COUNT; i++) {
e[i] += o[i];
}
}
/**
* Divide this matrix by another in-place (this /= other).
* Subtracts exponents component-wise.
* @param {UnitMatrix} other
*/
divide(other) {
const e = this.exponents;
const o = other.exponents;
for (let i = 0; i < UNIT_DIMENSION_COUNT; i++) {
e[i] -= o[i];
}
}
/**
* Store the product a * b into this matrix (this = a * b).
* Safe to call with this === a or this === b.
* @param {UnitMatrix} a
* @param {UnitMatrix} b
*/
multiply_matrices(a, b) {
const out = this.exponents;
const ax = a.exponents;
const bx = b.exponents;
for (let i = 0; i < UNIT_DIMENSION_COUNT; i++) {
out[i] = ax[i] + bx[i];
}
}
/**
* Store the quotient a / b into this matrix (this = a / b).
* Safe to call with this === a or this === b.
* @param {UnitMatrix} a
* @param {UnitMatrix} b
*/
divide_matrices(a, b) {
const out = this.exponents;
const ax = a.exponents;
const bx = b.exponents;
for (let i = 0; i < UNIT_DIMENSION_COUNT; i++) {
out[i] = ax[i] - bx[i];
}
}
/**
* Raise this matrix to a power in-place (this **= n).
* @param {number} n
*/
power(n) {
assert.isNumber(n, 'n');
const e = this.exponents;
for (let i = 0; i < UNIT_DIMENSION_COUNT; i++) {
e[i] *= n;
}
}
/**
* Store a ** n into this matrix (this = a ** n).
* Safe to call with this === a.
* @param {UnitMatrix} a
* @param {number} n
*/
power_of_matrix(a, n) {
assert.isNumber(n, 'n');
const out = this.exponents;
const ax = a.exponents;
for (let i = 0; i < UNIT_DIMENSION_COUNT; i++) {
out[i] = ax[i] * n;
}
}
/**
* Invert in-place: every exponent is negated, so this becomes 1 / this.
*/
invert() {
const e = this.exponents;
for (let i = 0; i < UNIT_DIMENSION_COUNT; i++) {
e[i] = -e[i];
}
}
/**
* Store 1 / a into this matrix.
* Safe to call with this === a.
* @param {UnitMatrix} a
*/
invert_matrix(a) {
const out = this.exponents;
const ax = a.exponents;
for (let i = 0; i < UNIT_DIMENSION_COUNT; i++) {
out[i] = -ax[i];
}
}
}