@lucania/vectorics
Version:
A linear algebra library.
344 lines (339 loc) • 13.1 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Vectorics = {}));
})(this, (function (exports) { 'use strict';
class Matrix {
_data;
size;
length;
constructor(...data) {
this._data = data;
this.size = Math.sqrt(data.length);
this.length = data.length;
}
get(row, column) {
return this._data[row * this.size + column];
}
add(value) {
const data = this._tuple(value);
this._data.map((_, index) => this._data[index] += data[index]);
return this;
}
subtract(value) {
const data = this._tuple(value);
this._data.forEach((_, index) => this._data[index] -= data[index]);
return this;
}
multiply(value) {
const data = this._tuple(value);
const result = new Array(data.length).fill(0);
for (let resultRow = 0; resultRow < this.size; resultRow++) {
for (let resultColumn = 0; resultColumn < this.size; resultColumn++) {
let sum = 0;
for (let i = 0; i < this.size; i++) {
sum += this._data[resultRow * this.size + i] * data[i * this.size + resultColumn];
}
result[resultRow * this.size + resultColumn] = sum;
}
}
this._data = result;
return this;
}
multiplyVector(vector) {
if (vector.size !== this.size) {
throw new Error(`Cannot multiply ${vector.size} component vector by ${this.size}x${this.size} matrix.`);
}
const result = vector.clone().set(0);
for (let row = 0; row < this.size; row++) {
for (let column = 0; column < this.size; column++) {
result.components[row] += this.get(row, column) * vector.components[column];
}
}
return result;
}
divide(value) {
const data = this._tuple(value);
this._data.forEach((_, index) => this._data[index] /= data[index]);
return this;
}
inverse() {
this._data.forEach((value, index) => this._data[index] = 1 / value);
return this;
}
transpose() {
const matrix = this.clone();
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.size; j++) {
matrix._data[i * this.size + j] = this._data[j * this.size + i];
}
}
this._data = matrix._data;
return this;
}
clone() {
return new Matrix(...this._data);
}
_tuple(value) {
if (typeof value === "number") {
return new Array(this.length).fill(value);
}
else if (value instanceof Matrix) {
return value._data;
}
else {
return value;
}
}
get data() {
return this._data;
}
toString(fractionDigits = 2) {
const maximumLength = this._data.reduce((length, value) => {
const newLength = value.toFixed(fractionDigits).length;
return newLength > length ? newLength : length;
}, 0);
const lines = [];
let line;
for (let i = 0; i < this.size; i++) {
line = [];
for (let j = 0; j < this.size; j++) {
line.push(this._data[i * this.size + j].toFixed(fractionDigits).padStart(maximumLength, " "));
}
lines.push(`[ ${line.join(", ")} ]`);
}
return lines.join("\n");
}
static tuple(length, source) {
if (typeof source === "number") {
return new Array(length).fill(source);
}
else if (source instanceof Matrix) {
return source.data;
}
else {
return source;
}
}
static fromSource(size, source) {
const tuple = Matrix.tuple(size, source);
switch (size) {
case 2: return new Matrix2(...tuple);
case 3: return new Matrix3(...tuple);
case 4: return new Matrix4(...tuple);
default: return new Matrix(...tuple);
}
}
}
class Matrix2 extends Matrix {
static SIZE = 2 * 2;
static identity() {
return new Matrix2(1, 0, 0, 1);
}
multiplyVector(vector) {
return super.multiplyVector(vector);
}
clone() {
return new Matrix2(...this.data);
}
}
class Matrix3 extends Matrix {
static SIZE = 3 * 3;
static identity() {
return new Matrix3(1, 0, 0, 0, 1, 0, 0, 0, 1);
}
multiplyVector(vector) {
return super.multiplyVector(vector);
}
clone() {
return new Matrix3(...this.data);
}
}
class Matrix4 extends Matrix {
static SIZE = 4 * 4;
static identity() {
return new Matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
multiplyVector(vector) {
return super.multiplyVector(vector);
}
static orthographic(left, right, bottom, top, near, far) {
const width = right - left;
const height = top - bottom;
const depth = far - near;
const translationX = -(right + left) / width;
const translationY = -(top + bottom) / height;
const translationZ = -(far + near) / depth;
return new Matrix4(2 / width, 0, 0, translationX, 0, 2 / height, 0, translationY, 0, 0, -2 / depth, translationZ, 0, 0, 0, 1);
}
static translate(translationX, translationY, translationZ) {
return new Matrix4(1, 0, 0, translationX, 0, 1, 0, translationY, 0, 0, 1, translationZ, 0, 0, 0, 1);
}
static rotate(angleInDegrees, axisX, axisY, axisZ) {
const angleInRadians = angleInDegrees * globalThis.Math.PI / 180;
const cosAngle = globalThis.Math.cos(angleInRadians);
const sinAngle = globalThis.Math.sin(angleInRadians);
const oneMinusCos = 1 - cosAngle;
const tx = oneMinusCos * axisX;
const ty = oneMinusCos * axisY;
const tz = oneMinusCos * axisZ;
const txy = tx * axisY;
const txz = tx * axisZ;
const tyz = ty * axisZ;
const sinX = sinAngle * axisX;
const sinY = sinAngle * axisY;
const sinZ = sinAngle * axisZ;
return new Matrix4(tx * axisX + cosAngle, txy - sinZ, txz + sinY, 0, txy + sinZ, ty * axisY + cosAngle, tyz - sinX, 0, txz - sinY, tyz + sinX, tz * axisZ + cosAngle, 0, 0, 0, 0, 1);
}
static scale(scaleX, scaleY, scaleZ) {
return new Matrix4(scaleX, 0, 0, 0, 0, scaleY, 0, 0, 0, 0, scaleZ, 0, 0, 0, 0, 1);
}
clone() {
return new Matrix4(...this.data);
}
}
class Vector {
_components;
constructor(...components) {
this._components = components;
}
add(value) { return this.operation(value, (a, b) => a + b); }
subtract(value) { return this.operation(value, (a, b) => a - b); }
multiply(value) { return this.operation(value, (a, b) => a * b); }
divide(value) { return this.operation(value, (a, b) => a / b); }
set(value) { return this.operation(value, (_, b) => b); }
normalize() { return this.operation(this.getMagnitude(), (a, b) => a / b); }
dot(value) { return this.clone().multiply(value).getSum(); }
distance(vector) {
const difference = this.clone().subtract(vector);
return Math.sqrt(difference.multiply(difference).getSum());
}
clone() {
return new Vector(...this._components);
}
getSum() {
return this._components.reduce((sum, value) => sum + value);
}
getMagnitude() {
return this.distance(this.clone().set(0));
}
isZero() {
return this._components.every((component) => component === 0);
}
operation(value, operation) {
if (typeof value === "number") {
for (let i = 0; i < this._components.length; i++) {
this._components[i] = operation(this._components[i], value);
}
}
else if (value instanceof Vector) {
for (let i = 0; i < this._components.length; i++) {
this._components[i] = operation(this._components[i], value._components[i]);
}
}
else {
for (let i = 0; i < this._components.length; i++) {
this._components[i] = operation(this._components[i], value[i]);
}
}
return this;
}
toString() {
return `[ ${this._components.join(", ")} ]`;
}
get size() {
return this._components.length;
}
get components() {
return this._components;
}
static tuple(size, source) {
if (typeof source === "number") {
return new Array(size).fill(source);
}
else if ("components" in source) {
return source.components;
}
else {
return source;
}
}
static fromSource(size, source) {
const tuple = Vector.tuple(size, source);
switch (size) {
case 2: return new Vector2(...tuple);
case 3: return new Vector3(...tuple);
case 4: return new Vector4(...tuple);
default: return new Vector(...tuple);
}
}
}
class Vector2 extends Vector {
static SIZE = 2;
get x() { return this.components[0]; }
set x(value) { this.components[0] = value; }
get y() { return this.components[1]; }
set y(value) { this.components[1] = value; }
get width() { return this.x; }
set width(value) { this.x = value; }
get height() { return this.y; }
set height(value) { this.y = value; }
clone() { return new Vector2(this.x, this.y); }
static from(source) {
return Vector.fromSource(Vector2.SIZE, source);
}
}
class Vector3 extends Vector {
static SIZE = 3;
get x() { return this.components[0]; }
set x(value) { this.components[0] = value; }
get y() { return this.components[1]; }
set y(value) { this.components[1] = value; }
get z() { return this.components[2]; }
set z(value) { this.components[2] = value; }
get width() { return this.x; }
set width(value) { this.x = value; }
get height() { return this.y; }
set height(value) { this.y = value; }
get depth() { return this.z; }
set depth(value) { this.z = value; }
cross(vector) {
return this.set([
this.y * vector.z - this.z * vector.y,
this.z * vector.x - this.x * vector.z,
this.x * vector.y - this.y * vector.x
]);
}
clone() { return new Vector3(this.x, this.y, this.z); }
static from(source) {
return Vector.fromSource(Vector3.SIZE, source);
}
}
class Vector4 extends Vector {
static SIZE = 4;
get x() { return this.components[0]; }
set x(value) { this.components[0] = value; }
get y() { return this.components[1]; }
set y(value) { this.components[1] = value; }
get z() { return this.components[2]; }
set z(value) { this.components[2] = value; }
get w() { return this.components[3]; }
set w(value) { this.components[3] = value; }
get width() { return this.z; }
set width(value) { this.z = value; }
get height() { return this.w; }
set height(value) { this.w = value; }
clone() { return new Vector4(this.x, this.y, this.z, this.w); }
static from(source) {
return Vector.fromSource(Vector4.SIZE, source);
}
}
exports.Matrix = Matrix;
exports.Matrix2 = Matrix2;
exports.Matrix3 = Matrix3;
exports.Matrix4 = Matrix4;
exports.Vector = Vector;
exports.Vector2 = Vector2;
exports.Vector3 = Vector3;
exports.Vector4 = Vector4;
}));