@forgeax/engine-math 0.0.0-dev.8d955ade1c79
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.
- package/LICENSE +202 -0
- package/README.md +294 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/_arbs.d.ts +36 -0
- package/dist/__tests__/_arbs.d.ts.map +1 -0
- package/dist/__tests__/_fixtures.d.ts +61 -0
- package/dist/__tests__/_fixtures.d.ts.map +1 -0
- package/dist/__tests__/bounds2.test.d.ts +2 -0
- package/dist/__tests__/bounds2.test.d.ts.map +1 -0
- package/dist/__tests__/box3.test.d.ts +2 -0
- package/dist/__tests__/box3.test.d.ts.map +1 -0
- package/dist/__tests__/easing.test.d.ts +2 -0
- package/dist/__tests__/easing.test.d.ts.map +1 -0
- package/dist/__tests__/euler.test-d.d.ts +2 -0
- package/dist/__tests__/euler.test-d.d.ts.map +1 -0
- package/dist/__tests__/mat3.test-d.d.ts +2 -0
- package/dist/__tests__/mat3.test-d.d.ts.map +1 -0
- package/dist/__tests__/mat4.property.test.d.ts +2 -0
- package/dist/__tests__/mat4.property.test.d.ts.map +1 -0
- package/dist/__tests__/mat4.test-d.d.ts +2 -0
- package/dist/__tests__/mat4.test-d.d.ts.map +1 -0
- package/dist/__tests__/mat4.test.d.ts +2 -0
- package/dist/__tests__/mat4.test.d.ts.map +1 -0
- package/dist/__tests__/noise.test.d.ts +2 -0
- package/dist/__tests__/noise.test.d.ts.map +1 -0
- package/dist/__tests__/quat.basis.test.d.ts +2 -0
- package/dist/__tests__/quat.basis.test.d.ts.map +1 -0
- package/dist/__tests__/quat.interpolation.test.d.ts +2 -0
- package/dist/__tests__/quat.interpolation.test.d.ts.map +1 -0
- package/dist/__tests__/quat.lookat.test.d.ts +2 -0
- package/dist/__tests__/quat.lookat.test.d.ts.map +1 -0
- package/dist/__tests__/quat.property.test.d.ts +2 -0
- package/dist/__tests__/quat.property.test.d.ts.map +1 -0
- package/dist/__tests__/quat.rotateaxis.test.d.ts +2 -0
- package/dist/__tests__/quat.rotateaxis.test.d.ts.map +1 -0
- package/dist/__tests__/quat.test-d.d.ts +2 -0
- package/dist/__tests__/quat.test-d.d.ts.map +1 -0
- package/dist/__tests__/ray.property.test.d.ts +2 -0
- package/dist/__tests__/ray.property.test.d.ts.map +1 -0
- package/dist/__tests__/ray.test.d.ts +2 -0
- package/dist/__tests__/ray.test.d.ts.map +1 -0
- package/dist/__tests__/types.test-d.d.ts +2 -0
- package/dist/__tests__/types.test-d.d.ts.map +1 -0
- package/dist/__tests__/vec-catmull-rom.test.d.ts +2 -0
- package/dist/__tests__/vec-catmull-rom.test.d.ts.map +1 -0
- package/dist/__tests__/vec-smooth-damp.test.d.ts +2 -0
- package/dist/__tests__/vec-smooth-damp.test.d.ts.map +1 -0
- package/dist/__tests__/vec2.test-d.d.ts +2 -0
- package/dist/__tests__/vec2.test-d.d.ts.map +1 -0
- package/dist/__tests__/vec3.property.test.d.ts +2 -0
- package/dist/__tests__/vec3.property.test.d.ts.map +1 -0
- package/dist/__tests__/vec3.test-d.d.ts +2 -0
- package/dist/__tests__/vec3.test-d.d.ts.map +1 -0
- package/dist/__tests__/vec4.test-d.d.ts +2 -0
- package/dist/__tests__/vec4.test-d.d.ts.map +1 -0
- package/dist/_internal/epsilon.d.ts +9 -0
- package/dist/_internal/epsilon.d.ts.map +1 -0
- package/dist/_internal/scalar.d.ts +40 -0
- package/dist/_internal/scalar.d.ts.map +1 -0
- package/dist/box2.d.ts +35 -0
- package/dist/box2.d.ts.map +1 -0
- package/dist/box3.d.ts +76 -0
- package/dist/box3.d.ts.map +1 -0
- package/dist/circle2.d.ts +29 -0
- package/dist/circle2.d.ts.map +1 -0
- package/dist/color.d.ts +64 -0
- package/dist/color.d.ts.map +1 -0
- package/dist/easing.d.ts +18 -0
- package/dist/easing.d.ts.map +1 -0
- package/dist/euler.d.ts +51 -0
- package/dist/euler.d.ts.map +1 -0
- package/dist/f32-to-f16-bytes.d.ts +10 -0
- package/dist/f32-to-f16-bytes.d.ts.map +1 -0
- package/dist/frustum.d.ts +49 -0
- package/dist/frustum.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +3688 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mat3.d.ts +70 -0
- package/dist/mat3.d.ts.map +1 -0
- package/dist/mat4.d.ts +329 -0
- package/dist/mat4.d.ts.map +1 -0
- package/dist/noise.d.ts +12 -0
- package/dist/noise.d.ts.map +1 -0
- package/dist/quat.d.ts +312 -0
- package/dist/quat.d.ts.map +1 -0
- package/dist/ray.d.ts +144 -0
- package/dist/ray.d.ts.map +1 -0
- package/dist/ray2.d.ts +38 -0
- package/dist/ray2.d.ts.map +1 -0
- package/dist/sphere.d.ts +41 -0
- package/dist/sphere.d.ts.map +1 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/vec2.d.ts +76 -0
- package/dist/vec2.d.ts.map +1 -0
- package/dist/vec3.d.ts +82 -0
- package/dist/vec3.d.ts.map +1 -0
- package/dist/vec4.d.ts +61 -0
- package/dist/vec4.d.ts.map +1 -0
- package/package.json +57 -0
- package/src/__tests__/_arbs.ts +149 -0
- package/src/__tests__/_fixtures.ts +118 -0
- package/src/__tests__/bounds2.test.ts +146 -0
- package/src/__tests__/box3.test.ts +277 -0
- package/src/__tests__/easing.test.ts +109 -0
- package/src/__tests__/euler.test-d.ts +63 -0
- package/src/__tests__/mat3.test-d.ts +47 -0
- package/src/__tests__/mat4.property.test.ts +256 -0
- package/src/__tests__/mat4.test-d.ts +130 -0
- package/src/__tests__/mat4.test.ts +162 -0
- package/src/__tests__/noise.test.ts +75 -0
- package/src/__tests__/quat.basis.test.ts +134 -0
- package/src/__tests__/quat.interpolation.test.ts +45 -0
- package/src/__tests__/quat.lookat.test.ts +103 -0
- package/src/__tests__/quat.property.test.ts +150 -0
- package/src/__tests__/quat.rotateaxis.test.ts +149 -0
- package/src/__tests__/quat.test-d.ts +138 -0
- package/src/__tests__/ray.property.test.ts +105 -0
- package/src/__tests__/ray.test.ts +539 -0
- package/src/__tests__/types.test-d.ts +64 -0
- package/src/__tests__/vec-catmull-rom.test.ts +125 -0
- package/src/__tests__/vec-smooth-damp.test.ts +167 -0
- package/src/__tests__/vec2.test-d.ts +59 -0
- package/src/__tests__/vec3.property.test.ts +72 -0
- package/src/__tests__/vec3.test-d.ts +65 -0
- package/src/__tests__/vec4.test-d.ts +61 -0
- package/src/_internal/epsilon.ts +29 -0
- package/src/_internal/scalar.ts +90 -0
- package/src/box2.ts +168 -0
- package/src/box3.ts +372 -0
- package/src/circle2.ts +134 -0
- package/src/color.ts +177 -0
- package/src/easing.ts +49 -0
- package/src/euler.ts +239 -0
- package/src/f32-to-f16-bytes.ts +71 -0
- package/src/frustum.ts +240 -0
- package/src/index.ts +65 -0
- package/src/mat3.ts +286 -0
- package/src/mat4.ts +1334 -0
- package/src/noise.ts +78 -0
- package/src/quat.ts +847 -0
- package/src/ray.ts +575 -0
- package/src/ray2.ts +198 -0
- package/src/sphere.ts +138 -0
- package/src/types.ts +78 -0
- package/src/vec2.ts +229 -0
- package/src/vec3.ts +294 -0
- package/src/vec4.ts +235 -0
package/src/frustum.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// frustum.ts — view-frustum plane extraction and intersection tests (M1 / w4).
|
|
2
|
+
//
|
|
3
|
+
// Frustum representation: Float32Array(24) — 6 planes × 4 floats (nx, ny, nz, d) each,
|
|
4
|
+
// normalized. Plane equation: nx*x + ny*y + nz*z + d = 0, positive side = inside frustum.
|
|
5
|
+
//
|
|
6
|
+
// Planes are extracted from a combined view-projection matrix (column-major) by
|
|
7
|
+
// combining rows per the Gribb/Hartmann method (Gribb/Hartmann 2001 "Fast
|
|
8
|
+
// Extraction of Viewing Frustum Planes from the World-View-Projection Matrix").
|
|
9
|
+
// Plane normalization is built into fromViewProjection internally (D-6).
|
|
10
|
+
//
|
|
11
|
+
// Surface: create / fromViewProjection / intersectsBox / intersectsSphere.
|
|
12
|
+
//
|
|
13
|
+
// Related: requirements §AC-01 (frustum function signatures + test coverage);
|
|
14
|
+
// plan-strategy §D-6 (internal normalization).
|
|
15
|
+
|
|
16
|
+
import type { Box3Like } from './box3';
|
|
17
|
+
import type { Mat4Like, Vec3Like } from './types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Frustum storage: Float32Array(24) — 6 planes × 4 floats each (nx, ny, nz, d).
|
|
21
|
+
* Local brand (not part of the seven-piece SSOT; same rationale as Box3).
|
|
22
|
+
*/
|
|
23
|
+
export type Frustum = Float32Array & { readonly __frustum: void };
|
|
24
|
+
|
|
25
|
+
/** Allocate a new Frustum (zero-initialized 6 planes). */
|
|
26
|
+
export function create(): Frustum {
|
|
27
|
+
return new Float32Array(24) as Frustum;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extract 6 frustum planes (left, right, bottom, top, near, far) from a
|
|
32
|
+
* combined view-projection matrix (column-major, right-handed).
|
|
33
|
+
*
|
|
34
|
+
* Uses Gribb/Hartmann method: each plane = sum or difference of VP rows,
|
|
35
|
+
* then normalized so (nx, ny, nz) is a unit vector and d is the signed
|
|
36
|
+
* distance from origin. Plane inside-half-space is nx*x + ny*y + nz*z + d > 0.
|
|
37
|
+
*
|
|
38
|
+
* Writes to `out` and returns it.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const view = mat4.lookAt(mat4.create(), [0, 0, 5], [0, 0, 0], [0, 1, 0]);
|
|
43
|
+
* const proj = mat4.perspective(mat4.create(), Math.PI / 2, 1, 0.1, 100);
|
|
44
|
+
* const vp = mat4.create();
|
|
45
|
+
* mat4.multiply(vp, proj, view);
|
|
46
|
+
* const f = frustum.fromViewProjection(frustum.create(), vp);
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function fromViewProjection(out: Frustum, vp: Mat4Like): Frustum {
|
|
50
|
+
const m0 = vp[0] as number;
|
|
51
|
+
const m1 = vp[1] as number;
|
|
52
|
+
const m2 = vp[2] as number;
|
|
53
|
+
const m3 = vp[3] as number;
|
|
54
|
+
const m4 = vp[4] as number;
|
|
55
|
+
const m5 = vp[5] as number;
|
|
56
|
+
const m6 = vp[6] as number;
|
|
57
|
+
const m7 = vp[7] as number;
|
|
58
|
+
const m8 = vp[8] as number;
|
|
59
|
+
const m9 = vp[9] as number;
|
|
60
|
+
const m10 = vp[10] as number;
|
|
61
|
+
const m11 = vp[11] as number;
|
|
62
|
+
const m12 = vp[12] as number;
|
|
63
|
+
const m13 = vp[13] as number;
|
|
64
|
+
const m14 = vp[14] as number;
|
|
65
|
+
const m15 = vp[15] as number;
|
|
66
|
+
|
|
67
|
+
// Left plane: row3 + row0
|
|
68
|
+
let nx = m3 + m0,
|
|
69
|
+
ny = m7 + m4,
|
|
70
|
+
nz = m11 + m8,
|
|
71
|
+
d = m15 + m12;
|
|
72
|
+
let len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
73
|
+
if (len > 0) {
|
|
74
|
+
const il = 1 / len;
|
|
75
|
+
nx *= il;
|
|
76
|
+
ny *= il;
|
|
77
|
+
nz *= il;
|
|
78
|
+
d *= il;
|
|
79
|
+
}
|
|
80
|
+
out[0] = nx;
|
|
81
|
+
out[1] = ny;
|
|
82
|
+
out[2] = nz;
|
|
83
|
+
out[3] = d;
|
|
84
|
+
|
|
85
|
+
// Right plane: row3 - row0
|
|
86
|
+
nx = m3 - m0;
|
|
87
|
+
ny = m7 - m4;
|
|
88
|
+
nz = m11 - m8;
|
|
89
|
+
d = m15 - m12;
|
|
90
|
+
len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
91
|
+
if (len > 0) {
|
|
92
|
+
const il = 1 / len;
|
|
93
|
+
nx *= il;
|
|
94
|
+
ny *= il;
|
|
95
|
+
nz *= il;
|
|
96
|
+
d *= il;
|
|
97
|
+
}
|
|
98
|
+
out[4] = nx;
|
|
99
|
+
out[5] = ny;
|
|
100
|
+
out[6] = nz;
|
|
101
|
+
out[7] = d;
|
|
102
|
+
|
|
103
|
+
// Bottom plane: row3 + row1
|
|
104
|
+
nx = m3 + m1;
|
|
105
|
+
ny = m7 + m5;
|
|
106
|
+
nz = m11 + m9;
|
|
107
|
+
d = m15 + m13;
|
|
108
|
+
len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
109
|
+
if (len > 0) {
|
|
110
|
+
const il = 1 / len;
|
|
111
|
+
nx *= il;
|
|
112
|
+
ny *= il;
|
|
113
|
+
nz *= il;
|
|
114
|
+
d *= il;
|
|
115
|
+
}
|
|
116
|
+
out[8] = nx;
|
|
117
|
+
out[9] = ny;
|
|
118
|
+
out[10] = nz;
|
|
119
|
+
out[11] = d;
|
|
120
|
+
|
|
121
|
+
// Top plane: row3 - row1
|
|
122
|
+
nx = m3 - m1;
|
|
123
|
+
ny = m7 - m5;
|
|
124
|
+
nz = m11 - m9;
|
|
125
|
+
d = m15 - m13;
|
|
126
|
+
len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
127
|
+
if (len > 0) {
|
|
128
|
+
const il = 1 / len;
|
|
129
|
+
nx *= il;
|
|
130
|
+
ny *= il;
|
|
131
|
+
nz *= il;
|
|
132
|
+
d *= il;
|
|
133
|
+
}
|
|
134
|
+
out[12] = nx;
|
|
135
|
+
out[13] = ny;
|
|
136
|
+
out[14] = nz;
|
|
137
|
+
out[15] = d;
|
|
138
|
+
|
|
139
|
+
// Near plane: row3 + row2
|
|
140
|
+
nx = m3 + m2;
|
|
141
|
+
ny = m7 + m6;
|
|
142
|
+
nz = m11 + m10;
|
|
143
|
+
d = m15 + m14;
|
|
144
|
+
len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
145
|
+
if (len > 0) {
|
|
146
|
+
const il = 1 / len;
|
|
147
|
+
nx *= il;
|
|
148
|
+
ny *= il;
|
|
149
|
+
nz *= il;
|
|
150
|
+
d *= il;
|
|
151
|
+
}
|
|
152
|
+
out[16] = nx;
|
|
153
|
+
out[17] = ny;
|
|
154
|
+
out[18] = nz;
|
|
155
|
+
out[19] = d;
|
|
156
|
+
|
|
157
|
+
// Far plane: row3 - row2
|
|
158
|
+
nx = m3 - m2;
|
|
159
|
+
ny = m7 - m6;
|
|
160
|
+
nz = m11 - m10;
|
|
161
|
+
d = m15 - m14;
|
|
162
|
+
len = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
163
|
+
if (len > 0) {
|
|
164
|
+
const il = 1 / len;
|
|
165
|
+
nx *= il;
|
|
166
|
+
ny *= il;
|
|
167
|
+
nz *= il;
|
|
168
|
+
d *= il;
|
|
169
|
+
}
|
|
170
|
+
out[20] = nx;
|
|
171
|
+
out[21] = ny;
|
|
172
|
+
out[22] = nz;
|
|
173
|
+
out[23] = d;
|
|
174
|
+
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Test whether an AABB intersects the frustum. Conservative: returns `true` when
|
|
180
|
+
* the box straddles a plane boundary, even if partially outside.
|
|
181
|
+
*
|
|
182
|
+
* For each plane, the signed distance of both the positive-most corner (p-vertex)
|
|
183
|
+
* and negative-most corner (n-vertex) are tested. The box is outside only when
|
|
184
|
+
* the p-vertex is on the negative side of any plane.
|
|
185
|
+
*/
|
|
186
|
+
export function intersectsBox(f: Frustum, box: Box3Like): boolean {
|
|
187
|
+
const bx = box[0] as number;
|
|
188
|
+
const by = box[1] as number;
|
|
189
|
+
const bz = box[2] as number;
|
|
190
|
+
const bX = box[3] as number;
|
|
191
|
+
const bY = box[4] as number;
|
|
192
|
+
const bZ = box[5] as number;
|
|
193
|
+
|
|
194
|
+
// For each plane, compute the p-vertex (most positive along plane normal)
|
|
195
|
+
// and test if it's on the negative side. If so, box is entirely outside.
|
|
196
|
+
for (let i = 0; i < 6; i++) {
|
|
197
|
+
const off = i * 4;
|
|
198
|
+
const nx = f[off] as number;
|
|
199
|
+
const ny = f[off + 1] as number;
|
|
200
|
+
const nz = f[off + 2] as number;
|
|
201
|
+
const d = f[off + 3] as number;
|
|
202
|
+
|
|
203
|
+
// p-vertex: corner maximizing dot(normal, corner) = corner select via sign of normal
|
|
204
|
+
const px = nx >= 0 ? bX : bx;
|
|
205
|
+
const py = ny >= 0 ? bY : by;
|
|
206
|
+
const pz = nz >= 0 ? bZ : bz;
|
|
207
|
+
|
|
208
|
+
if (nx * px + ny * py + nz * pz + d < 0) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Test whether a sphere intersects the frustum. Conservative: returns `true`
|
|
217
|
+
* when the sphere straddles a plane boundary.
|
|
218
|
+
*
|
|
219
|
+
* Computes signed distance from the sphere center to each plane;
|
|
220
|
+
* outside when distance < -radius.
|
|
221
|
+
*/
|
|
222
|
+
export function intersectsSphere(f: Frustum, center: Vec3Like, radius: number): boolean {
|
|
223
|
+
const cx = center[0] as number;
|
|
224
|
+
const cy = center[1] as number;
|
|
225
|
+
const cz = center[2] as number;
|
|
226
|
+
|
|
227
|
+
for (let i = 0; i < 6; i++) {
|
|
228
|
+
const off = i * 4;
|
|
229
|
+
const nx = f[off] as number;
|
|
230
|
+
const ny = f[off + 1] as number;
|
|
231
|
+
const nz = f[off + 2] as number;
|
|
232
|
+
const sd = f[off + 3] as number; // plane's d (signed distance from origin)
|
|
233
|
+
|
|
234
|
+
const dist = nx * cx + ny * cy + nz * cz + sd;
|
|
235
|
+
if (dist < -radius) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return true;
|
|
240
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// @forgeax/engine-math — single-entry public API surface (D-P9 / AC-09)
|
|
2
|
+
//
|
|
3
|
+
// Shape: types SSOT top-level re-export + namespace `* as <ns>` wrappers.
|
|
4
|
+
//
|
|
5
|
+
// M1 placeholder state:
|
|
6
|
+
// - types.ts seven-piece brand established (T-002);
|
|
7
|
+
// - vec3 / mat4 / quat legacy implementations kept as baseline before M2 replacement;
|
|
8
|
+
// - vec2 / vec4 / mat3 / quat (new) / color / euler arrive incrementally in M2~M5.
|
|
9
|
+
//
|
|
10
|
+
// Design locks (research §F-3 V8 elements-kinds + plan-strategy §K-3 SoA alignment):
|
|
11
|
+
// - all numeric storage is Float32Array (types.ts brand)
|
|
12
|
+
// - sideEffects: false (package.json) lets bundlers tree-shake at namespace granularity
|
|
13
|
+
// - zero runtime dependencies (AC-16 + T-037 jq guard)
|
|
14
|
+
//
|
|
15
|
+
// Related: requirements §AC-09 single entry + top-level re-export;
|
|
16
|
+
// plan-strategy D-P9 no sub-path + §1.1 file layering.
|
|
17
|
+
|
|
18
|
+
// === type SSOT top-level re-export ===
|
|
19
|
+
export type {
|
|
20
|
+
Color,
|
|
21
|
+
ColorLike,
|
|
22
|
+
Euler,
|
|
23
|
+
EulerOrder,
|
|
24
|
+
Mat3,
|
|
25
|
+
Mat3Like,
|
|
26
|
+
Mat4,
|
|
27
|
+
Mat4Like,
|
|
28
|
+
Quat,
|
|
29
|
+
QuatLike,
|
|
30
|
+
Vec2,
|
|
31
|
+
Vec2Like,
|
|
32
|
+
Vec3,
|
|
33
|
+
Vec3Like,
|
|
34
|
+
Vec4,
|
|
35
|
+
Vec4Like,
|
|
36
|
+
} from './types';
|
|
37
|
+
|
|
38
|
+
// === namespace re-export ===
|
|
39
|
+
//
|
|
40
|
+
// Core 9 namespaces:
|
|
41
|
+
// - vector family vec2 / vec3 / vec4
|
|
42
|
+
// - matrix family mat3 / mat4
|
|
43
|
+
// - rotation quat / euler
|
|
44
|
+
// - color (sRGB↔linear + hex parse/format)
|
|
45
|
+
// - easing (scalar S-curve time remaps)
|
|
46
|
+
// - noise (Perlin noise, growable home for further variants)
|
|
47
|
+
|
|
48
|
+
export * as box2 from './box2';
|
|
49
|
+
export * as box3 from './box3';
|
|
50
|
+
export * as circle2 from './circle2';
|
|
51
|
+
export * as color from './color';
|
|
52
|
+
export * as easing from './easing';
|
|
53
|
+
export * as euler from './euler';
|
|
54
|
+
export * as halfFloat from './f32-to-f16-bytes';
|
|
55
|
+
export * as frustum from './frustum';
|
|
56
|
+
export * as mat3 from './mat3';
|
|
57
|
+
export * as mat4 from './mat4';
|
|
58
|
+
export * as noise from './noise';
|
|
59
|
+
export * as quat from './quat';
|
|
60
|
+
export * as ray from './ray';
|
|
61
|
+
export * as ray2 from './ray2';
|
|
62
|
+
export * as sphere from './sphere';
|
|
63
|
+
export * as vec2 from './vec2';
|
|
64
|
+
export * as vec3 from './vec3';
|
|
65
|
+
export * as vec4 from './vec4';
|
package/src/mat3.ts
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
// mat3.ts — 3x3 matrix namespace (M3 / T-020)
|
|
2
|
+
//
|
|
3
|
+
// 10-function surface (≥ 10 lower bound):
|
|
4
|
+
// create / clone / identity / equals / multiply / transpose / invert /
|
|
5
|
+
// scale / fromMat4 / normalMatrix
|
|
6
|
+
//
|
|
7
|
+
// Memory layout lock (D-P4): 9 floats packed column-major; index mapping m[col*3 + row].
|
|
8
|
+
// Primary use: CPU normal matrix (normalMatrix = transpose(invert(upper-left mat3 of mat4)));
|
|
9
|
+
// callers that upload directly to a GPU UBO should pad to mat4 themselves
|
|
10
|
+
// (see R4 + the reserved toGpuLayout hook).
|
|
11
|
+
//
|
|
12
|
+
// Degenerate convention (same as D-P1): invert(singular) → write identity into out, return out (does not return null).
|
|
13
|
+
//
|
|
14
|
+
// Four ironclad rules (gl-matrix wiki / research §F1):
|
|
15
|
+
// 1. Out-param first (except for query functions, the first parameter = out, return out)
|
|
16
|
+
// 2. Aliasing-safe (out may equal an input; read all source data into locals first)
|
|
17
|
+
// 3. Module-as-namespace (pure functions; no class, no this)
|
|
18
|
+
// 4. Float32Array by default (V8 elements-kinds consistency)
|
|
19
|
+
//
|
|
20
|
+
// Related: requirements §Surface mat3 lower bound 10 + AC-04 (normalMatrix);
|
|
21
|
+
// plan-strategy D-P4 9-float lock + D-P1 invert writes identity;
|
|
22
|
+
// wiki/gl-matrix-overview Out-param four ironclad rules;
|
|
23
|
+
// wiki/typescript-branded-types §7.2 factory template.
|
|
24
|
+
//
|
|
25
|
+
// Degenerate-semantics registry (plan-strategy.md §appendix A; shares numbering #3 with mat4):
|
|
26
|
+
// - mat3.invert(singular) → out = identity (same convention as D-P1)
|
|
27
|
+
// - mat3.normalMatrix(singular) → out = identity (via the internal branch in mat3.invert)
|
|
28
|
+
|
|
29
|
+
import { EPS_DET } from './_internal/epsilon';
|
|
30
|
+
import type { Mat3, Mat3Like, Mat4Like, Vec3Like } from './types';
|
|
31
|
+
|
|
32
|
+
export type { Mat3, Mat3Like };
|
|
33
|
+
|
|
34
|
+
/** Create a Mat3 (default all zero; callers usually call identity() right after). */
|
|
35
|
+
export function create(): Mat3 {
|
|
36
|
+
return new Float32Array(9) as Mat3;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Allocate a new Mat3 copy. */
|
|
40
|
+
export function clone(a: Mat3Like): Mat3 {
|
|
41
|
+
return Float32Array.of(
|
|
42
|
+
a[0] as number,
|
|
43
|
+
a[1] as number,
|
|
44
|
+
a[2] as number,
|
|
45
|
+
a[3] as number,
|
|
46
|
+
a[4] as number,
|
|
47
|
+
a[5] as number,
|
|
48
|
+
a[6] as number,
|
|
49
|
+
a[7] as number,
|
|
50
|
+
a[8] as number,
|
|
51
|
+
) as Mat3;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** out = 3x3 identity (column-major [1,0,0, 0,1,0, 0,0,1]). Returns out. */
|
|
55
|
+
export function identity(out: Mat3): Mat3 {
|
|
56
|
+
out[0] = 1;
|
|
57
|
+
out[1] = 0;
|
|
58
|
+
out[2] = 0;
|
|
59
|
+
out[3] = 0;
|
|
60
|
+
out[4] = 1;
|
|
61
|
+
out[5] = 0;
|
|
62
|
+
out[6] = 0;
|
|
63
|
+
out[7] = 0;
|
|
64
|
+
out[8] = 1;
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Approximate equality: each element differs by ≤ epsilon. NaN inputs always return false. */
|
|
69
|
+
export function equals(a: Mat3Like, b: Mat3Like, epsilon = 1e-6): boolean {
|
|
70
|
+
for (let i = 0; i < 9; i++) {
|
|
71
|
+
const av = a[i] as number;
|
|
72
|
+
const bv = b[i] as number;
|
|
73
|
+
if (Number.isNaN(av) || Number.isNaN(bv)) return false;
|
|
74
|
+
if (Math.abs(av - bv) > epsilon) return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* out = a * b (column-major matrix multiply). Returns out.
|
|
81
|
+
*
|
|
82
|
+
* Aliasing-safe: out may equal a or b; reads all 18 source elements into locals first.
|
|
83
|
+
*/
|
|
84
|
+
export function multiply(out: Mat3, a: Mat3Like, b: Mat3Like): Mat3 {
|
|
85
|
+
const a00 = a[0] as number;
|
|
86
|
+
const a01 = a[1] as number;
|
|
87
|
+
const a02 = a[2] as number;
|
|
88
|
+
const a10 = a[3] as number;
|
|
89
|
+
const a11 = a[4] as number;
|
|
90
|
+
const a12 = a[5] as number;
|
|
91
|
+
const a20 = a[6] as number;
|
|
92
|
+
const a21 = a[7] as number;
|
|
93
|
+
const a22 = a[8] as number;
|
|
94
|
+
const b00 = b[0] as number;
|
|
95
|
+
const b01 = b[1] as number;
|
|
96
|
+
const b02 = b[2] as number;
|
|
97
|
+
const b10 = b[3] as number;
|
|
98
|
+
const b11 = b[4] as number;
|
|
99
|
+
const b12 = b[5] as number;
|
|
100
|
+
const b20 = b[6] as number;
|
|
101
|
+
const b21 = b[7] as number;
|
|
102
|
+
const b22 = b[8] as number;
|
|
103
|
+
out[0] = a00 * b00 + a10 * b01 + a20 * b02;
|
|
104
|
+
out[1] = a01 * b00 + a11 * b01 + a21 * b02;
|
|
105
|
+
out[2] = a02 * b00 + a12 * b01 + a22 * b02;
|
|
106
|
+
out[3] = a00 * b10 + a10 * b11 + a20 * b12;
|
|
107
|
+
out[4] = a01 * b10 + a11 * b11 + a21 * b12;
|
|
108
|
+
out[5] = a02 * b10 + a12 * b11 + a22 * b12;
|
|
109
|
+
out[6] = a00 * b20 + a10 * b21 + a20 * b22;
|
|
110
|
+
out[7] = a01 * b20 + a11 * b21 + a21 * b22;
|
|
111
|
+
out[8] = a02 * b20 + a12 * b21 + a22 * b22;
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* out = transpose(a). Returns out.
|
|
117
|
+
*
|
|
118
|
+
* Aliasing-safe (transpose(m, m) is legal; reads the 6 off-diagonal elements into locals first).
|
|
119
|
+
*/
|
|
120
|
+
export function transpose(out: Mat3, a: Mat3Like): Mat3 {
|
|
121
|
+
const a01 = a[1] as number;
|
|
122
|
+
const a02 = a[2] as number;
|
|
123
|
+
const a12 = a[5] as number;
|
|
124
|
+
const a10 = a[3] as number;
|
|
125
|
+
const a20 = a[6] as number;
|
|
126
|
+
const a21 = a[7] as number;
|
|
127
|
+
out[0] = a[0] as number;
|
|
128
|
+
out[1] = a10;
|
|
129
|
+
out[2] = a20;
|
|
130
|
+
out[3] = a01;
|
|
131
|
+
out[4] = a[4] as number;
|
|
132
|
+
out[5] = a21;
|
|
133
|
+
out[6] = a02;
|
|
134
|
+
out[7] = a12;
|
|
135
|
+
out[8] = a[8] as number;
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* out = invert(a). Returns out.
|
|
141
|
+
*
|
|
142
|
+
* @degrade a singular (|det| < EPS_DET) → out = identity (same convention as D-P1; does not return null).
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* // Caller-side guard:
|
|
147
|
+
* const det = mat3DetForGuard(m); // caller computes det to decide whether to invert
|
|
148
|
+
* const inv = mat3.invert(mat3.create(), m);
|
|
149
|
+
* // If m is singular, inv === identity (no NaN; safe to keep using).
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export function invert(out: Mat3, a: Mat3Like): Mat3 {
|
|
153
|
+
const a00 = a[0] as number;
|
|
154
|
+
const a01 = a[1] as number;
|
|
155
|
+
const a02 = a[2] as number;
|
|
156
|
+
const a10 = a[3] as number;
|
|
157
|
+
const a11 = a[4] as number;
|
|
158
|
+
const a12 = a[5] as number;
|
|
159
|
+
const a20 = a[6] as number;
|
|
160
|
+
const a21 = a[7] as number;
|
|
161
|
+
const a22 = a[8] as number;
|
|
162
|
+
|
|
163
|
+
// cofactors (column-major → derive from row/col indices)
|
|
164
|
+
const b01 = a22 * a11 - a12 * a21;
|
|
165
|
+
const b11 = -a22 * a10 + a12 * a20;
|
|
166
|
+
const b21 = a21 * a10 - a11 * a20;
|
|
167
|
+
const det = a00 * b01 + a01 * b11 + a02 * b21;
|
|
168
|
+
|
|
169
|
+
if (Math.abs(det) < EPS_DET) {
|
|
170
|
+
return identity(out);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const invDet = 1 / det;
|
|
174
|
+
out[0] = b01 * invDet;
|
|
175
|
+
out[1] = (-a22 * a01 + a02 * a21) * invDet;
|
|
176
|
+
out[2] = (a12 * a01 - a02 * a11) * invDet;
|
|
177
|
+
out[3] = b11 * invDet;
|
|
178
|
+
out[4] = (a22 * a00 - a02 * a20) * invDet;
|
|
179
|
+
out[5] = (-a12 * a00 + a02 * a10) * invDet;
|
|
180
|
+
out[6] = b21 * invDet;
|
|
181
|
+
out[7] = (-a21 * a00 + a01 * a20) * invDet;
|
|
182
|
+
out[8] = (a11 * a00 - a01 * a10) * invDet;
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* out = a * Scale(v) (per-column scale). Returns out.
|
|
188
|
+
*
|
|
189
|
+
* v takes the first 3 components (aligned with mat4.scale's vec3 input); mat3's third column is the z scale.
|
|
190
|
+
*/
|
|
191
|
+
export function scale(out: Mat3, a: Mat3Like, v: Vec3Like): Mat3 {
|
|
192
|
+
const x = v[0] as number;
|
|
193
|
+
const y = v[1] as number;
|
|
194
|
+
const z = v[2] as number;
|
|
195
|
+
out[0] = (a[0] as number) * x;
|
|
196
|
+
out[1] = (a[1] as number) * x;
|
|
197
|
+
out[2] = (a[2] as number) * x;
|
|
198
|
+
out[3] = (a[3] as number) * y;
|
|
199
|
+
out[4] = (a[4] as number) * y;
|
|
200
|
+
out[5] = (a[5] as number) * y;
|
|
201
|
+
out[6] = (a[6] as number) * z;
|
|
202
|
+
out[7] = (a[7] as number) * z;
|
|
203
|
+
out[8] = (a[8] as number) * z;
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* out = mat3 extracted from mat4's upper-left 3x3 (drop the 4th row and 4th column). Returns out.
|
|
209
|
+
*
|
|
210
|
+
* Column-major mapping: mat4 col0 [0..2] / col1 [4..6] / col2 [8..10] → mat3 col0/1/2.
|
|
211
|
+
*/
|
|
212
|
+
export function fromMat4(out: Mat3, m: Mat4Like): Mat3 {
|
|
213
|
+
out[0] = m[0] as number;
|
|
214
|
+
out[1] = m[1] as number;
|
|
215
|
+
out[2] = m[2] as number;
|
|
216
|
+
out[3] = m[4] as number;
|
|
217
|
+
out[4] = m[5] as number;
|
|
218
|
+
out[5] = m[6] as number;
|
|
219
|
+
out[6] = m[8] as number;
|
|
220
|
+
out[7] = m[9] as number;
|
|
221
|
+
out[8] = m[10] as number;
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* out = transpose(invert(upper-left 3x3 of m)) (the normal-transform matrix). Returns out.
|
|
227
|
+
*
|
|
228
|
+
* Used to transform normals from model space to world / view space; when m has a non-uniform
|
|
229
|
+
* scale, normals must use normalMatrix (the upper-left 3x3 of m alone is incorrect).
|
|
230
|
+
*
|
|
231
|
+
* Per-frame consumer: feat-20260518-pbr-direct-lighting-mvp M3 / w14 wires
|
|
232
|
+
* `render-system-record.ts` to call this helper once per renderable per frame
|
|
233
|
+
* (host-side computation; result lives in mesh SSBO `normalMatrix` slot at
|
|
234
|
+
* byte offset 64 within each PER_ENTITY_STRIDE = 256 B slot, plan-strategy
|
|
235
|
+
* D-5 + AC-08).
|
|
236
|
+
*
|
|
237
|
+
* @degrade upper-left 3x3 singular -> out = identity (same convention as D-P1, via mat3.invert).
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* const N = mat3.normalMatrix(mat3.create(), modelViewMat);
|
|
242
|
+
* // vertex shader: normal_view = N * normal_model
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
export function normalMatrix(out: Mat3, m: Mat4Like): Mat3 {
|
|
246
|
+
// 1. extract the upper-left 3x3
|
|
247
|
+
const m00 = m[0] as number;
|
|
248
|
+
const m01 = m[1] as number;
|
|
249
|
+
const m02 = m[2] as number;
|
|
250
|
+
const m10 = m[4] as number;
|
|
251
|
+
const m11 = m[5] as number;
|
|
252
|
+
const m12 = m[6] as number;
|
|
253
|
+
const m20 = m[8] as number;
|
|
254
|
+
const m21 = m[9] as number;
|
|
255
|
+
const m22 = m[10] as number;
|
|
256
|
+
|
|
257
|
+
// 2. inline invert + transpose (combine the two steps; avoid a temporary allocation)
|
|
258
|
+
const b01 = m22 * m11 - m12 * m21;
|
|
259
|
+
const b11 = -m22 * m10 + m12 * m20;
|
|
260
|
+
const b21 = m21 * m10 - m11 * m20;
|
|
261
|
+
const det = m00 * b01 + m01 * b11 + m02 * b21;
|
|
262
|
+
|
|
263
|
+
if (Math.abs(det) < EPS_DET) {
|
|
264
|
+
return identity(out);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const invDet = 1 / det;
|
|
268
|
+
// Elements of invert(upper-left), then transpose (swap rows/cols)
|
|
269
|
+
// invert column-major:
|
|
270
|
+
// inv[0]=b01*invDet inv[3]=b11*invDet inv[6]=b21*invDet
|
|
271
|
+
// inv[1]=... inv[4]=... inv[7]=...
|
|
272
|
+
// inv[2]=... inv[5]=... inv[8]=...
|
|
273
|
+
// After transpose (i, j) = inv (j, i)
|
|
274
|
+
// More directly: transposing the invert result = transpose(invert).
|
|
275
|
+
// The transpose(invert) elements are written out below:
|
|
276
|
+
out[0] = b01 * invDet;
|
|
277
|
+
out[3] = (-m22 * m01 + m02 * m21) * invDet;
|
|
278
|
+
out[6] = (m12 * m01 - m02 * m11) * invDet;
|
|
279
|
+
out[1] = b11 * invDet;
|
|
280
|
+
out[4] = (m22 * m00 - m02 * m20) * invDet;
|
|
281
|
+
out[7] = (-m12 * m00 + m02 * m10) * invDet;
|
|
282
|
+
out[2] = b21 * invDet;
|
|
283
|
+
out[5] = (-m21 * m00 + m01 * m20) * invDet;
|
|
284
|
+
out[8] = (m11 * m00 - m01 * m10) * invDet;
|
|
285
|
+
return out;
|
|
286
|
+
}
|