@amandaghassaei/vector-math
Version:
A minimal vector math library to handle 2D/3D translations and rotations, written in TypeScript.
249 lines (231 loc) • 6.33 kB
text/typescript
import { NUMERICAL_TOLERANCE } from './constants';
import type { Vector2Readonly } from './Vector2';
import type { THREE_Vector2 } from './THREE_types';
export type Matrix3Readonly = {
readonly elements: readonly number[];
readonly isIdentity: boolean;
equals: (matrix: Matrix3Readonly) => boolean;
clone: () => Matrix3;
}
/**
* These Matrix3s represent a rigid transform in homogeneous coords,
* therefore, we assume that the bottom row is [0, 0, 1] and only store 6 elements.
*/
export class Matrix3 {
private readonly _elements: number[];
private _isIdentity: boolean;
/**
* If no elements passed in, defaults to identity matrix.
*/
constructor();
constructor(
n11: number,
n12: number,
n13: number,
n21: number,
n22: number,
n23: number,
isIdentity?: boolean,
);
constructor(
n11?: number,
n12?: number,
n13?: number,
n21?: number,
n22?: number,
n23?: number,
isIdentity?: boolean,
) {
if (n11 !== undefined) {
this._elements = [
n11, n12!, n13!,
n21!, n22!, n23!,
];
this._isIdentity = isIdentity === undefined ? Matrix3._checkElementForIdentity(this._elements) : isIdentity;
} else {
this._elements = [
1, 0, 0,
0, 1, 0,
];
this._isIdentity = true;
}
}
/**
* @private
*/
set elements(elements: readonly number[]) {
throw new Error('No elements setter on Matrix3.');
}
/**
* Returns elements of Matrix3.
*/
get elements() {
return this._elements as readonly number[];
}
/**
* @private
*/
set isIdentity(isIdentity: boolean) {
throw new Error('No isIdentity setter on Matrix3.');
}
/**
* Returns whether Matrix3 is the identity matrix.
*/
get isIdentity() {
return this._isIdentity;
}
/**
* Set values element-wise.
*/
private _set(
n11: number,
n12: number,
n13: number,
n21: number,
n22: number,
n23: number,
) {
const { _elements } = this;
_elements[0] = n11; _elements[1] = n12; _elements[2] = n13;
_elements[3] = n21; _elements[4] = n22; _elements[5] = n23;
return this;
}
/**
* Set this Matrix4 to the identity matrix.
* @returns this
*/
setIdentity() {
this._set(
1, 0, 0,
0, 1, 0,
);
this._isIdentity = true;
return this;
}
private static _checkElementForIdentity(elements: number[]) {
const [
n11, n12, n13,
n21, n22, n23,
] = elements;
return Math.abs(n11 - 1) <= NUMERICAL_TOLERANCE() && Math.abs(n22 - 1) <= NUMERICAL_TOLERANCE() &&
Math.abs(n12) <= NUMERICAL_TOLERANCE() && Math.abs(n13) <= NUMERICAL_TOLERANCE() &&
Math.abs(n21) <= NUMERICAL_TOLERANCE() && Math.abs(n23) <= NUMERICAL_TOLERANCE();
}
/**
* Set elements of Matrix3 according to rotation.
* @param angle - Angle of rotation in radians.
* @returns this
*/
setRotation(angle: number) {
if (Math.abs(angle) <= NUMERICAL_TOLERANCE()) {
return this.setIdentity();
}
const c = Math.cos(angle), s = Math.sin(angle);
this._set(
c, -s, 0,
s, c, 0,
);
this._isIdentity = false;
return this;
}
/**
* Set elements of Matrix3 according to translation.
* @param translation - Translation vector.
* @returns this
*/
setTranslation(translation: Vector2Readonly | THREE_Vector2) {
if (Math.abs(translation.x) <= NUMERICAL_TOLERANCE() && Math.abs(translation.y) <= NUMERICAL_TOLERANCE()) {
return this.setIdentity();
}
this._set(
1, 0, translation.x,
0, 1, translation.y,
);
this._isIdentity = false;
return this;
}
/**
* Set elements of Matrix4 according to rotation and translation.
* @param angle - Angle of rotation in radians.
* @param translation - Translation vector.
* @returns this
*/
setRotationTranslation(angle: number, translation: Vector2Readonly | THREE_Vector2) {
if (Math.abs(angle) <= NUMERICAL_TOLERANCE() && Math.abs(translation.x) <= NUMERICAL_TOLERANCE() && Math.abs(translation.y) <= NUMERICAL_TOLERANCE()) {
return this.setIdentity();
}
// To do this we need to calculate R(angle) * T(position).
// Based on http://www.gamedev.net/reference/articles/article1199.asp
// First calc R.
const r11 = Math.cos(angle), r12 = -Math.sin(angle);
const r21 = -r12, r22 = r11;
// Pre-multiply T by R.
const tx = translation.x * r11 + translation.y * r12;
const ty = translation.x * r21 + translation.y * r22;
this._set(
r11, r12, tx,
r21, r22, ty,
);
this._isIdentity = false;
return this;
}
// /**
// * Invert the current transform.
// * https://math.stackexchange.com/questions/1234948/inverse-of-a-rigid-transformation
// */
// invertTransform() {
// if (this._isIdentity) return this;
// const { _elements } = this;
// // The inverted 2x2 rotation matrix is equal to its transpose: rTrans.
// const rTrans11 = _elements[0], rTrans12 = _elements[3];
// const rTrans21 = _elements[1], rTrans22 = _elements[4];
// // The inverted translation is -rTrans * t.
// const t1 = _elements[2], t2 = _elements[5];
// const t1Inv = -rTrans11 * t1 - rTrans12 * t2;
// const t2Inv = -rTrans21 * t1 - rTrans22 * t2;
// this._set(
// rTrans11, rTrans12, t1Inv,
// rTrans21, rTrans22, t2Inv,
// );
// return this;
// }
/**
* Test if this Matrix3 equals another Matrix3.
* @param matrix - Matrix3 to test equality with.
* @returns
*/
equals(matrix: Matrix3Readonly) {
const elementsA = this.elements;
const elementsB = matrix.elements;
for (let i = 0, numElements = elementsA.length; i < numElements; i++) {
if (Math.abs(elementsA[i] - elementsB[i]) > NUMERICAL_TOLERANCE()) return false;
}
return true;
}
/**
* Copy values from a Matrix3 into this Matrix3.
* @param matrix - Matrix3 to copy.
* @returns this
*/
copy(matrix: Matrix3Readonly) {
const { elements } = matrix;
this._set(
elements[0], elements[1], elements[2],
elements[3], elements[4], elements[5],
);
this._isIdentity = matrix.isIdentity;
return this;
}
/**
* Returns a deep copy of this Matrix3.
*/
clone() {
const { _elements } = this;
const clone = new Matrix3(
_elements[0], _elements[1], _elements[2],
_elements[3], _elements[4], _elements[5],
this._isIdentity,
);
return clone;
}
}