@es3d/es3d 1.0.0 → 1.0.2

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 (80) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +95 -86
  3. package/docs/SPEC.md +426 -417
  4. package/package.json +1 -1
  5. package/scripts/es3d-cli.mjs +390 -377
  6. package/src/cache/geometry-store.js +392 -392
  7. package/src/cache/mesh-bundle.js +123 -123
  8. package/src/cache/sha256.js +73 -73
  9. package/src/format/ast.js +238 -238
  10. package/src/format/colors.js +93 -93
  11. package/src/format/compact-parser.js +171 -171
  12. package/src/format/compact-tokenizer.js +165 -165
  13. package/src/format/errors.js +247 -247
  14. package/src/format/json.js +55 -55
  15. package/src/format/limits.js +14 -14
  16. package/src/format/parse.js +132 -92
  17. package/src/format/schema.js +617 -617
  18. package/src/format/serializer.js +333 -333
  19. package/src/format/validator.js +330 -330
  20. package/src/format/value-error.js +12 -12
  21. package/src/format/value-format.js +314 -314
  22. package/src/format/values.js +459 -459
  23. package/src/format/xml-parser.js +136 -136
  24. package/src/format/xml-tokenizer.js +237 -237
  25. package/src/geometry/contour.js +86 -86
  26. package/src/geometry/extrude.js +175 -175
  27. package/src/geometry/gear.js +85 -85
  28. package/src/geometry/geometry-cache.js +159 -159
  29. package/src/geometry/lod-error.js +178 -178
  30. package/src/geometry/mesh.js +458 -458
  31. package/src/geometry/primitives.js +299 -299
  32. package/src/geometry/registry.js +645 -645
  33. package/src/geometry/revolve.js +136 -136
  34. package/src/geometry/sweep.js +441 -441
  35. package/src/index.js +47 -47
  36. package/src/io/compression.js +67 -67
  37. package/src/io/convert.js +236 -236
  38. package/src/io/download.js +13 -13
  39. package/src/io/external-scene.js +127 -127
  40. package/src/io/file-loader.js +28 -28
  41. package/src/io/gltf-pack.js +1461 -0
  42. package/src/io/gltf.js +630 -630
  43. package/src/io/mesh-import.js +104 -104
  44. package/src/io/obj.js +352 -352
  45. package/src/io/step-brep.js +439 -439
  46. package/src/io/step-exact.js +1149 -1149
  47. package/src/io/step.js +1418 -1418
  48. package/src/io/stl.js +193 -193
  49. package/src/io/svg-import.js +787 -787
  50. package/src/material/materials.js +154 -154
  51. package/src/math/mat4.js +475 -475
  52. package/src/math/random.js +30 -30
  53. package/src/math/trig.js +65 -65
  54. package/src/math/vec3.js +81 -81
  55. package/src/optimize/optimizer.js +604 -579
  56. package/src/path/path-flatten.js +322 -322
  57. package/src/path/path-parser.js +332 -332
  58. package/src/path/shapes.js +129 -129
  59. package/src/path/triangulate.js +517 -517
  60. package/src/pipeline.js +74 -74
  61. package/src/render/camera.js +168 -168
  62. package/src/render/frame-builder.js +362 -362
  63. package/src/render/geometry-pool.js +149 -149
  64. package/src/render/lighting.js +149 -149
  65. package/src/render/orbit-controls.js +237 -237
  66. package/src/render/renderer.js +258 -249
  67. package/src/render/scroll-timeline.js +104 -104
  68. package/src/render/shaders/glsl.js +285 -285
  69. package/src/render/shaders/wgsl.js +252 -252
  70. package/src/render/stage.js +365 -352
  71. package/src/render/texture-cache.js +40 -40
  72. package/src/render/textures.js +223 -223
  73. package/src/render/webgl2/webgl2-backend.js +586 -532
  74. package/src/render/webgpu/webgpu-backend.js +482 -480
  75. package/src/scene/flatten.js +754 -754
  76. package/src/scene/picking.js +161 -161
  77. package/src/scene/scene-graph.js +320 -320
  78. package/src/transform/transform.js +211 -211
  79. package/src/wasm/kernels-binary.js +3 -3
  80. package/src/wasm/kernels.js +385 -385
package/src/math/mat4.js CHANGED
@@ -1,475 +1,475 @@
1
- // Minimal column-major 4x4 matrix library (same memory layout as WebGL/WebGPU).
2
- // Functions write into `out` and return it; `out` may alias an input.
3
- // Rotations use the normative placement trig so two readers build the same matrix.
4
-
5
- import { es3dCos, es3dSin } from './trig.js';
6
-
7
- export const DEG = Math.PI / 180;
8
-
9
- export function create() {
10
- const m = new Float32Array(16);
11
- m[0] = m[5] = m[10] = m[15] = 1;
12
- return m;
13
- }
14
-
15
- export function createF64() {
16
- const m = new Float64Array(16);
17
- m[0] = m[5] = m[10] = m[15] = 1;
18
- return m;
19
- }
20
-
21
- export function identity(out) {
22
- out.fill(0);
23
- out[0] = out[5] = out[10] = out[15] = 1;
24
- return out;
25
- }
26
-
27
- export function copy(out, a) {
28
- for (let i = 0; i < 16; i++) out[i] = a[i];
29
- return out;
30
- }
31
-
32
- export function clone(a) {
33
- return new Float64Array(a);
34
- }
35
-
36
- export function isIdentity(a, eps = 1e-12) {
37
- for (let i = 0; i < 16; i++) {
38
- const e = i % 5 === 0 ? 1 : 0;
39
- if (Math.abs(a[i] - e) > eps) return false;
40
- }
41
- return true;
42
- }
43
-
44
- export function multiply(out, a, b) {
45
- const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
46
- const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
47
- const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
48
- const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
49
- let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
50
- out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
51
- out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
52
- out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
53
- out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
54
- b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
55
- out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
56
- out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
57
- out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
58
- out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
59
- b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
60
- out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
61
- out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
62
- out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
63
- out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
64
- b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
65
- out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
66
- out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
67
- out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
68
- out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
69
- return out;
70
- }
71
-
72
- /** Multiply affine matrices (last row is 0,0,0,1). Faster for scene graphs. */
73
- export function multiplyAffine(out, a, b) {
74
- const a00 = a[0], a01 = a[1], a02 = a[2];
75
- const a10 = a[4], a11 = a[5], a12 = a[6];
76
- const a20 = a[8], a21 = a[9], a22 = a[10];
77
- const a30 = a[12], a31 = a[13], a32 = a[14];
78
- const b00 = b[0], b01 = b[1], b02 = b[2];
79
- const b10 = b[4], b11 = b[5], b12 = b[6];
80
- const b20 = b[8], b21 = b[9], b22 = b[10];
81
- const b30 = b[12], b31 = b[13], b32 = b[14];
82
- out[0] = b00 * a00 + b01 * a10 + b02 * a20;
83
- out[1] = b00 * a01 + b01 * a11 + b02 * a21;
84
- out[2] = b00 * a02 + b01 * a12 + b02 * a22;
85
- out[3] = 0;
86
- out[4] = b10 * a00 + b11 * a10 + b12 * a20;
87
- out[5] = b10 * a01 + b11 * a11 + b12 * a21;
88
- out[6] = b10 * a02 + b11 * a12 + b12 * a22;
89
- out[7] = 0;
90
- out[8] = b20 * a00 + b21 * a10 + b22 * a20;
91
- out[9] = b20 * a01 + b21 * a11 + b22 * a21;
92
- out[10] = b20 * a02 + b21 * a12 + b22 * a22;
93
- out[11] = 0;
94
- out[12] = b30 * a00 + b31 * a10 + b32 * a20 + a30;
95
- out[13] = b30 * a01 + b31 * a11 + b32 * a21 + a31;
96
- out[14] = b30 * a02 + b31 * a12 + b32 * a22 + a32;
97
- out[15] = 1;
98
- return out;
99
- }
100
-
101
- export function fromTranslation(out, x, y, z) {
102
- identity(out);
103
- out[12] = x;
104
- out[13] = y;
105
- out[14] = z;
106
- return out;
107
- }
108
-
109
- export function fromScaling(out, x, y, z) {
110
- identity(out);
111
- out[0] = x;
112
- out[5] = y;
113
- out[10] = z;
114
- return out;
115
- }
116
-
117
- export function fromRotationX(out, rad) {
118
- const c = es3dCos(rad), s = es3dSin(rad);
119
- identity(out);
120
- out[5] = c;
121
- out[6] = s;
122
- out[9] = -s;
123
- out[10] = c;
124
- return out;
125
- }
126
-
127
- export function fromRotationY(out, rad) {
128
- const c = es3dCos(rad), s = es3dSin(rad);
129
- identity(out);
130
- out[0] = c;
131
- out[2] = -s;
132
- out[8] = s;
133
- out[10] = c;
134
- return out;
135
- }
136
-
137
- export function fromRotationZ(out, rad) {
138
- const c = es3dCos(rad), s = es3dSin(rad);
139
- identity(out);
140
- out[0] = c;
141
- out[1] = s;
142
- out[4] = -s;
143
- out[5] = c;
144
- return out;
145
- }
146
-
147
- /**
148
- * ES3D Euler convention: rotation="x,y,z" (degrees) rotates about the fixed X
149
- * axis first, then Y, then Z, i.e. R = Rz * Ry * Rx (Blender "XYZ Euler").
150
- */
151
- export function fromEulerDeg(out, xd, yd, zd) {
152
- const x = xd * DEG, y = yd * DEG, z = zd * DEG;
153
- const cx = es3dCos(x), sx = es3dSin(x);
154
- const cy = es3dCos(y), sy = es3dSin(y);
155
- const cz = es3dCos(z), sz = es3dSin(z);
156
- // columns of Rz*Ry*Rx
157
- out[0] = cz * cy;
158
- out[1] = sz * cy;
159
- out[2] = -sy;
160
- out[3] = 0;
161
- out[4] = cz * sy * sx - sz * cx;
162
- out[5] = sz * sy * sx + cz * cx;
163
- out[6] = cy * sx;
164
- out[7] = 0;
165
- out[8] = cz * sy * cx + sz * sx;
166
- out[9] = sz * sy * cx - cz * sx;
167
- out[10] = cy * cx;
168
- out[11] = 0;
169
- out[12] = 0;
170
- out[13] = 0;
171
- out[14] = 0;
172
- out[15] = 1;
173
- return out;
174
- }
175
-
176
- export function fromAxisAngle(out, ax, ay, az, rad) {
177
- let len = Math.hypot(ax, ay, az);
178
- if (len < 1e-12) return identity(out);
179
- len = 1 / len;
180
- const x = ax * len, y = ay * len, z = az * len;
181
- const s = es3dSin(rad), c = es3dCos(rad), t = 1 - c;
182
- out[0] = x * x * t + c;
183
- out[1] = y * x * t + z * s;
184
- out[2] = z * x * t - y * s;
185
- out[3] = 0;
186
- out[4] = x * y * t - z * s;
187
- out[5] = y * y * t + c;
188
- out[6] = z * y * t + x * s;
189
- out[7] = 0;
190
- out[8] = x * z * t + y * s;
191
- out[9] = y * z * t - x * s;
192
- out[10] = z * z * t + c;
193
- out[11] = 0;
194
- out[12] = 0;
195
- out[13] = 0;
196
- out[14] = 0;
197
- out[15] = 1;
198
- return out;
199
- }
200
-
201
- export function fromQuat(out, x, y, z, w) {
202
- const len = Math.hypot(x, y, z, w) || 1;
203
- x /= len; y /= len; z /= len; w /= len;
204
- const x2 = x + x, y2 = y + y, z2 = z + z;
205
- const xx = x * x2, yx = y * x2, yy = y * y2;
206
- const zx = z * x2, zy = z * y2, zz = z * z2;
207
- const wx = w * x2, wy = w * y2, wz = w * z2;
208
- out[0] = 1 - yy - zz;
209
- out[1] = yx + wz;
210
- out[2] = zx - wy;
211
- out[3] = 0;
212
- out[4] = yx - wz;
213
- out[5] = 1 - xx - zz;
214
- out[6] = zy + wx;
215
- out[7] = 0;
216
- out[8] = zx + wy;
217
- out[9] = zy - wx;
218
- out[10] = 1 - xx - yy;
219
- out[11] = 0;
220
- out[12] = 0;
221
- out[13] = 0;
222
- out[14] = 0;
223
- out[15] = 1;
224
- return out;
225
- }
226
-
227
- /** M = T(position) * R(euler) * S(scale) */
228
- export function compose(out, position, rotationDeg, scale) {
229
- fromEulerDeg(out, rotationDeg[0], rotationDeg[1], rotationDeg[2]);
230
- const sx = scale[0], sy = scale[1], sz = scale[2];
231
- out[0] *= sx; out[1] *= sx; out[2] *= sx;
232
- out[4] *= sy; out[5] *= sy; out[6] *= sy;
233
- out[8] *= sz; out[9] *= sz; out[10] *= sz;
234
- out[12] = position[0];
235
- out[13] = position[1];
236
- out[14] = position[2];
237
- return out;
238
- }
239
-
240
- export function invert(out, a) {
241
- const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
242
- const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
243
- const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
244
- const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
245
- const b00 = a00 * a11 - a01 * a10;
246
- const b01 = a00 * a12 - a02 * a10;
247
- const b02 = a00 * a13 - a03 * a10;
248
- const b03 = a01 * a12 - a02 * a11;
249
- const b04 = a01 * a13 - a03 * a11;
250
- const b05 = a02 * a13 - a03 * a12;
251
- const b06 = a20 * a31 - a21 * a30;
252
- const b07 = a20 * a32 - a22 * a30;
253
- const b08 = a20 * a33 - a23 * a30;
254
- const b09 = a21 * a32 - a22 * a31;
255
- const b10 = a21 * a33 - a23 * a31;
256
- const b11 = a22 * a33 - a23 * a32;
257
- let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
258
- if (!det) return null;
259
- det = 1 / det;
260
- out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
261
- out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
262
- out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
263
- out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
264
- out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
265
- out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
266
- out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
267
- out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
268
- out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
269
- out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
270
- out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
271
- out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
272
- out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
273
- out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
274
- out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
275
- out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
276
- return out;
277
- }
278
-
279
- export function transpose(out, a) {
280
- if (out === a) {
281
- const a01 = a[1], a02 = a[2], a03 = a[3], a12 = a[6], a13 = a[7], a23 = a[11];
282
- out[1] = a[4]; out[2] = a[8]; out[3] = a[12];
283
- out[4] = a01; out[6] = a[9]; out[7] = a[13];
284
- out[8] = a02; out[9] = a12; out[11] = a[14];
285
- out[12] = a03; out[13] = a13; out[14] = a23;
286
- return out;
287
- }
288
- for (let r = 0; r < 4; r++) for (let c = 0; c < 4; c++) out[c * 4 + r] = a[r * 4 + c];
289
- return out;
290
- }
291
-
292
- export function determinant3(a) {
293
- return (
294
- a[0] * (a[5] * a[10] - a[6] * a[9]) -
295
- a[4] * (a[1] * a[10] - a[2] * a[9]) +
296
- a[8] * (a[1] * a[6] - a[2] * a[5])
297
- );
298
- }
299
-
300
- /** Largest axis scale factor of the upper 3x3. */
301
- export function maxScale(a) {
302
- const sx = a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
303
- const sy = a[4] * a[4] + a[5] * a[5] + a[6] * a[6];
304
- const sz = a[8] * a[8] + a[9] * a[9] + a[10] * a[10];
305
- return Math.sqrt(Math.max(sx, sy, sz));
306
- }
307
-
308
- export function transformPoint(out, m, x, y, z) {
309
- const w = m[3] * x + m[7] * y + m[11] * z + m[15] || 1;
310
- out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;
311
- out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;
312
- out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;
313
- return out;
314
- }
315
-
316
- export function transformDir(out, m, x, y, z) {
317
- out[0] = m[0] * x + m[4] * y + m[8] * z;
318
- out[1] = m[1] * x + m[5] * y + m[9] * z;
319
- out[2] = m[2] * x + m[6] * y + m[10] * z;
320
- return out;
321
- }
322
-
323
- /**
324
- * Perspective projection. zeroToOne selects the WebGPU clip-space depth range
325
- * [0,1]; otherwise the OpenGL range [-1,1] is used.
326
- */
327
- export function perspective(out, fovyRad, aspect, near, far, zeroToOne = false) {
328
- const f = 1 / Math.tan(fovyRad / 2);
329
- out.fill(0);
330
- out[0] = f / aspect;
331
- out[5] = f;
332
- out[11] = -1;
333
- if (zeroToOne) {
334
- out[10] = far / (near - far);
335
- out[14] = (far * near) / (near - far);
336
- } else {
337
- out[10] = (far + near) / (near - far);
338
- out[14] = (2 * far * near) / (near - far);
339
- }
340
- return out;
341
- }
342
-
343
- export function ortho(out, left, right, bottom, top, near, far, zeroToOne = false) {
344
- const lr = 1 / (left - right);
345
- const bt = 1 / (bottom - top);
346
- const nf = 1 / (near - far);
347
- out.fill(0);
348
- out[0] = -2 * lr;
349
- out[5] = -2 * bt;
350
- out[12] = (left + right) * lr;
351
- out[13] = (top + bottom) * bt;
352
- if (zeroToOne) {
353
- out[10] = nf;
354
- out[14] = near * nf;
355
- } else {
356
- out[10] = 2 * nf;
357
- out[14] = (far + near) * nf;
358
- }
359
- out[15] = 1;
360
- return out;
361
- }
362
-
363
- export function lookAt(out, eye, target, up) {
364
- let zx = eye[0] - target[0], zy = eye[1] - target[1], zz = eye[2] - target[2];
365
- let len = Math.hypot(zx, zy, zz);
366
- if (len < 1e-12) return identity(out);
367
- zx /= len; zy /= len; zz /= len;
368
- let xx = up[1] * zz - up[2] * zy;
369
- let xy = up[2] * zx - up[0] * zz;
370
- let xz = up[0] * zy - up[1] * zx;
371
- len = Math.hypot(xx, xy, xz);
372
- if (len < 1e-12) {
373
- // up is parallel to the view direction: pick another up vector
374
- const alt = Math.abs(zy) < 0.9 ? [0, 1, 0] : [0, 0, 1];
375
- xx = alt[1] * zz - alt[2] * zy;
376
- xy = alt[2] * zx - alt[0] * zz;
377
- xz = alt[0] * zy - alt[1] * zx;
378
- len = Math.hypot(xx, xy, xz);
379
- }
380
- xx /= len; xy /= len; xz /= len;
381
- const yx = zy * xz - zz * xy;
382
- const yy = zz * xx - zx * xz;
383
- const yz = zx * xy - zy * xx;
384
- out[0] = xx; out[1] = yx; out[2] = zx; out[3] = 0;
385
- out[4] = xy; out[5] = yy; out[6] = zy; out[7] = 0;
386
- out[8] = xz; out[9] = yz; out[10] = zz; out[11] = 0;
387
- out[12] = -(xx * eye[0] + xy * eye[1] + xz * eye[2]);
388
- out[13] = -(yx * eye[0] + yy * eye[1] + yz * eye[2]);
389
- out[14] = -(zx * eye[0] + zy * eye[1] + zz * eye[2]);
390
- out[15] = 1;
391
- return out;
392
- }
393
-
394
- /**
395
- * Decompose an affine matrix into translation, rotation quaternion and scale.
396
- * Returns null when the matrix contains shear (not representable as TRS).
397
- */
398
- export function decompose(m, eps = 1e-5) {
399
- const t = [m[12], m[13], m[14]];
400
- let sx = Math.hypot(m[0], m[1], m[2]);
401
- const sy = Math.hypot(m[4], m[5], m[6]);
402
- const sz = Math.hypot(m[8], m[9], m[10]);
403
- if (determinant3(m) < 0) sx = -sx;
404
- if (Math.abs(sx) < 1e-20 || sy < 1e-20 || sz < 1e-20) return null;
405
- const r = [m[0] / sx, m[1] / sx, m[2] / sx, m[4] / sy, m[5] / sy, m[6] / sy, m[8] / sz, m[9] / sz, m[10] / sz];
406
- // orthogonality check (shear detection)
407
- const d01 = r[0] * r[3] + r[1] * r[4] + r[2] * r[5];
408
- const d02 = r[0] * r[6] + r[1] * r[7] + r[2] * r[8];
409
- const d12 = r[3] * r[6] + r[4] * r[7] + r[5] * r[8];
410
- if (Math.abs(d01) > eps || Math.abs(d02) > eps || Math.abs(d12) > eps) return null;
411
- const q = quatFromRotationMatrix(r);
412
- return { translation: t, rotation: q, scale: [sx, sy, sz] };
413
- }
414
-
415
- /** r: 3x3 column-major rotation matrix */
416
- export function quatFromRotationMatrix(r) {
417
- const m00 = r[0], m10 = r[1], m20 = r[2];
418
- const m01 = r[3], m11 = r[4], m21 = r[5];
419
- const m02 = r[6], m12 = r[7], m22 = r[8];
420
- const trace = m00 + m11 + m22;
421
- let x, y, z, w;
422
- if (trace > 0) {
423
- const s = 0.5 / Math.sqrt(trace + 1);
424
- w = 0.25 / s;
425
- x = (m21 - m12) * s;
426
- y = (m02 - m20) * s;
427
- z = (m10 - m01) * s;
428
- } else if (m00 > m11 && m00 > m22) {
429
- const s = 2 * Math.sqrt(1 + m00 - m11 - m22);
430
- w = (m21 - m12) / s;
431
- x = 0.25 * s;
432
- y = (m01 + m10) / s;
433
- z = (m02 + m20) / s;
434
- } else if (m11 > m22) {
435
- const s = 2 * Math.sqrt(1 + m11 - m00 - m22);
436
- w = (m02 - m20) / s;
437
- x = (m01 + m10) / s;
438
- y = 0.25 * s;
439
- z = (m12 + m21) / s;
440
- } else {
441
- const s = 2 * Math.sqrt(1 + m22 - m00 - m11);
442
- w = (m10 - m01) / s;
443
- x = (m02 + m20) / s;
444
- y = (m12 + m21) / s;
445
- z = 0.25 * s;
446
- }
447
- const len = Math.hypot(x, y, z, w) || 1;
448
- return [x / len, y / len, z / len, w / len];
449
- }
450
-
451
- /** Integer power of an affine matrix by repeated squaring. */
452
- export function power(out, m, n) {
453
- const result = createF64();
454
- let base = new Float64Array(m);
455
- let k = n;
456
- const tmp = new Float64Array(16);
457
- while (k > 0) {
458
- if (k & 1) {
459
- multiplyAffine(tmp, result, base);
460
- result.set(tmp);
461
- }
462
- k >>= 1;
463
- if (k) {
464
- multiplyAffine(tmp, base, base);
465
- base = new Float64Array(tmp);
466
- }
467
- }
468
- out.set(result);
469
- return out;
470
- }
471
-
472
- export function equals(a, b, eps = 1e-9) {
473
- for (let i = 0; i < 16; i++) if (Math.abs(a[i] - b[i]) > eps * Math.max(1, Math.abs(a[i]), Math.abs(b[i]))) return false;
474
- return true;
475
- }
1
+ // Minimal column-major 4x4 matrix library (same memory layout as WebGL/WebGPU).
2
+ // Functions write into `out` and return it; `out` may alias an input.
3
+ // Rotations use the normative placement trig so two readers build the same matrix.
4
+
5
+ import { es3dCos, es3dSin } from './trig.js';
6
+
7
+ export const DEG = Math.PI / 180;
8
+
9
+ export function create() {
10
+ const m = new Float32Array(16);
11
+ m[0] = m[5] = m[10] = m[15] = 1;
12
+ return m;
13
+ }
14
+
15
+ export function createF64() {
16
+ const m = new Float64Array(16);
17
+ m[0] = m[5] = m[10] = m[15] = 1;
18
+ return m;
19
+ }
20
+
21
+ export function identity(out) {
22
+ out.fill(0);
23
+ out[0] = out[5] = out[10] = out[15] = 1;
24
+ return out;
25
+ }
26
+
27
+ export function copy(out, a) {
28
+ for (let i = 0; i < 16; i++) out[i] = a[i];
29
+ return out;
30
+ }
31
+
32
+ export function clone(a) {
33
+ return new Float64Array(a);
34
+ }
35
+
36
+ export function isIdentity(a, eps = 1e-12) {
37
+ for (let i = 0; i < 16; i++) {
38
+ const e = i % 5 === 0 ? 1 : 0;
39
+ if (Math.abs(a[i] - e) > eps) return false;
40
+ }
41
+ return true;
42
+ }
43
+
44
+ export function multiply(out, a, b) {
45
+ const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
46
+ const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
47
+ const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
48
+ const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
49
+ let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
50
+ out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
51
+ out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
52
+ out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
53
+ out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
54
+ b0 = b[4]; b1 = b[5]; b2 = b[6]; b3 = b[7];
55
+ out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
56
+ out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
57
+ out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
58
+ out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
59
+ b0 = b[8]; b1 = b[9]; b2 = b[10]; b3 = b[11];
60
+ out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
61
+ out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
62
+ out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
63
+ out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
64
+ b0 = b[12]; b1 = b[13]; b2 = b[14]; b3 = b[15];
65
+ out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
66
+ out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
67
+ out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
68
+ out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
69
+ return out;
70
+ }
71
+
72
+ /** Multiply affine matrices (last row is 0,0,0,1). Faster for scene graphs. */
73
+ export function multiplyAffine(out, a, b) {
74
+ const a00 = a[0], a01 = a[1], a02 = a[2];
75
+ const a10 = a[4], a11 = a[5], a12 = a[6];
76
+ const a20 = a[8], a21 = a[9], a22 = a[10];
77
+ const a30 = a[12], a31 = a[13], a32 = a[14];
78
+ const b00 = b[0], b01 = b[1], b02 = b[2];
79
+ const b10 = b[4], b11 = b[5], b12 = b[6];
80
+ const b20 = b[8], b21 = b[9], b22 = b[10];
81
+ const b30 = b[12], b31 = b[13], b32 = b[14];
82
+ out[0] = b00 * a00 + b01 * a10 + b02 * a20;
83
+ out[1] = b00 * a01 + b01 * a11 + b02 * a21;
84
+ out[2] = b00 * a02 + b01 * a12 + b02 * a22;
85
+ out[3] = 0;
86
+ out[4] = b10 * a00 + b11 * a10 + b12 * a20;
87
+ out[5] = b10 * a01 + b11 * a11 + b12 * a21;
88
+ out[6] = b10 * a02 + b11 * a12 + b12 * a22;
89
+ out[7] = 0;
90
+ out[8] = b20 * a00 + b21 * a10 + b22 * a20;
91
+ out[9] = b20 * a01 + b21 * a11 + b22 * a21;
92
+ out[10] = b20 * a02 + b21 * a12 + b22 * a22;
93
+ out[11] = 0;
94
+ out[12] = b30 * a00 + b31 * a10 + b32 * a20 + a30;
95
+ out[13] = b30 * a01 + b31 * a11 + b32 * a21 + a31;
96
+ out[14] = b30 * a02 + b31 * a12 + b32 * a22 + a32;
97
+ out[15] = 1;
98
+ return out;
99
+ }
100
+
101
+ export function fromTranslation(out, x, y, z) {
102
+ identity(out);
103
+ out[12] = x;
104
+ out[13] = y;
105
+ out[14] = z;
106
+ return out;
107
+ }
108
+
109
+ export function fromScaling(out, x, y, z) {
110
+ identity(out);
111
+ out[0] = x;
112
+ out[5] = y;
113
+ out[10] = z;
114
+ return out;
115
+ }
116
+
117
+ export function fromRotationX(out, rad) {
118
+ const c = es3dCos(rad), s = es3dSin(rad);
119
+ identity(out);
120
+ out[5] = c;
121
+ out[6] = s;
122
+ out[9] = -s;
123
+ out[10] = c;
124
+ return out;
125
+ }
126
+
127
+ export function fromRotationY(out, rad) {
128
+ const c = es3dCos(rad), s = es3dSin(rad);
129
+ identity(out);
130
+ out[0] = c;
131
+ out[2] = -s;
132
+ out[8] = s;
133
+ out[10] = c;
134
+ return out;
135
+ }
136
+
137
+ export function fromRotationZ(out, rad) {
138
+ const c = es3dCos(rad), s = es3dSin(rad);
139
+ identity(out);
140
+ out[0] = c;
141
+ out[1] = s;
142
+ out[4] = -s;
143
+ out[5] = c;
144
+ return out;
145
+ }
146
+
147
+ /**
148
+ * ES3D Euler convention: rotation="x,y,z" (degrees) rotates about the fixed X
149
+ * axis first, then Y, then Z, i.e. R = Rz * Ry * Rx (Blender "XYZ Euler").
150
+ */
151
+ export function fromEulerDeg(out, xd, yd, zd) {
152
+ const x = xd * DEG, y = yd * DEG, z = zd * DEG;
153
+ const cx = es3dCos(x), sx = es3dSin(x);
154
+ const cy = es3dCos(y), sy = es3dSin(y);
155
+ const cz = es3dCos(z), sz = es3dSin(z);
156
+ // columns of Rz*Ry*Rx
157
+ out[0] = cz * cy;
158
+ out[1] = sz * cy;
159
+ out[2] = -sy;
160
+ out[3] = 0;
161
+ out[4] = cz * sy * sx - sz * cx;
162
+ out[5] = sz * sy * sx + cz * cx;
163
+ out[6] = cy * sx;
164
+ out[7] = 0;
165
+ out[8] = cz * sy * cx + sz * sx;
166
+ out[9] = sz * sy * cx - cz * sx;
167
+ out[10] = cy * cx;
168
+ out[11] = 0;
169
+ out[12] = 0;
170
+ out[13] = 0;
171
+ out[14] = 0;
172
+ out[15] = 1;
173
+ return out;
174
+ }
175
+
176
+ export function fromAxisAngle(out, ax, ay, az, rad) {
177
+ let len = Math.hypot(ax, ay, az);
178
+ if (len < 1e-12) return identity(out);
179
+ len = 1 / len;
180
+ const x = ax * len, y = ay * len, z = az * len;
181
+ const s = es3dSin(rad), c = es3dCos(rad), t = 1 - c;
182
+ out[0] = x * x * t + c;
183
+ out[1] = y * x * t + z * s;
184
+ out[2] = z * x * t - y * s;
185
+ out[3] = 0;
186
+ out[4] = x * y * t - z * s;
187
+ out[5] = y * y * t + c;
188
+ out[6] = z * y * t + x * s;
189
+ out[7] = 0;
190
+ out[8] = x * z * t + y * s;
191
+ out[9] = y * z * t - x * s;
192
+ out[10] = z * z * t + c;
193
+ out[11] = 0;
194
+ out[12] = 0;
195
+ out[13] = 0;
196
+ out[14] = 0;
197
+ out[15] = 1;
198
+ return out;
199
+ }
200
+
201
+ export function fromQuat(out, x, y, z, w) {
202
+ const len = Math.hypot(x, y, z, w) || 1;
203
+ x /= len; y /= len; z /= len; w /= len;
204
+ const x2 = x + x, y2 = y + y, z2 = z + z;
205
+ const xx = x * x2, yx = y * x2, yy = y * y2;
206
+ const zx = z * x2, zy = z * y2, zz = z * z2;
207
+ const wx = w * x2, wy = w * y2, wz = w * z2;
208
+ out[0] = 1 - yy - zz;
209
+ out[1] = yx + wz;
210
+ out[2] = zx - wy;
211
+ out[3] = 0;
212
+ out[4] = yx - wz;
213
+ out[5] = 1 - xx - zz;
214
+ out[6] = zy + wx;
215
+ out[7] = 0;
216
+ out[8] = zx + wy;
217
+ out[9] = zy - wx;
218
+ out[10] = 1 - xx - yy;
219
+ out[11] = 0;
220
+ out[12] = 0;
221
+ out[13] = 0;
222
+ out[14] = 0;
223
+ out[15] = 1;
224
+ return out;
225
+ }
226
+
227
+ /** M = T(position) * R(euler) * S(scale) */
228
+ export function compose(out, position, rotationDeg, scale) {
229
+ fromEulerDeg(out, rotationDeg[0], rotationDeg[1], rotationDeg[2]);
230
+ const sx = scale[0], sy = scale[1], sz = scale[2];
231
+ out[0] *= sx; out[1] *= sx; out[2] *= sx;
232
+ out[4] *= sy; out[5] *= sy; out[6] *= sy;
233
+ out[8] *= sz; out[9] *= sz; out[10] *= sz;
234
+ out[12] = position[0];
235
+ out[13] = position[1];
236
+ out[14] = position[2];
237
+ return out;
238
+ }
239
+
240
+ export function invert(out, a) {
241
+ const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
242
+ const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
243
+ const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
244
+ const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
245
+ const b00 = a00 * a11 - a01 * a10;
246
+ const b01 = a00 * a12 - a02 * a10;
247
+ const b02 = a00 * a13 - a03 * a10;
248
+ const b03 = a01 * a12 - a02 * a11;
249
+ const b04 = a01 * a13 - a03 * a11;
250
+ const b05 = a02 * a13 - a03 * a12;
251
+ const b06 = a20 * a31 - a21 * a30;
252
+ const b07 = a20 * a32 - a22 * a30;
253
+ const b08 = a20 * a33 - a23 * a30;
254
+ const b09 = a21 * a32 - a22 * a31;
255
+ const b10 = a21 * a33 - a23 * a31;
256
+ const b11 = a22 * a33 - a23 * a32;
257
+ let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
258
+ if (!det) return null;
259
+ det = 1 / det;
260
+ out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
261
+ out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
262
+ out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
263
+ out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
264
+ out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
265
+ out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
266
+ out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
267
+ out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
268
+ out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
269
+ out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
270
+ out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
271
+ out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
272
+ out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
273
+ out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
274
+ out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
275
+ out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
276
+ return out;
277
+ }
278
+
279
+ export function transpose(out, a) {
280
+ if (out === a) {
281
+ const a01 = a[1], a02 = a[2], a03 = a[3], a12 = a[6], a13 = a[7], a23 = a[11];
282
+ out[1] = a[4]; out[2] = a[8]; out[3] = a[12];
283
+ out[4] = a01; out[6] = a[9]; out[7] = a[13];
284
+ out[8] = a02; out[9] = a12; out[11] = a[14];
285
+ out[12] = a03; out[13] = a13; out[14] = a23;
286
+ return out;
287
+ }
288
+ for (let r = 0; r < 4; r++) for (let c = 0; c < 4; c++) out[c * 4 + r] = a[r * 4 + c];
289
+ return out;
290
+ }
291
+
292
+ export function determinant3(a) {
293
+ return (
294
+ a[0] * (a[5] * a[10] - a[6] * a[9]) -
295
+ a[4] * (a[1] * a[10] - a[2] * a[9]) +
296
+ a[8] * (a[1] * a[6] - a[2] * a[5])
297
+ );
298
+ }
299
+
300
+ /** Largest axis scale factor of the upper 3x3. */
301
+ export function maxScale(a) {
302
+ const sx = a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
303
+ const sy = a[4] * a[4] + a[5] * a[5] + a[6] * a[6];
304
+ const sz = a[8] * a[8] + a[9] * a[9] + a[10] * a[10];
305
+ return Math.sqrt(Math.max(sx, sy, sz));
306
+ }
307
+
308
+ export function transformPoint(out, m, x, y, z) {
309
+ const w = m[3] * x + m[7] * y + m[11] * z + m[15] || 1;
310
+ out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;
311
+ out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;
312
+ out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;
313
+ return out;
314
+ }
315
+
316
+ export function transformDir(out, m, x, y, z) {
317
+ out[0] = m[0] * x + m[4] * y + m[8] * z;
318
+ out[1] = m[1] * x + m[5] * y + m[9] * z;
319
+ out[2] = m[2] * x + m[6] * y + m[10] * z;
320
+ return out;
321
+ }
322
+
323
+ /**
324
+ * Perspective projection. zeroToOne selects the WebGPU clip-space depth range
325
+ * [0,1]; otherwise the OpenGL range [-1,1] is used.
326
+ */
327
+ export function perspective(out, fovyRad, aspect, near, far, zeroToOne = false) {
328
+ const f = 1 / Math.tan(fovyRad / 2);
329
+ out.fill(0);
330
+ out[0] = f / aspect;
331
+ out[5] = f;
332
+ out[11] = -1;
333
+ if (zeroToOne) {
334
+ out[10] = far / (near - far);
335
+ out[14] = (far * near) / (near - far);
336
+ } else {
337
+ out[10] = (far + near) / (near - far);
338
+ out[14] = (2 * far * near) / (near - far);
339
+ }
340
+ return out;
341
+ }
342
+
343
+ export function ortho(out, left, right, bottom, top, near, far, zeroToOne = false) {
344
+ const lr = 1 / (left - right);
345
+ const bt = 1 / (bottom - top);
346
+ const nf = 1 / (near - far);
347
+ out.fill(0);
348
+ out[0] = -2 * lr;
349
+ out[5] = -2 * bt;
350
+ out[12] = (left + right) * lr;
351
+ out[13] = (top + bottom) * bt;
352
+ if (zeroToOne) {
353
+ out[10] = nf;
354
+ out[14] = near * nf;
355
+ } else {
356
+ out[10] = 2 * nf;
357
+ out[14] = (far + near) * nf;
358
+ }
359
+ out[15] = 1;
360
+ return out;
361
+ }
362
+
363
+ export function lookAt(out, eye, target, up) {
364
+ let zx = eye[0] - target[0], zy = eye[1] - target[1], zz = eye[2] - target[2];
365
+ let len = Math.hypot(zx, zy, zz);
366
+ if (len < 1e-12) return identity(out);
367
+ zx /= len; zy /= len; zz /= len;
368
+ let xx = up[1] * zz - up[2] * zy;
369
+ let xy = up[2] * zx - up[0] * zz;
370
+ let xz = up[0] * zy - up[1] * zx;
371
+ len = Math.hypot(xx, xy, xz);
372
+ if (len < 1e-12) {
373
+ // up is parallel to the view direction: pick another up vector
374
+ const alt = Math.abs(zy) < 0.9 ? [0, 1, 0] : [0, 0, 1];
375
+ xx = alt[1] * zz - alt[2] * zy;
376
+ xy = alt[2] * zx - alt[0] * zz;
377
+ xz = alt[0] * zy - alt[1] * zx;
378
+ len = Math.hypot(xx, xy, xz);
379
+ }
380
+ xx /= len; xy /= len; xz /= len;
381
+ const yx = zy * xz - zz * xy;
382
+ const yy = zz * xx - zx * xz;
383
+ const yz = zx * xy - zy * xx;
384
+ out[0] = xx; out[1] = yx; out[2] = zx; out[3] = 0;
385
+ out[4] = xy; out[5] = yy; out[6] = zy; out[7] = 0;
386
+ out[8] = xz; out[9] = yz; out[10] = zz; out[11] = 0;
387
+ out[12] = -(xx * eye[0] + xy * eye[1] + xz * eye[2]);
388
+ out[13] = -(yx * eye[0] + yy * eye[1] + yz * eye[2]);
389
+ out[14] = -(zx * eye[0] + zy * eye[1] + zz * eye[2]);
390
+ out[15] = 1;
391
+ return out;
392
+ }
393
+
394
+ /**
395
+ * Decompose an affine matrix into translation, rotation quaternion and scale.
396
+ * Returns null when the matrix contains shear (not representable as TRS).
397
+ */
398
+ export function decompose(m, eps = 1e-5) {
399
+ const t = [m[12], m[13], m[14]];
400
+ let sx = Math.hypot(m[0], m[1], m[2]);
401
+ const sy = Math.hypot(m[4], m[5], m[6]);
402
+ const sz = Math.hypot(m[8], m[9], m[10]);
403
+ if (determinant3(m) < 0) sx = -sx;
404
+ if (Math.abs(sx) < 1e-20 || sy < 1e-20 || sz < 1e-20) return null;
405
+ const r = [m[0] / sx, m[1] / sx, m[2] / sx, m[4] / sy, m[5] / sy, m[6] / sy, m[8] / sz, m[9] / sz, m[10] / sz];
406
+ // orthogonality check (shear detection)
407
+ const d01 = r[0] * r[3] + r[1] * r[4] + r[2] * r[5];
408
+ const d02 = r[0] * r[6] + r[1] * r[7] + r[2] * r[8];
409
+ const d12 = r[3] * r[6] + r[4] * r[7] + r[5] * r[8];
410
+ if (Math.abs(d01) > eps || Math.abs(d02) > eps || Math.abs(d12) > eps) return null;
411
+ const q = quatFromRotationMatrix(r);
412
+ return { translation: t, rotation: q, scale: [sx, sy, sz] };
413
+ }
414
+
415
+ /** r: 3x3 column-major rotation matrix */
416
+ export function quatFromRotationMatrix(r) {
417
+ const m00 = r[0], m10 = r[1], m20 = r[2];
418
+ const m01 = r[3], m11 = r[4], m21 = r[5];
419
+ const m02 = r[6], m12 = r[7], m22 = r[8];
420
+ const trace = m00 + m11 + m22;
421
+ let x, y, z, w;
422
+ if (trace > 0) {
423
+ const s = 0.5 / Math.sqrt(trace + 1);
424
+ w = 0.25 / s;
425
+ x = (m21 - m12) * s;
426
+ y = (m02 - m20) * s;
427
+ z = (m10 - m01) * s;
428
+ } else if (m00 > m11 && m00 > m22) {
429
+ const s = 2 * Math.sqrt(1 + m00 - m11 - m22);
430
+ w = (m21 - m12) / s;
431
+ x = 0.25 * s;
432
+ y = (m01 + m10) / s;
433
+ z = (m02 + m20) / s;
434
+ } else if (m11 > m22) {
435
+ const s = 2 * Math.sqrt(1 + m11 - m00 - m22);
436
+ w = (m02 - m20) / s;
437
+ x = (m01 + m10) / s;
438
+ y = 0.25 * s;
439
+ z = (m12 + m21) / s;
440
+ } else {
441
+ const s = 2 * Math.sqrt(1 + m22 - m00 - m11);
442
+ w = (m10 - m01) / s;
443
+ x = (m02 + m20) / s;
444
+ y = (m12 + m21) / s;
445
+ z = 0.25 * s;
446
+ }
447
+ const len = Math.hypot(x, y, z, w) || 1;
448
+ return [x / len, y / len, z / len, w / len];
449
+ }
450
+
451
+ /** Integer power of an affine matrix by repeated squaring. */
452
+ export function power(out, m, n) {
453
+ const result = createF64();
454
+ let base = new Float64Array(m);
455
+ let k = n;
456
+ const tmp = new Float64Array(16);
457
+ while (k > 0) {
458
+ if (k & 1) {
459
+ multiplyAffine(tmp, result, base);
460
+ result.set(tmp);
461
+ }
462
+ k >>= 1;
463
+ if (k) {
464
+ multiplyAffine(tmp, base, base);
465
+ base = new Float64Array(tmp);
466
+ }
467
+ }
468
+ out.set(result);
469
+ return out;
470
+ }
471
+
472
+ export function equals(a, b, eps = 1e-9) {
473
+ for (let i = 0; i < 16; i++) if (Math.abs(a[i] - b[i]) > eps * Math.max(1, Math.abs(a[i]), Math.abs(b[i]))) return false;
474
+ return true;
475
+ }