NMV 0.8.0 - Big refactor and linting fixes
This commit is contained in:
547
lib/classes/Matrix4.ts
Normal file
547
lib/classes/Matrix4.ts
Normal file
@@ -0,0 +1,547 @@
|
||||
import { Vector4 } from './Vector4';
|
||||
|
||||
const EPSILON = 1e-6;
|
||||
|
||||
import { Vector3 } from './Vector3';
|
||||
import { Matrix3 } from './Matrix3';
|
||||
|
||||
export class Matrix4
|
||||
{
|
||||
public static readonly identity: Matrix4 = new Matrix4().setIdentity();
|
||||
|
||||
private readonly values: Float32Array;
|
||||
|
||||
public constructor(values: number[] | null = null)
|
||||
{
|
||||
this.values = new Float32Array(16);
|
||||
if (values)
|
||||
{
|
||||
this.init(values);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setIdentity();
|
||||
}
|
||||
}
|
||||
|
||||
public static frustum(
|
||||
left: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
top: number,
|
||||
near: number,
|
||||
far: number
|
||||
): Matrix4
|
||||
{
|
||||
const rl: number = right - left;
|
||||
const tb: number = top - bottom;
|
||||
const fn: number = far - near;
|
||||
|
||||
return new Matrix4([
|
||||
(near * 2) / rl, 0, 0, 0,
|
||||
0, (near * 2) / tb, 0, 0,
|
||||
(right + left) / rl, (top + bottom) / tb, -(far + near) / fn, -1,
|
||||
0, 0, -(far * near * 2) / fn, 0
|
||||
]);
|
||||
}
|
||||
|
||||
public static perspective(
|
||||
fov: number,
|
||||
aspect: number,
|
||||
near: number,
|
||||
far: number
|
||||
): Matrix4
|
||||
{
|
||||
const top: number = near * Math.tan((fov * Math.PI) / 360.0);
|
||||
const right: number = top * aspect;
|
||||
|
||||
return Matrix4.frustum(-right, right, -top, top, near, far);
|
||||
}
|
||||
|
||||
public static orthographic(
|
||||
left: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
top: number,
|
||||
near: number,
|
||||
far: number
|
||||
): Matrix4
|
||||
{
|
||||
const rl: number = right - left;
|
||||
const tb: number = top - bottom;
|
||||
const fn: number = far - near;
|
||||
|
||||
return new Matrix4([
|
||||
2 / rl, 0, 0, 0,
|
||||
0, 2 / tb, 0, 0,
|
||||
0, 0, -2 / fn, 0,
|
||||
-(left + right) / rl, -(top + bottom) / tb, -(far + near) / fn, 1
|
||||
]);
|
||||
}
|
||||
|
||||
public static lookAt(
|
||||
position: Vector3,
|
||||
target: Vector3,
|
||||
up: Vector3 = Vector3.up
|
||||
): Matrix4
|
||||
{
|
||||
if (position.equals(target))
|
||||
{
|
||||
return this.identity.copy();
|
||||
}
|
||||
|
||||
const z: Vector3 = position.difference(target).normalize();
|
||||
const x: Vector3 = up.cross(z).normalize();
|
||||
const y: Vector3 = z.cross(x).normalize();
|
||||
|
||||
return new Matrix4([
|
||||
x.x, y.x, z.x, 0,
|
||||
x.y, y.y, z.y, 0,
|
||||
x.z, y.z, z.z, 0,
|
||||
-x.dot(position), -y.dot(position), -z.dot(position), 1
|
||||
]);
|
||||
}
|
||||
|
||||
public static product(m1: Matrix4, m2: Matrix4, result: Matrix4 | null = null): Matrix4
|
||||
{
|
||||
const a: Float32Array = m1.values;
|
||||
const b: Float32Array = m2.values;
|
||||
|
||||
const productValues: number[] = [
|
||||
b[0] * a[0] + b[1] * a[4] + b[2] * a[8] + b[3] * a[12],
|
||||
b[0] * a[1] + b[1] * a[5] + b[2] * a[9] + b[3] * a[13],
|
||||
b[0] * a[2] + b[1] * a[6] + b[2] * a[10] + b[3] * a[14],
|
||||
b[0] * a[3] + b[1] * a[7] + b[2] * a[11] + b[3] * a[15],
|
||||
|
||||
b[4] * a[0] + b[5] * a[4] + b[6] * a[8] + b[7] * a[12],
|
||||
b[4] * a[1] + b[5] * a[5] + b[6] * a[9] + b[7] * a[13],
|
||||
b[4] * a[2] + b[5] * a[6] + b[6] * a[10] + b[7] * a[14],
|
||||
b[4] * a[3] + b[5] * a[7] + b[6] * a[11] + b[7] * a[15],
|
||||
|
||||
b[8] * a[0] + b[9] * a[4] + b[10] * a[8] + b[11] * a[12],
|
||||
b[8] * a[1] + b[9] * a[5] + b[10] * a[9] + b[11] * a[13],
|
||||
b[8] * a[2] + b[9] * a[6] + b[10] * a[10] + b[11] * a[14],
|
||||
b[8] * a[3] + b[9] * a[7] + b[10] * a[11] + b[11] * a[15],
|
||||
|
||||
b[12] * a[0] + b[13] * a[4] + b[14] * a[8] + b[15] * a[12],
|
||||
b[12] * a[1] + b[13] * a[5] + b[14] * a[9] + b[15] * a[13],
|
||||
b[12] * a[2] + b[13] * a[6] + b[14] * a[10] + b[15] * a[14],
|
||||
b[12] * a[3] + b[13] * a[7] + b[14] * a[11] + b[15] * a[15],
|
||||
];
|
||||
|
||||
if (result)
|
||||
{
|
||||
result.init(productValues);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Matrix4(productValues);
|
||||
}
|
||||
}
|
||||
|
||||
public at(index: number): number
|
||||
{
|
||||
return this.values[index];
|
||||
}
|
||||
|
||||
public init(values: unknown[]): this
|
||||
{
|
||||
if (values.length !== 16)
|
||||
{
|
||||
throw new Error('Initialization array must have exactly 16 elements.');
|
||||
}
|
||||
|
||||
for (const val of values)
|
||||
{
|
||||
if (typeof val !== 'number')
|
||||
{
|
||||
throw new Error('Array contains non-numbers');
|
||||
}
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
for (const value of values)
|
||||
{
|
||||
this.values[i++] = Number(value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public reset(): void
|
||||
{
|
||||
for (let i = 0; i < this.values.length; i++)
|
||||
{
|
||||
this.values[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public copy(dest: Matrix4 | null = null): Matrix4
|
||||
{
|
||||
const destination: Matrix4 = dest ?? new Matrix4();
|
||||
for (let i = 0; i < this.values.length; i++)
|
||||
{
|
||||
destination.values[i] = this.values[i];
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
public all(): number[]
|
||||
{
|
||||
const data: number[] = [];
|
||||
for (const value of this.values)
|
||||
{
|
||||
data.push(value);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public row(index: number): number[]
|
||||
{
|
||||
if (index < 0 || index > 3)
|
||||
{
|
||||
throw new RangeError('Row index must be between 0 and 3.');
|
||||
}
|
||||
|
||||
return [
|
||||
this.values[index * 4],
|
||||
this.values[index * 4 + 1],
|
||||
this.values[index * 4 + 2],
|
||||
this.values[index * 4 + 3]
|
||||
];
|
||||
}
|
||||
|
||||
public col(index: number): number[]
|
||||
{
|
||||
if (index < 0 || index > 3)
|
||||
{
|
||||
throw new RangeError('Column index must be between 0 and 3.');
|
||||
}
|
||||
|
||||
return [
|
||||
this.values[index],
|
||||
this.values[index + 4],
|
||||
this.values[index + 8],
|
||||
this.values[index + 12]
|
||||
];
|
||||
}
|
||||
|
||||
public equals(matrix: Matrix4, threshold: number = EPSILON): boolean
|
||||
{
|
||||
for (let i = 0; i < this.values.length; i++)
|
||||
{
|
||||
if (Math.abs(this.values[i] - matrix.at(i)) > threshold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public determinant(): number
|
||||
{
|
||||
const m = this.values;
|
||||
const det00 = m[0] * m[5] - m[1] * m[4];
|
||||
const det01 = m[0] * m[6] - m[2] * m[4];
|
||||
const det02 = m[0] * m[7] - m[3] * m[4];
|
||||
const det03 = m[1] * m[6] - m[2] * m[5];
|
||||
const det04 = m[1] * m[7] - m[3] * m[5];
|
||||
const det05 = m[2] * m[7] - m[3] * m[6];
|
||||
const det06 = m[4] * m[9] - m[5] * m[8];
|
||||
const det07 = m[4] * m[10] - m[6] * m[8];
|
||||
const det08 = m[4] * m[11] - m[7] * m[8];
|
||||
const det09 = m[5] * m[10] - m[6] * m[9];
|
||||
const det10 = m[5] * m[11] - m[7] * m[9];
|
||||
const det11 = m[6] * m[11] - m[7] * m[10];
|
||||
|
||||
return (
|
||||
det00 * det11
|
||||
- det01 * det10
|
||||
+ det02 * det09
|
||||
+ det03 * det08
|
||||
- det04 * det07
|
||||
+ det05 * det06
|
||||
);
|
||||
}
|
||||
|
||||
public setIdentity(): this
|
||||
{
|
||||
this.reset();
|
||||
this.values[0] = 1;
|
||||
this.values[5] = 1;
|
||||
this.values[10] = 1;
|
||||
this.values[15] = 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
public transpose(): Matrix4
|
||||
{
|
||||
const m: Float32Array = this.values;
|
||||
const transposed: Float32Array = new Float32Array(16);
|
||||
|
||||
for (let row = 0; row < 4; row++)
|
||||
{
|
||||
for (let col = 0; col < 4; col++)
|
||||
{
|
||||
transposed[col * 4 + row] = m[row * 4 + col];
|
||||
}
|
||||
}
|
||||
|
||||
return new Matrix4(Array.from(transposed));
|
||||
}
|
||||
|
||||
public inverse(): Matrix4 | null
|
||||
{
|
||||
const m = this.values;
|
||||
const inv: number[] = new Array(16);
|
||||
|
||||
inv[0] = m[5] * m[10] * m[15] -
|
||||
m[5] * m[11] * m[14] -
|
||||
m[9] * m[6] * m[15] +
|
||||
m[9] * m[7] * m[14] +
|
||||
m[13] * m[6] * m[11] -
|
||||
m[13] * m[7] * m[10];
|
||||
|
||||
inv[4] = -m[4] * m[10] * m[15] +
|
||||
m[4] * m[11] * m[14] +
|
||||
m[8] * m[6] * m[15] -
|
||||
m[8] * m[7] * m[14] -
|
||||
m[12] * m[6] * m[11] +
|
||||
m[12] * m[7] * m[10];
|
||||
|
||||
inv[8] = m[4] * m[9] * m[15] -
|
||||
m[4] * m[11] * m[13] -
|
||||
m[8] * m[5] * m[15] +
|
||||
m[8] * m[7] * m[13] +
|
||||
m[12] * m[5] * m[11] -
|
||||
m[12] * m[7] * m[9];
|
||||
|
||||
inv[12] = -m[4] * m[9] * m[14] +
|
||||
m[4] * m[10] * m[13] +
|
||||
m[8] * m[5] * m[14] -
|
||||
m[8] * m[6] * m[13] -
|
||||
m[12] * m[5] * m[10] +
|
||||
m[12] * m[6] * m[9];
|
||||
|
||||
inv[1] = -m[1] * m[10] * m[15] +
|
||||
m[1] * m[11] * m[14] +
|
||||
m[9] * m[2] * m[15] -
|
||||
m[9] * m[3] * m[14] -
|
||||
m[13] * m[2] * m[11] +
|
||||
m[13] * m[3] * m[10];
|
||||
|
||||
inv[5] = m[0] * m[10] * m[15] -
|
||||
m[0] * m[11] * m[14] -
|
||||
m[8] * m[2] * m[15] +
|
||||
m[8] * m[3] * m[14] +
|
||||
m[12] * m[2] * m[11] -
|
||||
m[12] * m[3] * m[10];
|
||||
|
||||
inv[9] = -m[0] * m[9] * m[15] +
|
||||
m[0] * m[11] * m[13] +
|
||||
m[8] * m[1] * m[15] -
|
||||
m[8] * m[3] * m[13] -
|
||||
m[12] * m[1] * m[11] +
|
||||
m[12] * m[3] * m[9];
|
||||
|
||||
inv[13] = m[0] * m[9] * m[14] -
|
||||
m[0] * m[10] * m[13] -
|
||||
m[8] * m[1] * m[14] +
|
||||
m[8] * m[2] * m[13] +
|
||||
m[12] * m[1] * m[10] -
|
||||
m[12] * m[2] * m[9];
|
||||
|
||||
inv[2] = m[1] * m[6] * m[15] -
|
||||
m[1] * m[7] * m[14] -
|
||||
m[5] * m[2] * m[15] +
|
||||
m[5] * m[3] * m[14] +
|
||||
m[13] * m[2] * m[7] -
|
||||
m[13] * m[3] * m[6];
|
||||
|
||||
inv[6] = -m[0] * m[6] * m[15] +
|
||||
m[0] * m[7] * m[14] +
|
||||
m[4] * m[2] * m[15] -
|
||||
m[4] * m[3] * m[14] -
|
||||
m[12] * m[2] * m[7] +
|
||||
m[12] * m[3] * m[6];
|
||||
|
||||
inv[10] = m[0] * m[5] * m[15] -
|
||||
m[0] * m[7] * m[13] -
|
||||
m[4] * m[1] * m[15] +
|
||||
m[4] * m[3] * m[13] +
|
||||
m[12] * m[1] * m[7] -
|
||||
m[12] * m[3] * m[5];
|
||||
|
||||
inv[14] = -m[0] * m[5] * m[14] +
|
||||
m[0] * m[6] * m[13] +
|
||||
m[4] * m[1] * m[14] -
|
||||
m[4] * m[2] * m[13] -
|
||||
m[12] * m[1] * m[6] +
|
||||
m[12] * m[2] * m[5];
|
||||
|
||||
inv[3] = -m[1] * m[6] * m[11] +
|
||||
m[1] * m[7] * m[10] +
|
||||
m[5] * m[2] * m[11] -
|
||||
m[5] * m[3] * m[10] -
|
||||
m[9] * m[2] * m[7] +
|
||||
m[9] * m[3] * m[6];
|
||||
|
||||
inv[7] = m[0] * m[6] * m[11] -
|
||||
m[0] * m[7] * m[10] -
|
||||
m[4] * m[2] * m[11] +
|
||||
m[4] * m[3] * m[10] +
|
||||
m[8] * m[2] * m[7] -
|
||||
m[8] * m[3] * m[6];
|
||||
|
||||
inv[11] = -m[0] * m[5] * m[11] +
|
||||
m[0] * m[7] * m[9] +
|
||||
m[4] * m[1] * m[11] -
|
||||
m[4] * m[3] * m[9] -
|
||||
m[8] * m[1] * m[7] +
|
||||
m[8] * m[3] * m[5];
|
||||
|
||||
inv[15] = m[0] * m[5] * m[10] -
|
||||
m[0] * m[6] * m[9] -
|
||||
m[4] * m[1] * m[10] +
|
||||
m[4] * m[2] * m[9] +
|
||||
m[8] * m[1] * m[6] -
|
||||
m[8] * m[2] * m[5];
|
||||
|
||||
const det: number = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];
|
||||
|
||||
if (Math.abs(det) < EPSILON)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
const inverseDet: number = 1.0 / det;
|
||||
for (let i = 0; i < 16; i++)
|
||||
{
|
||||
inv[i] *= inverseDet;
|
||||
}
|
||||
|
||||
return new Matrix4(inv);
|
||||
}
|
||||
|
||||
public multiply(matrix: Matrix4): Matrix4
|
||||
{
|
||||
return Matrix4.product(this, matrix);
|
||||
}
|
||||
|
||||
public multiplyVector3(vector: Vector3): Vector3
|
||||
{
|
||||
const x: number = vector.x;
|
||||
const y: number = vector.y;
|
||||
const z: number = vector.z;
|
||||
|
||||
const m: Float32Array = this.values;
|
||||
|
||||
return new Vector3([
|
||||
m[0] * x + m[4] * y + m[8] * z + m[12],
|
||||
m[1] * x + m[5] * y + m[9] * z + m[13],
|
||||
m[2] * x + m[6] * y + m[10] * z + m[14]
|
||||
]);
|
||||
}
|
||||
|
||||
public multiplyVector4(vector: Vector4): Vector4
|
||||
{
|
||||
const m: Float32Array = this.values;
|
||||
const x: number = vector.x;
|
||||
const y: number = vector.y;
|
||||
const z: number = vector.z;
|
||||
const w: number = vector.w;
|
||||
|
||||
return new Vector4([
|
||||
m[0] * x + m[4] * y + m[8] * z + m[12] * w,
|
||||
m[1] * x + m[5] * y + m[9] * z + m[13] * w,
|
||||
m[2] * x + m[6] * y + m[10] * z + m[14] * w,
|
||||
m[3] * x + m[7] * y + m[11] * z + m[15] * w
|
||||
]);
|
||||
}
|
||||
|
||||
public toMatrix3(): Matrix3
|
||||
{
|
||||
return new Matrix3([
|
||||
this.values[0],
|
||||
this.values[1],
|
||||
this.values[2],
|
||||
this.values[4],
|
||||
this.values[5],
|
||||
this.values[6],
|
||||
this.values[8],
|
||||
this.values[9],
|
||||
this.values[10]
|
||||
]);
|
||||
}
|
||||
|
||||
public toInverseMatrix3(): Matrix3 | null
|
||||
{
|
||||
const matrix3 = this.toMatrix3();
|
||||
return matrix3.inverse();
|
||||
}
|
||||
|
||||
public translate(vector: Vector3): Matrix4
|
||||
{
|
||||
const translation = new Matrix4([
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
vector.x, vector.y, vector.z, 1
|
||||
]);
|
||||
|
||||
return Matrix4.product(this, translation);
|
||||
}
|
||||
|
||||
public scale(vector: Vector3): Matrix4
|
||||
{
|
||||
const scaling = new Matrix4([
|
||||
vector.x, 0, 0, 0,
|
||||
0, vector.y, 0, 0,
|
||||
0, 0, vector.z, 0,
|
||||
0, 0, 0, 1
|
||||
]);
|
||||
|
||||
return Matrix4.product(this, scaling);
|
||||
}
|
||||
|
||||
public rotate(angle: number, axis: Vector3): Matrix4 | null
|
||||
{
|
||||
const x: number = axis.x;
|
||||
const y: number = axis.y;
|
||||
const z: number = axis.z;
|
||||
|
||||
const length: number = Math.sqrt(x * x + y * y + z * z);
|
||||
|
||||
if (length === 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (length !== 1)
|
||||
{
|
||||
const invLength: number = 1 / length;
|
||||
this.rotate(angle, new Vector3([x * invLength, y * invLength, z * invLength]));
|
||||
return this;
|
||||
}
|
||||
|
||||
const s: number = Math.sin(angle);
|
||||
const c: number = Math.cos(angle);
|
||||
const t: number = 1 - c;
|
||||
|
||||
const m: number[] = [
|
||||
x * x * t + c, y * x * t + z * s, z * x * t - y * s, 0,
|
||||
x * y * t - z * s, y * y * t + c, z * y * t + x * s, 0,
|
||||
x * z * t + y * s, y * z * t - x * s, z * z * t + c, 0,
|
||||
0, 0, 0, 1
|
||||
];
|
||||
|
||||
const rotationMatrix = new Matrix4(m);
|
||||
return Matrix4.product(this, rotationMatrix);
|
||||
}
|
||||
|
||||
public toArray(): number[]
|
||||
{
|
||||
return Array.from(this.values);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user