@aura3d/math 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +18 -0
  2. package/dist/Box3.d.ts +19 -0
  3. package/dist/Box3.d.ts.map +1 -0
  4. package/dist/Box3.js +61 -0
  5. package/dist/Box3.js.map +1 -0
  6. package/dist/Color.d.ts +15 -0
  7. package/dist/Color.d.ts.map +1 -0
  8. package/dist/Color.js +41 -0
  9. package/dist/Color.js.map +1 -0
  10. package/dist/Curves.d.ts +22 -0
  11. package/dist/Curves.d.ts.map +1 -0
  12. package/dist/Curves.js +74 -0
  13. package/dist/Curves.js.map +1 -0
  14. package/dist/Easing.d.ts +7 -0
  15. package/dist/Easing.d.ts.map +1 -0
  16. package/dist/Easing.js +24 -0
  17. package/dist/Easing.js.map +1 -0
  18. package/dist/Euler.d.ts +12 -0
  19. package/dist/Euler.d.ts.map +1 -0
  20. package/dist/Euler.js +30 -0
  21. package/dist/Euler.js.map +1 -0
  22. package/dist/Frustum.d.ts +14 -0
  23. package/dist/Frustum.d.ts.map +1 -0
  24. package/dist/Frustum.js +40 -0
  25. package/dist/Frustum.js.map +1 -0
  26. package/dist/Interpolation.d.ts +11 -0
  27. package/dist/Interpolation.d.ts.map +1 -0
  28. package/dist/Interpolation.js +30 -0
  29. package/dist/Interpolation.js.map +1 -0
  30. package/dist/Matrix3.d.ts +9 -0
  31. package/dist/Matrix3.d.ts.map +1 -0
  32. package/dist/Matrix3.js +51 -0
  33. package/dist/Matrix3.js.map +1 -0
  34. package/dist/Matrix4.d.ts +45 -0
  35. package/dist/Matrix4.d.ts.map +1 -0
  36. package/dist/Matrix4.js +204 -0
  37. package/dist/Matrix4.js.map +1 -0
  38. package/dist/Plane.d.ts +11 -0
  39. package/dist/Plane.d.ts.map +1 -0
  40. package/dist/Plane.js +31 -0
  41. package/dist/Plane.js.map +1 -0
  42. package/dist/Quaternion.d.ts +25 -0
  43. package/dist/Quaternion.d.ts.map +1 -0
  44. package/dist/Quaternion.js +121 -0
  45. package/dist/Quaternion.js.map +1 -0
  46. package/dist/Random.d.ts +9 -0
  47. package/dist/Random.d.ts.map +1 -0
  48. package/dist/Random.js +30 -0
  49. package/dist/Random.js.map +1 -0
  50. package/dist/Ray.d.ts +14 -0
  51. package/dist/Ray.d.ts.map +1 -0
  52. package/dist/Ray.js +67 -0
  53. package/dist/Ray.js.map +1 -0
  54. package/dist/Sphere.d.ts +9 -0
  55. package/dist/Sphere.d.ts.map +1 -0
  56. package/dist/Sphere.js +18 -0
  57. package/dist/Sphere.js.map +1 -0
  58. package/dist/Transform.d.ts +14 -0
  59. package/dist/Transform.d.ts.map +1 -0
  60. package/dist/Transform.js +28 -0
  61. package/dist/Transform.js.map +1 -0
  62. package/dist/Vector2.d.ts +22 -0
  63. package/dist/Vector2.d.ts.map +1 -0
  64. package/dist/Vector2.js +54 -0
  65. package/dist/Vector2.js.map +1 -0
  66. package/dist/Vector3.d.ts +34 -0
  67. package/dist/Vector3.d.ts.map +1 -0
  68. package/dist/Vector3.js +77 -0
  69. package/dist/Vector3.js.map +1 -0
  70. package/dist/Vector4.d.ts +19 -0
  71. package/dist/Vector4.d.ts.map +1 -0
  72. package/dist/Vector4.js +43 -0
  73. package/dist/Vector4.js.map +1 -0
  74. package/dist/index.d.ts +19 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +19 -0
  77. package/dist/index.js.map +1 -0
  78. package/package.json +13 -0
  79. package/src/Box3.ts +65 -0
  80. package/src/Color.ts +49 -0
  81. package/src/Curves.ts +79 -0
  82. package/src/Easing.ts +24 -0
  83. package/src/Euler.ts +30 -0
  84. package/src/Frustum.ts +43 -0
  85. package/src/Interpolation.ts +34 -0
  86. package/src/Matrix3.ts +54 -0
  87. package/src/Matrix4.ts +240 -0
  88. package/src/Plane.ts +34 -0
  89. package/src/Quaternion.ts +178 -0
  90. package/src/Random.ts +32 -0
  91. package/src/Ray.ts +67 -0
  92. package/src/Sphere.ts +18 -0
  93. package/src/Transform.ts +33 -0
  94. package/src/Vector2.ts +66 -0
  95. package/src/Vector3.ts +101 -0
  96. package/src/Vector4.ts +55 -0
  97. package/src/index.ts +18 -0
package/src/Euler.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { Quaternion, type EulerOrder } from "./Quaternion.js";
2
+
3
+ export class Euler {
4
+ constructor(
5
+ readonly x = 0,
6
+ readonly y = 0,
7
+ readonly z = 0,
8
+ readonly order: EulerOrder = "XYZ"
9
+ ) {
10
+ if (![x, y, z].every(Number.isFinite)) throw new RangeError("Euler angles must be finite.");
11
+ if (order !== "XYZ") throw new RangeError(`Unsupported Euler order: ${order}`);
12
+ }
13
+
14
+ toQuaternion(): Quaternion {
15
+ return Quaternion.fromEuler(this.x, this.y, this.z, this.order);
16
+ }
17
+
18
+ equals(euler: Euler, epsilon = 1e-10): boolean {
19
+ return (
20
+ Math.abs(this.x - euler.x) <= epsilon &&
21
+ Math.abs(this.y - euler.y) <= epsilon &&
22
+ Math.abs(this.z - euler.z) <= epsilon &&
23
+ this.order === euler.order
24
+ );
25
+ }
26
+
27
+ toArray(): [number, number, number] {
28
+ return [this.x, this.y, this.z];
29
+ }
30
+ }
package/src/Frustum.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { Box3 } from "./Box3.js";
2
+ import { Matrix4 } from "./Matrix4.js";
3
+ import { Plane } from "./Plane.js";
4
+ import { Sphere } from "./Sphere.js";
5
+ import { Vector3 } from "./Vector3.js";
6
+
7
+ export class Frustum {
8
+ constructor(readonly planes: readonly [Plane, Plane, Plane, Plane, Plane, Plane]) {}
9
+
10
+ static fromMatrix(matrix: Matrix4): Frustum {
11
+ const m = matrix.elements;
12
+ const planes: [Plane, Plane, Plane, Plane, Plane, Plane] = [
13
+ new Plane(new Vector3(m[3] + m[0], m[7] + m[4], m[11] + m[8]), m[15] + m[12]),
14
+ new Plane(new Vector3(m[3] - m[0], m[7] - m[4], m[11] - m[8]), m[15] - m[12]),
15
+ new Plane(new Vector3(m[3] + m[1], m[7] + m[5], m[11] + m[9]), m[15] + m[13]),
16
+ new Plane(new Vector3(m[3] - m[1], m[7] - m[5], m[11] - m[9]), m[15] - m[13]),
17
+ new Plane(new Vector3(m[3] + m[2], m[7] + m[6], m[11] + m[10]), m[15] + m[14]),
18
+ new Plane(new Vector3(m[3] - m[2], m[7] - m[6], m[11] - m[10]), m[15] - m[14])
19
+ ];
20
+ return new Frustum(planes);
21
+ }
22
+
23
+ containsPoint(point: Vector3): boolean {
24
+ return this.planes.every((plane) => plane.distanceToPoint(point) >= 0);
25
+ }
26
+
27
+ intersectsSphere(sphere: Sphere): boolean {
28
+ return this.planes.every((plane) => plane.distanceToPoint(sphere.center) >= -sphere.radius);
29
+ }
30
+
31
+ intersectsBox(box: Box3): boolean {
32
+ if (box.isEmpty()) return false;
33
+ for (const plane of this.planes) {
34
+ const p = new Vector3(
35
+ plane.normal.x >= 0 ? box.max.x : box.min.x,
36
+ plane.normal.y >= 0 ? box.max.y : box.min.y,
37
+ plane.normal.z >= 0 ? box.max.z : box.min.z
38
+ );
39
+ if (plane.distanceToPoint(p) < 0) return false;
40
+ }
41
+ return true;
42
+ }
43
+ }
@@ -0,0 +1,34 @@
1
+ import { Vector2 } from "./Vector2.js";
2
+ import { Vector3 } from "./Vector3.js";
3
+ import { Vector4 } from "./Vector4.js";
4
+
5
+ export function clamp(value: number, min = 0, max = 1): number {
6
+ if (min > max) throw new RangeError("min must be <= max");
7
+ return Math.min(max, Math.max(min, value));
8
+ }
9
+
10
+ export function lerp(a: number, b: number, t: number): number {
11
+ return a + (b - a) * t;
12
+ }
13
+
14
+ export function inverseLerp(a: number, b: number, value: number): number {
15
+ if (a === b) throw new RangeError("inverseLerp requires distinct endpoints");
16
+ return (value - a) / (b - a);
17
+ }
18
+
19
+ export function smoothstep(edge0: number, edge1: number, value: number): number {
20
+ const t = clamp(inverseLerp(edge0, edge1, value));
21
+ return t * t * (3 - 2 * t);
22
+ }
23
+
24
+ export function lerpVector2(a: Vector2, b: Vector2, t: number): Vector2 {
25
+ return a.lerp(b, t);
26
+ }
27
+
28
+ export function lerpVector3(a: Vector3, b: Vector3, t: number): Vector3 {
29
+ return a.lerp(b, t);
30
+ }
31
+
32
+ export function lerpVector4(a: Vector4, b: Vector4, t: number): Vector4 {
33
+ return a.lerp(b, t);
34
+ }
package/src/Matrix3.ts ADDED
@@ -0,0 +1,54 @@
1
+ export class Matrix3 {
2
+ readonly elements: readonly [number, number, number, number, number, number, number, number, number];
3
+
4
+ constructor(elements?: readonly [number, number, number, number, number, number, number, number, number]) {
5
+ this.elements = elements ? [...elements] as typeof this.elements : [1, 0, 0, 0, 1, 0, 0, 0, 1];
6
+ }
7
+
8
+ determinant(): number {
9
+ const m = this.elements;
10
+ return m[0] * (m[4] * m[8] - m[5] * m[7]) - m[3] * (m[1] * m[8] - m[2] * m[7]) + m[6] * (m[1] * m[5] - m[2] * m[4]);
11
+ }
12
+
13
+ inverse(): Matrix3 {
14
+ const m = this.elements;
15
+ const det = this.determinant();
16
+ if (Math.abs(det) < 1e-12) throw new RangeError("Matrix3 is singular.");
17
+ const invDet = 1 / det;
18
+ return new Matrix3([
19
+ (m[4] * m[8] - m[5] * m[7]) * invDet,
20
+ (m[2] * m[7] - m[1] * m[8]) * invDet,
21
+ (m[1] * m[5] - m[2] * m[4]) * invDet,
22
+ (m[5] * m[6] - m[3] * m[8]) * invDet,
23
+ (m[0] * m[8] - m[2] * m[6]) * invDet,
24
+ (m[2] * m[3] - m[0] * m[5]) * invDet,
25
+ (m[3] * m[7] - m[4] * m[6]) * invDet,
26
+ (m[1] * m[6] - m[0] * m[7]) * invDet,
27
+ (m[0] * m[4] - m[1] * m[3]) * invDet
28
+ ]);
29
+ }
30
+
31
+ multiply(other: Matrix3): Matrix3 {
32
+ const a = this.elements;
33
+ const b = other.elements;
34
+ const out = new Array<number>(9);
35
+ for (let row = 0; row < 3; row++) {
36
+ for (let col = 0; col < 3; col++) {
37
+ out[col * 3 + row] =
38
+ a[0 * 3 + row]! * b[col * 3 + 0]! +
39
+ a[1 * 3 + row]! * b[col * 3 + 1]! +
40
+ a[2 * 3 + row]! * b[col * 3 + 2]!;
41
+ }
42
+ }
43
+ return new Matrix3(out as unknown as Matrix3["elements"]);
44
+ }
45
+
46
+ transpose(): Matrix3 {
47
+ const m = this.elements;
48
+ return new Matrix3([
49
+ m[0], m[3], m[6],
50
+ m[1], m[4], m[7],
51
+ m[2], m[5], m[8]
52
+ ]);
53
+ }
54
+ }
package/src/Matrix4.ts ADDED
@@ -0,0 +1,240 @@
1
+ import { Quaternion } from "./Quaternion.js";
2
+ import { Vector3 } from "./Vector3.js";
3
+ import { Vector4 } from "./Vector4.js";
4
+
5
+ export class Matrix4 {
6
+ readonly elements: readonly [
7
+ number, number, number, number,
8
+ number, number, number, number,
9
+ number, number, number, number,
10
+ number, number, number, number
11
+ ];
12
+
13
+ constructor(elements?: Matrix4["elements"]) {
14
+ this.elements = elements ? [...elements] as Matrix4["elements"] : [
15
+ 1, 0, 0, 0,
16
+ 0, 1, 0, 0,
17
+ 0, 0, 1, 0,
18
+ 0, 0, 0, 1
19
+ ];
20
+ }
21
+
22
+ static identity(): Matrix4 {
23
+ return new Matrix4();
24
+ }
25
+
26
+ static translation(v: Vector3): Matrix4 {
27
+ return new Matrix4([
28
+ 1, 0, 0, 0,
29
+ 0, 1, 0, 0,
30
+ 0, 0, 1, 0,
31
+ v.x, v.y, v.z, 1
32
+ ]);
33
+ }
34
+
35
+ static scaling(v: Vector3): Matrix4 {
36
+ return new Matrix4([
37
+ v.x, 0, 0, 0,
38
+ 0, v.y, 0, 0,
39
+ 0, 0, v.z, 0,
40
+ 0, 0, 0, 1
41
+ ]);
42
+ }
43
+
44
+ static rotation(q: Quaternion): Matrix4 {
45
+ const n = q.normalize();
46
+ const x2 = n.x + n.x;
47
+ const y2 = n.y + n.y;
48
+ const z2 = n.z + n.z;
49
+ const xx = n.x * x2;
50
+ const xy = n.x * y2;
51
+ const xz = n.x * z2;
52
+ const yy = n.y * y2;
53
+ const yz = n.y * z2;
54
+ const zz = n.z * z2;
55
+ const wx = n.w * x2;
56
+ const wy = n.w * y2;
57
+ const wz = n.w * z2;
58
+ return new Matrix4([
59
+ 1 - (yy + zz), xy + wz, xz - wy, 0,
60
+ xy - wz, 1 - (xx + zz), yz + wx, 0,
61
+ xz + wy, yz - wx, 1 - (xx + yy), 0,
62
+ 0, 0, 0, 1
63
+ ]);
64
+ }
65
+
66
+ static compose(position: Vector3, rotation: Quaternion, scale: Vector3): Matrix4 {
67
+ return Matrix4.translation(position).multiply(Matrix4.rotation(rotation)).multiply(Matrix4.scaling(scale));
68
+ }
69
+
70
+ static perspective(fovYRadians: number, aspect: number, near: number, far: number): Matrix4 {
71
+ if (![fovYRadians, aspect, near, far].every(Number.isFinite) || fovYRadians <= 0 || aspect <= 0 || near <= 0 || far <= near) {
72
+ throw new RangeError("Invalid perspective projection parameters.");
73
+ }
74
+ const f = 1 / Math.tan(fovYRadians / 2);
75
+ const nf = 1 / (near - far);
76
+ return new Matrix4([
77
+ f / aspect, 0, 0, 0,
78
+ 0, f, 0, 0,
79
+ 0, 0, (far + near) * nf, -1,
80
+ 0, 0, 2 * far * near * nf, 0
81
+ ]);
82
+ }
83
+
84
+ static orthographic(left: number, right: number, bottom: number, top: number, near: number, far: number): Matrix4 {
85
+ if (![left, right, bottom, top, near, far].every(Number.isFinite) || left === right || bottom === top || near === far) {
86
+ throw new RangeError("Invalid orthographic projection parameters.");
87
+ }
88
+ const lr = 1 / (left - right);
89
+ const bt = 1 / (bottom - top);
90
+ const nf = 1 / (near - far);
91
+ return new Matrix4([
92
+ -2 * lr, 0, 0, 0,
93
+ 0, -2 * bt, 0, 0,
94
+ 0, 0, 2 * nf, 0,
95
+ (left + right) * lr, (top + bottom) * bt, (far + near) * nf, 1
96
+ ]);
97
+ }
98
+
99
+ static lookAt(eye: Vector3, target: Vector3, up: Vector3): Matrix4 {
100
+ const z = eye.subtract(target).normalize();
101
+ if (z.lengthSquared() === 0) throw new RangeError("Look-at eye and target must not be identical.");
102
+
103
+ let x = up.cross(z).normalize();
104
+ if (x.lengthSquared() === 0) {
105
+ const fallbackUp = Math.abs(up.z) === 1 ? Vector3.up : Vector3.forward;
106
+ x = fallbackUp.cross(z).normalize();
107
+ }
108
+ const y = z.cross(x).normalize();
109
+
110
+ return new Matrix4([
111
+ x.x, y.x, z.x, 0,
112
+ x.y, y.y, z.y, 0,
113
+ x.z, y.z, z.z, 0,
114
+ -x.dot(eye), -y.dot(eye), -z.dot(eye), 1
115
+ ]);
116
+ }
117
+
118
+ multiply(other: Matrix4): Matrix4 {
119
+ const a = this.elements;
120
+ const b = other.elements;
121
+ const out = new Array<number>(16);
122
+ for (let row = 0; row < 4; row++) {
123
+ for (let col = 0; col < 4; col++) {
124
+ out[col * 4 + row] =
125
+ a[0 * 4 + row]! * b[col * 4 + 0]! +
126
+ a[1 * 4 + row]! * b[col * 4 + 1]! +
127
+ a[2 * 4 + row]! * b[col * 4 + 2]! +
128
+ a[3 * 4 + row]! * b[col * 4 + 3]!;
129
+ }
130
+ }
131
+ return new Matrix4(out as unknown as Matrix4["elements"]);
132
+ }
133
+
134
+ transformPoint(v: Vector3): Vector3 {
135
+ const m = this.elements;
136
+ const x = v.x;
137
+ const y = v.y;
138
+ const z = v.z;
139
+ const w = m[3] * x + m[7] * y + m[11] * z + m[15];
140
+ const iw = w === 0 ? 1 : 1 / w;
141
+ return new Vector3(
142
+ (m[0] * x + m[4] * y + m[8] * z + m[12]) * iw,
143
+ (m[1] * x + m[5] * y + m[9] * z + m[13]) * iw,
144
+ (m[2] * x + m[6] * y + m[10] * z + m[14]) * iw
145
+ );
146
+ }
147
+
148
+ transformVector4(v: Vector4): Vector4 {
149
+ const m = this.elements;
150
+ return new Vector4(
151
+ m[0] * v.x + m[4] * v.y + m[8] * v.z + m[12] * v.w,
152
+ m[1] * v.x + m[5] * v.y + m[9] * v.z + m[13] * v.w,
153
+ m[2] * v.x + m[6] * v.y + m[10] * v.z + m[14] * v.w,
154
+ m[3] * v.x + m[7] * v.y + m[11] * v.z + m[15] * v.w
155
+ );
156
+ }
157
+
158
+ transpose(): Matrix4 {
159
+ const m = this.elements;
160
+ return new Matrix4([
161
+ m[0], m[4], m[8], m[12],
162
+ m[1], m[5], m[9], m[13],
163
+ m[2], m[6], m[10], m[14],
164
+ m[3], m[7], m[11], m[15]
165
+ ]);
166
+ }
167
+
168
+ determinant(): number {
169
+ const m = this.elements;
170
+ const m00 = m[0], m01 = m[4], m02 = m[8], m03 = m[12];
171
+ const m10 = m[1], m11 = m[5], m12 = m[9], m13 = m[13];
172
+ const m20 = m[2], m21 = m[6], m22 = m[10], m23 = m[14];
173
+ const m30 = m[3], m31 = m[7], m32 = m[11], m33 = m[15];
174
+ return (
175
+ m03 * m12 * m21 * m30 - m02 * m13 * m21 * m30 -
176
+ m03 * m11 * m22 * m30 + m01 * m13 * m22 * m30 +
177
+ m02 * m11 * m23 * m30 - m01 * m12 * m23 * m30 -
178
+ m03 * m12 * m20 * m31 + m02 * m13 * m20 * m31 +
179
+ m03 * m10 * m22 * m31 - m00 * m13 * m22 * m31 -
180
+ m02 * m10 * m23 * m31 + m00 * m12 * m23 * m31 +
181
+ m03 * m11 * m20 * m32 - m01 * m13 * m20 * m32 -
182
+ m03 * m10 * m21 * m32 + m00 * m13 * m21 * m32 +
183
+ m01 * m10 * m23 * m32 - m00 * m11 * m23 * m32 -
184
+ m02 * m11 * m20 * m33 + m01 * m12 * m20 * m33 +
185
+ m02 * m10 * m21 * m33 - m00 * m12 * m21 * m33 -
186
+ m01 * m10 * m22 * m33 + m00 * m11 * m22 * m33
187
+ );
188
+ }
189
+
190
+ inverse(): Matrix4 {
191
+ const m = this.elements;
192
+ const inv = new Array<number>(16);
193
+
194
+ 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];
195
+ 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];
196
+ 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];
197
+ 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];
198
+ 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];
199
+ 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];
200
+ 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];
201
+ 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];
202
+ 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];
203
+ 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];
204
+ 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];
205
+ 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];
206
+ 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];
207
+ 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];
208
+ 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];
209
+ 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];
210
+
211
+ const det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];
212
+ if (Math.abs(det) < 1e-12) throw new RangeError("Matrix4 is singular.");
213
+ return new Matrix4(inv.map((value) => value / det) as unknown as Matrix4["elements"]);
214
+ }
215
+
216
+ decompose(): { position: Vector3; rotation: Quaternion; scale: Vector3 } {
217
+ const m = this.elements;
218
+ const position = new Vector3(m[12], m[13], m[14]);
219
+ const sx = new Vector3(m[0], m[1], m[2]).length();
220
+ const sy = new Vector3(m[4], m[5], m[6]).length();
221
+ const sz = new Vector3(m[8], m[9], m[10]).length();
222
+ if (sx === 0 || sy === 0 || sz === 0) throw new RangeError("Cannot decompose matrix with zero scale.");
223
+
224
+ const det = this.determinant();
225
+ const scale = new Vector3(det < 0 ? -sx : sx, sy, sz);
226
+ const rotationMatrix = new Matrix4([
227
+ m[0] / scale.x, m[1] / scale.x, m[2] / scale.x, 0,
228
+ m[4] / scale.y, m[5] / scale.y, m[6] / scale.y, 0,
229
+ m[8] / scale.z, m[9] / scale.z, m[10] / scale.z, 0,
230
+ 0, 0, 0, 1
231
+ ]);
232
+
233
+ return { position, rotation: Quaternion.fromRotationMatrix(rotationMatrix), scale };
234
+ }
235
+
236
+ getTranslation(): Vector3 {
237
+ const m = this.elements;
238
+ return new Vector3(m[12], m[13], m[14]);
239
+ }
240
+ }
package/src/Plane.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { Vector3 } from "./Vector3.js";
2
+
3
+ export class Plane {
4
+ readonly normal: Vector3;
5
+ readonly constant: number;
6
+
7
+ constructor(normal = Vector3.up.clone(), constant = 0) {
8
+ const len = normal.length();
9
+ if (len === 0) throw new RangeError("Plane normal must be non-zero.");
10
+ this.normal = normal.multiplyScalar(1 / len);
11
+ this.constant = constant / len;
12
+ }
13
+
14
+ static fromPointNormal(point: Vector3, normal: Vector3): Plane {
15
+ const normalized = normal.normalize();
16
+ return new Plane(normalized, -point.dot(normalized));
17
+ }
18
+
19
+ distanceToPoint(point: Vector3): number {
20
+ return this.normal.dot(point) + this.constant;
21
+ }
22
+
23
+ projectPoint(point: Vector3): Vector3 {
24
+ return point.subtract(this.normal.multiplyScalar(this.distanceToPoint(point)));
25
+ }
26
+
27
+ intersectLine(start: Vector3, end: Vector3): Vector3 | undefined {
28
+ const direction = end.subtract(start);
29
+ const denominator = this.normal.dot(direction);
30
+ if (Math.abs(denominator) < 1e-12) return undefined;
31
+ const t = -(start.dot(this.normal) + this.constant) / denominator;
32
+ return t < 0 || t > 1 ? undefined : start.add(direction.multiplyScalar(t));
33
+ }
34
+ }
@@ -0,0 +1,178 @@
1
+ import { Vector3 } from "./Vector3.js";
2
+ import type { Matrix4 } from "./Matrix4.js";
3
+
4
+ export type EulerOrder = "XYZ";
5
+
6
+ export class Quaternion {
7
+ static readonly identity = Object.freeze(new Quaternion(0, 0, 0, 1));
8
+
9
+ constructor(
10
+ readonly x = 0,
11
+ readonly y = 0,
12
+ readonly z = 0,
13
+ readonly w = 1
14
+ ) {}
15
+
16
+ static fromAxisAngle(axis: Vector3, radians: number): Quaternion {
17
+ const normalized = axis.normalize();
18
+ if (normalized.lengthSquared() === 0) throw new RangeError("Axis must be non-zero.");
19
+ const half = radians / 2;
20
+ const s = Math.sin(half);
21
+ return new Quaternion(normalized.x * s, normalized.y * s, normalized.z * s, Math.cos(half)).normalize();
22
+ }
23
+
24
+ static fromUnitVectors(from: Vector3, to: Vector3): Quaternion {
25
+ const vFrom = from.normalize();
26
+ const vTo = to.normalize();
27
+ const dot = vFrom.dot(vTo);
28
+
29
+ if (dot < -0.999999) {
30
+ const axis = Math.abs(vFrom.x) > 0.1 ? Vector3.up.cross(vFrom).normalize() : Vector3.right.cross(vFrom).normalize();
31
+ return Quaternion.fromAxisAngle(axis, Math.PI);
32
+ }
33
+ if (dot > 0.999999) return Quaternion.identity.clone();
34
+
35
+ const cross = vFrom.cross(vTo);
36
+ return new Quaternion(cross.x, cross.y, cross.z, 1 + dot).normalize();
37
+ }
38
+
39
+ static fromEuler(x: number, y: number, z: number, order: EulerOrder = "XYZ"): Quaternion {
40
+ if (order !== "XYZ") throw new RangeError(`Unsupported Euler order: ${order}`);
41
+ if (![x, y, z].every(Number.isFinite)) throw new RangeError("Euler angles must be finite.");
42
+ const c1 = Math.cos(x / 2);
43
+ const c2 = Math.cos(y / 2);
44
+ const c3 = Math.cos(z / 2);
45
+ const s1 = Math.sin(x / 2);
46
+ const s2 = Math.sin(y / 2);
47
+ const s3 = Math.sin(z / 2);
48
+ return new Quaternion(
49
+ s1 * c2 * c3 + c1 * s2 * s3,
50
+ c1 * s2 * c3 - s1 * c2 * s3,
51
+ c1 * c2 * s3 + s1 * s2 * c3,
52
+ c1 * c2 * c3 - s1 * s2 * s3
53
+ ).normalize();
54
+ }
55
+
56
+ static fromRotationMatrix(matrix: Matrix4): Quaternion {
57
+ const m = matrix.elements;
58
+ const m11 = m[0]!, m12 = m[4]!, m13 = m[8]!;
59
+ const m21 = m[1]!, m22 = m[5]!, m23 = m[9]!;
60
+ const m31 = m[2]!, m32 = m[6]!, m33 = m[10]!;
61
+ const trace = m11 + m22 + m33;
62
+
63
+ if (trace > 0) {
64
+ const s = 0.5 / Math.sqrt(trace + 1);
65
+ return new Quaternion(
66
+ (m32 - m23) * s,
67
+ (m13 - m31) * s,
68
+ (m21 - m12) * s,
69
+ 0.25 / s
70
+ ).normalize();
71
+ }
72
+ if (m11 > m22 && m11 > m33) {
73
+ const s = 2 * Math.sqrt(1 + m11 - m22 - m33);
74
+ return new Quaternion(
75
+ 0.25 * s,
76
+ (m12 + m21) / s,
77
+ (m13 + m31) / s,
78
+ (m32 - m23) / s
79
+ ).normalize();
80
+ }
81
+ if (m22 > m33) {
82
+ const s = 2 * Math.sqrt(1 + m22 - m11 - m33);
83
+ return new Quaternion(
84
+ (m12 + m21) / s,
85
+ 0.25 * s,
86
+ (m23 + m32) / s,
87
+ (m13 - m31) / s
88
+ ).normalize();
89
+ }
90
+ const s = 2 * Math.sqrt(1 + m33 - m11 - m22);
91
+ return new Quaternion(
92
+ (m13 + m31) / s,
93
+ (m23 + m32) / s,
94
+ 0.25 * s,
95
+ (m21 - m12) / s
96
+ ).normalize();
97
+ }
98
+
99
+ clone(): Quaternion {
100
+ return new Quaternion(this.x, this.y, this.z, this.w);
101
+ }
102
+
103
+ length(): number {
104
+ return Math.hypot(this.x, this.y, this.z, this.w);
105
+ }
106
+
107
+ normalize(): Quaternion {
108
+ const len = this.length();
109
+ if (len === 0) return Quaternion.identity.clone();
110
+ return new Quaternion(this.x / len, this.y / len, this.z / len, this.w / len);
111
+ }
112
+
113
+ conjugate(): Quaternion {
114
+ return new Quaternion(-this.x, -this.y, -this.z, this.w);
115
+ }
116
+
117
+ inverse(): Quaternion {
118
+ const lengthSquared = this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
119
+ if (lengthSquared === 0) return Quaternion.identity.clone();
120
+ const conjugate = this.conjugate();
121
+ return new Quaternion(
122
+ conjugate.x / lengthSquared,
123
+ conjugate.y / lengthSquared,
124
+ conjugate.z / lengthSquared,
125
+ conjugate.w / lengthSquared
126
+ );
127
+ }
128
+
129
+ multiply(q: Quaternion): Quaternion {
130
+ return new Quaternion(
131
+ this.w * q.x + this.x * q.w + this.y * q.z - this.z * q.y,
132
+ this.w * q.y - this.x * q.z + this.y * q.w + this.z * q.x,
133
+ this.w * q.z + this.x * q.y - this.y * q.x + this.z * q.w,
134
+ this.w * q.w - this.x * q.x - this.y * q.y - this.z * q.z
135
+ );
136
+ }
137
+
138
+ rotateVector(v: Vector3): Vector3 {
139
+ const qv = new Quaternion(v.x, v.y, v.z, 0);
140
+ const result = this.multiply(qv).multiply(this.conjugate());
141
+ return new Vector3(result.x, result.y, result.z);
142
+ }
143
+
144
+ slerp(to: Quaternion, t: number): Quaternion {
145
+ let target = to;
146
+ let cosHalfTheta = this.x * to.x + this.y * to.y + this.z * to.z + this.w * to.w;
147
+
148
+ if (cosHalfTheta < 0) {
149
+ target = new Quaternion(-to.x, -to.y, -to.z, -to.w);
150
+ cosHalfTheta = -cosHalfTheta;
151
+ }
152
+
153
+ if (cosHalfTheta >= 1.0) return this.clone();
154
+ if (cosHalfTheta > 0.9995) {
155
+ return new Quaternion(
156
+ this.x + t * (target.x - this.x),
157
+ this.y + t * (target.y - this.y),
158
+ this.z + t * (target.z - this.z),
159
+ this.w + t * (target.w - this.w)
160
+ ).normalize();
161
+ }
162
+
163
+ const halfTheta = Math.acos(cosHalfTheta);
164
+ const sinHalfTheta = Math.sqrt(1 - cosHalfTheta * cosHalfTheta);
165
+ const ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta;
166
+ const ratioB = Math.sin(t * halfTheta) / sinHalfTheta;
167
+ return new Quaternion(
168
+ this.x * ratioA + target.x * ratioB,
169
+ this.y * ratioA + target.y * ratioB,
170
+ this.z * ratioA + target.z * ratioB,
171
+ this.w * ratioA + target.w * ratioB
172
+ ).normalize();
173
+ }
174
+
175
+ equals(q: Quaternion, epsilon = 1e-10): boolean {
176
+ return Math.abs(this.x - q.x) <= epsilon && Math.abs(this.y - q.y) <= epsilon && Math.abs(this.z - q.z) <= epsilon && Math.abs(this.w - q.w) <= epsilon;
177
+ }
178
+ }
package/src/Random.ts ADDED
@@ -0,0 +1,32 @@
1
+ export class SeededRandom {
2
+ private state: number;
3
+
4
+ constructor(seed: number) {
5
+ if (!Number.isInteger(seed)) throw new RangeError("Seed must be an integer.");
6
+ this.state = seed >>> 0;
7
+ }
8
+
9
+ nextUint32(): number {
10
+ let x = this.state;
11
+ x ^= x << 13;
12
+ x ^= x >>> 17;
13
+ x ^= x << 5;
14
+ this.state = x >>> 0;
15
+ return this.state;
16
+ }
17
+
18
+ nextFloat(): number {
19
+ return this.nextUint32() / 0x1_0000_0000;
20
+ }
21
+
22
+ range(min: number, max: number): number {
23
+ if (min > max) throw new RangeError("min must be <= max");
24
+ return min + (max - min) * this.nextFloat();
25
+ }
26
+
27
+ clone(): SeededRandom {
28
+ const random = new SeededRandom(0);
29
+ random.state = this.state;
30
+ return random;
31
+ }
32
+ }