@es3d/es3d 1.0.0 → 1.0.1
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 +201 -201
- package/README.md +86 -86
- package/docs/SPEC.md +417 -417
- package/package.json +1 -1
- package/scripts/es3d-cli.mjs +377 -377
- package/src/cache/geometry-store.js +392 -392
- package/src/cache/mesh-bundle.js +123 -123
- package/src/cache/sha256.js +73 -73
- package/src/format/ast.js +238 -238
- package/src/format/colors.js +93 -93
- package/src/format/compact-parser.js +171 -171
- package/src/format/compact-tokenizer.js +165 -165
- package/src/format/errors.js +247 -247
- package/src/format/json.js +55 -55
- package/src/format/limits.js +14 -14
- package/src/format/parse.js +92 -92
- package/src/format/schema.js +617 -617
- package/src/format/serializer.js +333 -333
- package/src/format/validator.js +330 -330
- package/src/format/value-error.js +12 -12
- package/src/format/value-format.js +314 -314
- package/src/format/values.js +459 -459
- package/src/format/xml-parser.js +136 -136
- package/src/format/xml-tokenizer.js +237 -237
- package/src/geometry/contour.js +86 -86
- package/src/geometry/extrude.js +175 -175
- package/src/geometry/gear.js +85 -85
- package/src/geometry/geometry-cache.js +159 -159
- package/src/geometry/lod-error.js +178 -178
- package/src/geometry/mesh.js +458 -458
- package/src/geometry/primitives.js +299 -299
- package/src/geometry/registry.js +645 -645
- package/src/geometry/revolve.js +136 -136
- package/src/geometry/sweep.js +441 -441
- package/src/index.js +47 -47
- package/src/io/compression.js +67 -67
- package/src/io/convert.js +236 -236
- package/src/io/download.js +13 -13
- package/src/io/external-scene.js +127 -127
- package/src/io/file-loader.js +28 -28
- package/src/io/gltf.js +630 -630
- package/src/io/mesh-import.js +104 -104
- package/src/io/obj.js +352 -352
- package/src/io/step-brep.js +439 -439
- package/src/io/step-exact.js +1149 -1149
- package/src/io/step.js +1418 -1418
- package/src/io/stl.js +193 -193
- package/src/io/svg-import.js +787 -787
- package/src/material/materials.js +154 -154
- package/src/math/mat4.js +475 -475
- package/src/math/random.js +30 -30
- package/src/math/trig.js +65 -65
- package/src/math/vec3.js +81 -81
- package/src/optimize/optimizer.js +579 -579
- package/src/path/path-flatten.js +322 -322
- package/src/path/path-parser.js +332 -332
- package/src/path/shapes.js +129 -129
- package/src/path/triangulate.js +517 -517
- package/src/pipeline.js +74 -74
- package/src/render/camera.js +168 -168
- package/src/render/frame-builder.js +362 -362
- package/src/render/geometry-pool.js +149 -149
- package/src/render/lighting.js +149 -149
- package/src/render/orbit-controls.js +237 -237
- package/src/render/renderer.js +258 -249
- package/src/render/scroll-timeline.js +104 -104
- package/src/render/shaders/glsl.js +285 -285
- package/src/render/shaders/wgsl.js +252 -252
- package/src/render/stage.js +364 -352
- package/src/render/texture-cache.js +40 -40
- package/src/render/textures.js +223 -223
- package/src/render/webgl2/webgl2-backend.js +586 -532
- package/src/render/webgpu/webgpu-backend.js +482 -480
- package/src/scene/flatten.js +754 -754
- package/src/scene/picking.js +161 -161
- package/src/scene/scene-graph.js +320 -320
- package/src/transform/transform.js +211 -211
- package/src/wasm/kernels-binary.js +3 -3
- 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
|
+
}
|