@nakednous/tree 0.0.3 → 0.0.5
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/README.md +45 -19
- package/dist/index.js +362 -334
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -65,45 +65,36 @@ const _k = Object.freeze([0, 0, -1]);
|
|
|
65
65
|
*
|
|
66
66
|
* Every function uses only stack locals for intermediates (zero shared state).
|
|
67
67
|
* Every mutating function writes to a caller-provided `out` and returns `out`.
|
|
68
|
+
* Returns null on degeneracy (singular matrix, etc.).
|
|
68
69
|
*/
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
72
|
-
// Mat4
|
|
73
|
+
// Mat4 math
|
|
73
74
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
74
75
|
|
|
75
|
-
/** out =
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** out = a * b (column-major) */
|
|
85
|
-
function mat4Mul(out, a, b) {
|
|
86
|
-
const a0=a[0],a1=a[1],a2=a[2],a3=a[3],
|
|
87
|
-
a4=a[4],a5=a[5],a6=a[6],a7=a[7],
|
|
88
|
-
a8=a[8],a9=a[9],a10=a[10],a11=a[11],
|
|
89
|
-
a12=a[12],a13=a[13],a14=a[14],a15=a[15];
|
|
90
|
-
let b0,b1,b2,b3;
|
|
91
|
-
b0=b[0];b1=b[1];b2=b[2];b3=b[3];
|
|
76
|
+
/** out = A · B (column-major, standard math order) */
|
|
77
|
+
function mat4Mul(out, A, B) {
|
|
78
|
+
const a0=A[0],a1=A[1],a2=A[2],a3=A[3],
|
|
79
|
+
a4=A[4],a5=A[5],a6=A[6],a7=A[7],
|
|
80
|
+
a8=A[8],a9=A[9],a10=A[10],a11=A[11],
|
|
81
|
+
a12=A[12],a13=A[13],a14=A[14],a15=A[15];
|
|
82
|
+
let b0=B[0],b1=B[1],b2=B[2],b3=B[3];
|
|
92
83
|
out[0]=a0*b0+a4*b1+a8*b2+a12*b3;
|
|
93
84
|
out[1]=a1*b0+a5*b1+a9*b2+a13*b3;
|
|
94
85
|
out[2]=a2*b0+a6*b1+a10*b2+a14*b3;
|
|
95
86
|
out[3]=a3*b0+a7*b1+a11*b2+a15*b3;
|
|
96
|
-
b0=
|
|
87
|
+
b0=B[4];b1=B[5];b2=B[6];b3=B[7];
|
|
97
88
|
out[4]=a0*b0+a4*b1+a8*b2+a12*b3;
|
|
98
89
|
out[5]=a1*b0+a5*b1+a9*b2+a13*b3;
|
|
99
90
|
out[6]=a2*b0+a6*b1+a10*b2+a14*b3;
|
|
100
91
|
out[7]=a3*b0+a7*b1+a11*b2+a15*b3;
|
|
101
|
-
b0=
|
|
92
|
+
b0=B[8];b1=B[9];b2=B[10];b3=B[11];
|
|
102
93
|
out[8]=a0*b0+a4*b1+a8*b2+a12*b3;
|
|
103
94
|
out[9]=a1*b0+a5*b1+a9*b2+a13*b3;
|
|
104
95
|
out[10]=a2*b0+a6*b1+a10*b2+a14*b3;
|
|
105
96
|
out[11]=a3*b0+a7*b1+a11*b2+a15*b3;
|
|
106
|
-
b0=
|
|
97
|
+
b0=B[12];b1=B[13];b2=B[14];b3=B[15];
|
|
107
98
|
out[12]=a0*b0+a4*b1+a8*b2+a12*b3;
|
|
108
99
|
out[13]=a1*b0+a5*b1+a9*b2+a13*b3;
|
|
109
100
|
out[14]=a2*b0+a6*b1+a10*b2+a14*b3;
|
|
@@ -111,7 +102,7 @@ function mat4Mul(out, a, b) {
|
|
|
111
102
|
return out;
|
|
112
103
|
}
|
|
113
104
|
|
|
114
|
-
/** out = inverse(src).
|
|
105
|
+
/** out = inverse(src). Returns null if singular. */
|
|
115
106
|
function mat4Invert(out, src) {
|
|
116
107
|
const s=src;
|
|
117
108
|
const a00=s[0],a01=s[1],a02=s[2],a03=s[3],
|
|
@@ -202,32 +193,8 @@ function mat4MulPoint(out, m, x, y, z) {
|
|
|
202
193
|
return out;
|
|
203
194
|
}
|
|
204
195
|
|
|
205
|
-
/** out = mat4 * [x,y,z,0] (direction, no translation) */
|
|
206
|
-
function mat4MulDir(out, m, x, y, z) {
|
|
207
|
-
out[0] = m[0]*x + m[4]*y + m[8]*z;
|
|
208
|
-
out[1] = m[1]*x + m[5]*y + m[9]*z;
|
|
209
|
-
out[2] = m[2]*x + m[6]*y + m[10]*z;
|
|
210
|
-
return out;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/** out = upper-left 3×3 transposed from mat4 (direction / dMatrix extraction) */
|
|
214
|
-
function mat3FromMat4T(out, m) {
|
|
215
|
-
out[0]=m[0]; out[1]=m[4]; out[2]=m[8];
|
|
216
|
-
out[3]=m[1]; out[4]=m[5]; out[5]=m[9];
|
|
217
|
-
out[6]=m[2]; out[7]=m[6]; out[8]=m[10];
|
|
218
|
-
return out;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/** out = mat3 * vec3 */
|
|
222
|
-
function mat3MulVec3(out, m, x, y, z) {
|
|
223
|
-
out[0] = m[0]*x + m[3]*y + m[6]*z;
|
|
224
|
-
out[1] = m[1]*x + m[4]*y + m[7]*z;
|
|
225
|
-
out[2] = m[2]*x + m[5]*y + m[8]*z;
|
|
226
|
-
return out;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
196
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
230
|
-
// Projection queries
|
|
197
|
+
// Projection queries (read scalars from a projection mat4)
|
|
231
198
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
232
199
|
|
|
233
200
|
/** @returns {boolean} true if orthographic */
|
|
@@ -235,8 +202,8 @@ function projIsOrtho(p) { return p[15] !== 0; }
|
|
|
235
202
|
|
|
236
203
|
/**
|
|
237
204
|
* Near plane distance.
|
|
238
|
-
* @param {ArrayLike<number>} p Projection Mat4
|
|
239
|
-
* @param {number} ndcZMin WEBGL (−1) or WEBGPU (0)
|
|
205
|
+
* @param {ArrayLike<number>} p Projection Mat4.
|
|
206
|
+
* @param {number} ndcZMin WEBGL (−1) or WEBGPU (0).
|
|
240
207
|
*/
|
|
241
208
|
function projNear(p, ndcZMin) {
|
|
242
209
|
return p[15] === 0
|
|
@@ -289,107 +256,34 @@ function projHfov(p) {
|
|
|
289
256
|
// Derived matrices (convenience)
|
|
290
257
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
291
258
|
|
|
292
|
-
/** out =
|
|
259
|
+
/** out = P · V */
|
|
293
260
|
function mat4PV(out, proj, view) { return mat4Mul(out, proj, view); }
|
|
294
261
|
|
|
295
|
-
/** out =
|
|
262
|
+
/** out = V · M */
|
|
296
263
|
function mat4MV(out, model, view) { return mat4Mul(out, view, model); }
|
|
297
264
|
|
|
298
|
-
/** out = proj * view * model = P · V · M (standard GL) */
|
|
299
|
-
function mat4PMV(out, proj, model, view) {
|
|
300
|
-
// MV = view * model (V · M)
|
|
301
|
-
const t0=view[0],t1=view[1],t2=view[2],t3=view[3],
|
|
302
|
-
t4=view[4],t5=view[5],t6=view[6],t7=view[7],
|
|
303
|
-
t8=view[8],t9=view[9],t10=view[10],t11=view[11],
|
|
304
|
-
t12=view[12],t13=view[13],t14=view[14],t15=view[15];
|
|
305
|
-
let b0,b1,b2,b3;
|
|
306
|
-
b0=model[0];b1=model[1];b2=model[2];b3=model[3];
|
|
307
|
-
const mv0=t0*b0+t4*b1+t8*b2+t12*b3, mv1=t1*b0+t5*b1+t9*b2+t13*b3,
|
|
308
|
-
mv2=t2*b0+t6*b1+t10*b2+t14*b3, mv3=t3*b0+t7*b1+t11*b2+t15*b3;
|
|
309
|
-
b0=model[4];b1=model[5];b2=model[6];b3=model[7];
|
|
310
|
-
const mv4=t0*b0+t4*b1+t8*b2+t12*b3, mv5=t1*b0+t5*b1+t9*b2+t13*b3,
|
|
311
|
-
mv6=t2*b0+t6*b1+t10*b2+t14*b3, mv7=t3*b0+t7*b1+t11*b2+t15*b3;
|
|
312
|
-
b0=model[8];b1=model[9];b2=model[10];b3=model[11];
|
|
313
|
-
const mv8=t0*b0+t4*b1+t8*b2+t12*b3, mv9=t1*b0+t5*b1+t9*b2+t13*b3,
|
|
314
|
-
mv10=t2*b0+t6*b1+t10*b2+t14*b3, mv11=t3*b0+t7*b1+t11*b2+t15*b3;
|
|
315
|
-
b0=model[12];b1=model[13];b2=model[14];b3=model[15];
|
|
316
|
-
const mv12=t0*b0+t4*b1+t8*b2+t12*b3, mv13=t1*b0+t5*b1+t9*b2+t13*b3,
|
|
317
|
-
mv14=t2*b0+t6*b1+t10*b2+t14*b3, mv15=t3*b0+t7*b1+t11*b2+t15*b3;
|
|
318
|
-
// PMV = proj * MV (P · V · M)
|
|
319
|
-
const p0=proj[0],p1=proj[1],p2=proj[2],p3=proj[3],
|
|
320
|
-
p4=proj[4],p5=proj[5],p6=proj[6],p7=proj[7],
|
|
321
|
-
p8=proj[8],p9=proj[9],p10=proj[10],p11=proj[11],
|
|
322
|
-
p12=proj[12],p13=proj[13],p14=proj[14],p15=proj[15];
|
|
323
|
-
out[0]=p0*mv0+p4*mv1+p8*mv2+p12*mv3;
|
|
324
|
-
out[1]=p1*mv0+p5*mv1+p9*mv2+p13*mv3;
|
|
325
|
-
out[2]=p2*mv0+p6*mv1+p10*mv2+p14*mv3;
|
|
326
|
-
out[3]=p3*mv0+p7*mv1+p11*mv2+p15*mv3;
|
|
327
|
-
out[4]=p0*mv4+p4*mv5+p8*mv6+p12*mv7;
|
|
328
|
-
out[5]=p1*mv4+p5*mv5+p9*mv6+p13*mv7;
|
|
329
|
-
out[6]=p2*mv4+p6*mv5+p10*mv6+p14*mv7;
|
|
330
|
-
out[7]=p3*mv4+p7*mv5+p11*mv6+p15*mv7;
|
|
331
|
-
out[8]=p0*mv8+p4*mv9+p8*mv10+p12*mv11;
|
|
332
|
-
out[9]=p1*mv8+p5*mv9+p9*mv10+p13*mv11;
|
|
333
|
-
out[10]=p2*mv8+p6*mv9+p10*mv10+p14*mv11;
|
|
334
|
-
out[11]=p3*mv8+p7*mv9+p11*mv10+p15*mv11;
|
|
335
|
-
out[12]=p0*mv12+p4*mv13+p8*mv14+p12*mv15;
|
|
336
|
-
out[13]=p1*mv12+p5*mv13+p9*mv14+p13*mv15;
|
|
337
|
-
out[14]=p2*mv12+p6*mv13+p10*mv14+p14*mv15;
|
|
338
|
-
out[15]=p3*mv12+p7*mv13+p11*mv14+p15*mv15;
|
|
339
|
-
return out;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
265
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
343
|
-
//
|
|
266
|
+
// Location / Direction transforms
|
|
344
267
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
345
|
-
//
|
|
346
|
-
// FLAT DISPATCH: every from→to pair is a self-contained leaf.
|
|
347
|
-
// No path calls back into mapLocation/mapDirection (no reentrancy).
|
|
348
|
-
// All intermediates are stack locals (zero shared state).
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
// ── Location Transform ───────────────────────────────────────────────────
|
|
352
268
|
|
|
353
269
|
/**
|
|
354
|
-
* Relative transform for locations (points).
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
* p_to = out · p_from
|
|
361
|
-
*
|
|
362
|
-
* @param {ArrayLike<number>} out Destination 4×4 matrix (length 16).
|
|
363
|
-
* @param {ArrayLike<number>} from Source frame transform.
|
|
364
|
-
* @param {ArrayLike<number>} to Destination frame transform.
|
|
365
|
-
* @returns {ArrayLike<number>|null} `out`, or `null` if `to` is singular.
|
|
270
|
+
* Relative transform for locations (points): out = inv(to) · from.
|
|
271
|
+
* @param {ArrayLike<number>} out 16-element destination.
|
|
272
|
+
* @param {ArrayLike<number>} from Source frame transform.
|
|
273
|
+
* @param {ArrayLike<number>} to Destination frame transform.
|
|
274
|
+
* @returns {ArrayLike<number>|null} out, or null if to is singular.
|
|
366
275
|
*/
|
|
367
276
|
function mat4Location(out, from, to) {
|
|
368
277
|
return mat4Invert(out, to) && mat4Mul(out, out, from);
|
|
369
278
|
}
|
|
370
279
|
|
|
371
|
-
// ── Direction Transform ──────────────────────────────────────────────────
|
|
372
|
-
|
|
373
280
|
/**
|
|
374
|
-
* Relative transform for directions (vectors).
|
|
375
|
-
*
|
|
281
|
+
* Relative transform for directions (vectors): out = to₃ · inv(from₃).
|
|
376
282
|
* Uses only the upper-left 3×3 blocks, ignoring translation.
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
* and maps directions as:
|
|
383
|
-
*
|
|
384
|
-
* d_to = out · d_from
|
|
385
|
-
*
|
|
386
|
-
* Note: the final write is transposed so the result matches this module's
|
|
387
|
-
* matrix layout and multiplication convention.
|
|
388
|
-
*
|
|
389
|
-
* @param {ArrayLike<number>} out Destination 3×3 matrix (length 9).
|
|
390
|
-
* @param {ArrayLike<number>} from Source frame transform.
|
|
391
|
-
* @param {ArrayLike<number>} to Destination frame transform.
|
|
392
|
-
* @returns {ArrayLike<number>|null} `out`, or `null` if `from` is singular.
|
|
283
|
+
* @param {ArrayLike<number>} out 9-element destination.
|
|
284
|
+
* @param {ArrayLike<number>} from Source frame transform.
|
|
285
|
+
* @param {ArrayLike<number>} to Destination frame transform.
|
|
286
|
+
* @returns {ArrayLike<number>|null} out, or null if from is singular.
|
|
393
287
|
*/
|
|
394
288
|
function mat3Direction(out, from, to) {
|
|
395
289
|
const a00=from[0], a01=from[1], a02=from[2],
|
|
@@ -401,33 +295,41 @@ function mat3Direction(out, from, to) {
|
|
|
401
295
|
let det=a00*b01+a01*b11+a02*b21;
|
|
402
296
|
if (Math.abs(det) < 1e-12) return null;
|
|
403
297
|
det=1/det;
|
|
404
|
-
const i00=b01*det;
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
const i10=b11*det;
|
|
408
|
-
const i11=(a22*a00-a02*a20)*det;
|
|
409
|
-
const i12=(a02*a10-a12*a00)*det;
|
|
410
|
-
const i20=b21*det;
|
|
411
|
-
const i21=(a01*a20-a21*a00)*det;
|
|
412
|
-
const i22=(a11*a00-a01*a10)*det;
|
|
298
|
+
const i00=b01*det, i01=(a02*a21-a22*a01)*det, i02=(a12*a01-a02*a11)*det;
|
|
299
|
+
const i10=b11*det, i11=(a22*a00-a02*a20)*det, i12=(a02*a10-a12*a00)*det;
|
|
300
|
+
const i20=b21*det, i21=(a01*a20-a21*a00)*det, i22=(a11*a00-a01*a10)*det;
|
|
413
301
|
const t00=to[0], t01=to[1], t02=to[2],
|
|
414
302
|
t10=to[4], t11=to[5], t12=to[6],
|
|
415
303
|
t20=to[8], t21=to[9], t22=to[10];
|
|
416
|
-
const m00=t00*i00+t10*i01+t20*i02;
|
|
417
|
-
const
|
|
418
|
-
const
|
|
419
|
-
const m10=t00*i10+t10*i11+t20*i12;
|
|
420
|
-
const m11=t01*i10+t11*i11+t21*i12;
|
|
421
|
-
const m12=t02*i10+t12*i11+t22*i12;
|
|
422
|
-
const m20=t00*i20+t10*i21+t20*i22;
|
|
423
|
-
const m21=t01*i20+t11*i21+t21*i22;
|
|
424
|
-
const m22=t02*i20+t12*i21+t22*i22;
|
|
304
|
+
const m00=t00*i00+t10*i01+t20*i02, m01=t01*i00+t11*i01+t21*i02, m02=t02*i00+t12*i01+t22*i02;
|
|
305
|
+
const m10=t00*i10+t10*i11+t20*i12, m11=t01*i10+t11*i11+t21*i12, m12=t02*i10+t12*i11+t22*i12;
|
|
306
|
+
const m20=t00*i20+t10*i21+t20*i22, m21=t01*i20+t11*i21+t21*i22, m22=t02*i20+t12*i21+t22*i22;
|
|
425
307
|
out[0]=m00; out[1]=m10; out[2]=m20;
|
|
426
308
|
out[3]=m01; out[4]=m11; out[5]=m21;
|
|
427
309
|
out[6]=m02; out[7]=m12; out[8]=m22;
|
|
428
310
|
return out;
|
|
429
311
|
}
|
|
430
312
|
|
|
313
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
314
|
+
// Space transforms — mapLocation / mapDirection
|
|
315
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
316
|
+
//
|
|
317
|
+
// FLAT DISPATCH: every from→to pair is a self-contained leaf.
|
|
318
|
+
// No path calls back into mapLocation/mapDirection (no reentrancy).
|
|
319
|
+
// All intermediates are stack locals (zero shared state).
|
|
320
|
+
//
|
|
321
|
+
// Matrices bag m:
|
|
322
|
+
// {
|
|
323
|
+
// pMatrix: Float32Array(16) — projection (eye → clip)
|
|
324
|
+
// vMatrix: Float32Array(16) — view (world → eye)
|
|
325
|
+
// eMatrix?: Float32Array(16) — eye (eye → world, inv view); lazy
|
|
326
|
+
// pvMatrix?: Float32Array(16) — P · V; lazy
|
|
327
|
+
// ipvMatrix?:Float32Array(16) — inv(P · V); lazy
|
|
328
|
+
// fromFrame?:Float32Array(16) — MATRIX source frame (custom space)
|
|
329
|
+
// toFrameInv?:Float32Array(16) — inv(MATRIX dest frame)
|
|
330
|
+
// }
|
|
331
|
+
//
|
|
332
|
+
|
|
431
333
|
// ── Location leaf helpers ────────────────────────────────────────────────
|
|
432
334
|
|
|
433
335
|
function _worldToScreen(out, px, py, pz, pv, vp, ndcZMin) {
|
|
@@ -435,45 +337,43 @@ function _worldToScreen(out, px, py, pz, pv, vp, ndcZMin) {
|
|
|
435
337
|
const y = pv[1]*px+pv[5]*py+pv[9]*pz+pv[13];
|
|
436
338
|
const z = pv[2]*px+pv[6]*py+pv[10]*pz+pv[14];
|
|
437
339
|
const w = pv[3]*px+pv[7]*py+pv[11]*pz+pv[15];
|
|
438
|
-
|
|
439
|
-
const
|
|
340
|
+
const xi = (w !== 0 && w !== 1) ? x/w : x;
|
|
341
|
+
const yi = (w !== 0 && w !== 1) ? y/w : y;
|
|
342
|
+
const zi = (w !== 0 && w !== 1) ? z/w : z;
|
|
440
343
|
const ndcZRange = 1 - ndcZMin;
|
|
441
|
-
out[0] = (
|
|
442
|
-
out[1] = (
|
|
443
|
-
out[2] = (
|
|
344
|
+
out[0] = (xi*0.5+0.5)*vp[2]+vp[0];
|
|
345
|
+
out[1] = (yi*0.5+0.5)*vp[3]+vp[1];
|
|
346
|
+
out[2] = (zi - ndcZMin) / ndcZRange;
|
|
444
347
|
return out;
|
|
445
348
|
}
|
|
446
349
|
|
|
447
350
|
function _screenToWorld(out, px, py, pz, ipv, vp, ndcZMin) {
|
|
448
|
-
const sx=(px-vp[0])/vp[2], sy=(py-vp[1])/vp[3];
|
|
449
|
-
const nx=sx*2-1, ny=sy*2-1;
|
|
450
351
|
const ndcZRange = 1 - ndcZMin;
|
|
352
|
+
const nx = ((px-vp[0])/vp[2])*2-1;
|
|
353
|
+
const ny = ((py-vp[1])/vp[3])*2-1;
|
|
451
354
|
const nz = pz * ndcZRange + ndcZMin;
|
|
452
|
-
const x=ipv[0]*nx+ipv[4]*ny+ipv[8]*nz+ipv[12];
|
|
453
|
-
const y=ipv[1]*nx+ipv[5]*ny+ipv[9]*nz+ipv[13];
|
|
454
|
-
const z=ipv[2]*nx+ipv[6]*ny+ipv[10]*nz+ipv[14];
|
|
455
|
-
const w=ipv[3]*nx+ipv[7]*ny+ipv[11]*nz+ipv[15];
|
|
456
|
-
if (w === 0) { out[0]=px; out[1]=py; out[2]=pz; return out; }
|
|
355
|
+
const x = ipv[0]*nx+ipv[4]*ny+ipv[8]*nz+ipv[12];
|
|
356
|
+
const y = ipv[1]*nx+ipv[5]*ny+ipv[9]*nz+ipv[13];
|
|
357
|
+
const z = ipv[2]*nx+ipv[6]*ny+ipv[10]*nz+ipv[14];
|
|
358
|
+
const w = ipv[3]*nx+ipv[7]*ny+ipv[11]*nz+ipv[15];
|
|
457
359
|
out[0]=x/w; out[1]=y/w; out[2]=z/w;
|
|
458
360
|
return out;
|
|
459
361
|
}
|
|
460
362
|
|
|
461
363
|
function _worldToNDC(out, px, py, pz, pv) {
|
|
462
|
-
const x=pv[0]*px+pv[4]*py+pv[8]*pz+pv[12];
|
|
463
|
-
const y=pv[1]*px+pv[5]*py+pv[9]*pz+pv[13];
|
|
464
|
-
const z=pv[2]*px+pv[6]*py+pv[10]*pz+pv[14];
|
|
465
|
-
const w=pv[3]*px+pv[7]*py+pv[11]*pz+pv[15];
|
|
466
|
-
if (w === 0) { out[0]=px; out[1]=py; out[2]=pz; return out; }
|
|
364
|
+
const x = pv[0]*px+pv[4]*py+pv[8]*pz+pv[12];
|
|
365
|
+
const y = pv[1]*px+pv[5]*py+pv[9]*pz+pv[13];
|
|
366
|
+
const z = pv[2]*px+pv[6]*py+pv[10]*pz+pv[14];
|
|
367
|
+
const w = pv[3]*px+pv[7]*py+pv[11]*pz+pv[15];
|
|
467
368
|
out[0]=x/w; out[1]=y/w; out[2]=z/w;
|
|
468
369
|
return out;
|
|
469
370
|
}
|
|
470
371
|
|
|
471
372
|
function _ndcToWorld(out, px, py, pz, ipv) {
|
|
472
|
-
const x=ipv[0]*px+ipv[4]*py+ipv[8]*pz+ipv[12];
|
|
473
|
-
const y=ipv[1]*px+ipv[5]*py+ipv[9]*pz+ipv[13];
|
|
474
|
-
const z=ipv[2]*px+ipv[6]*py+ipv[10]*pz+ipv[14];
|
|
475
|
-
const w=ipv[3]*px+ipv[7]*py+ipv[11]*pz+ipv[15];
|
|
476
|
-
if (w === 0) { out[0]=px; out[1]=py; out[2]=pz; return out; }
|
|
373
|
+
const x = ipv[0]*px+ipv[4]*py+ipv[8]*pz+ipv[12];
|
|
374
|
+
const y = ipv[1]*px+ipv[5]*py+ipv[9]*pz+ipv[13];
|
|
375
|
+
const z = ipv[2]*px+ipv[6]*py+ipv[10]*pz+ipv[14];
|
|
376
|
+
const w = ipv[3]*px+ipv[7]*py+ipv[11]*pz+ipv[15];
|
|
477
377
|
out[0]=x/w; out[1]=y/w; out[2]=z/w;
|
|
478
378
|
return out;
|
|
479
379
|
}
|
|
@@ -494,12 +394,11 @@ function _ndcToScreen(out, px, py, pz, vp, ndcZMin) {
|
|
|
494
394
|
return out;
|
|
495
395
|
}
|
|
496
396
|
|
|
497
|
-
// ──
|
|
397
|
+
// ── _ensurePV — return pvMatrix from bag, computing inline if absent ──────
|
|
498
398
|
|
|
499
399
|
function _ensurePV(m) {
|
|
500
|
-
if (m.
|
|
501
|
-
|
|
502
|
-
const p = m.proj, v = m.view;
|
|
400
|
+
if (m.pvMatrix) return m.pvMatrix;
|
|
401
|
+
const p = m.pMatrix, v = m.vMatrix;
|
|
503
402
|
return [
|
|
504
403
|
p[0]*v[0]+p[4]*v[1]+p[8]*v[2]+p[12]*v[3],
|
|
505
404
|
p[1]*v[0]+p[5]*v[1]+p[9]*v[2]+p[13]*v[3],
|
|
@@ -523,27 +422,27 @@ function _ensurePV(m) {
|
|
|
523
422
|
/**
|
|
524
423
|
* Map a point between coordinate spaces.
|
|
525
424
|
*
|
|
526
|
-
* @param {Vec3} out
|
|
527
|
-
* @param {number} px,py,pz
|
|
528
|
-
* @param {string} from
|
|
529
|
-
* @param {string} to
|
|
530
|
-
* @param {object} m
|
|
531
|
-
*
|
|
532
|
-
* @param {Vec4} vp
|
|
533
|
-
* @param {number} ndcZMin
|
|
425
|
+
* @param {Vec3} out Result written here.
|
|
426
|
+
* @param {number} px,py,pz Input point.
|
|
427
|
+
* @param {string} from Source space constant.
|
|
428
|
+
* @param {string} to Target space constant.
|
|
429
|
+
* @param {object} m Matrices bag:
|
|
430
|
+
* { pMatrix, vMatrix, eMatrix?, pvMatrix?, ipvMatrix?, fromFrame?, toFrameInv? }
|
|
431
|
+
* @param {Vec4} vp Viewport [x, y, width, height].
|
|
432
|
+
* @param {number} ndcZMin WEBGL (−1) or WEBGPU (0).
|
|
534
433
|
*/
|
|
535
434
|
function mapLocation(out, px, py, pz, from, to, m, vp, ndcZMin) {
|
|
536
435
|
// WORLD ↔ SCREEN
|
|
537
436
|
if (from === WORLD && to === SCREEN)
|
|
538
437
|
return _worldToScreen(out, px,py,pz, _ensurePV(m), vp, ndcZMin);
|
|
539
438
|
if (from === SCREEN && to === WORLD)
|
|
540
|
-
return _screenToWorld(out, px,py,pz, m.
|
|
439
|
+
return _screenToWorld(out, px,py,pz, m.ipvMatrix, vp, ndcZMin);
|
|
541
440
|
|
|
542
441
|
// WORLD ↔ NDC
|
|
543
442
|
if (from === WORLD && to === NDC)
|
|
544
443
|
return _worldToNDC(out, px,py,pz, _ensurePV(m));
|
|
545
444
|
if (from === NDC && to === WORLD)
|
|
546
|
-
return _ndcToWorld(out, px,py,pz, m.
|
|
445
|
+
return _ndcToWorld(out, px,py,pz, m.ipvMatrix);
|
|
547
446
|
|
|
548
447
|
// SCREEN ↔ NDC
|
|
549
448
|
if (from === SCREEN && to === NDC)
|
|
@@ -553,34 +452,36 @@ function mapLocation(out, px, py, pz, from, to, m, vp, ndcZMin) {
|
|
|
553
452
|
|
|
554
453
|
// WORLD ↔ EYE
|
|
555
454
|
if (from === WORLD && to === EYE)
|
|
556
|
-
return mat4MulPoint(out, m.
|
|
455
|
+
return mat4MulPoint(out, m.vMatrix, px,py,pz);
|
|
557
456
|
if (from === EYE && to === WORLD)
|
|
558
|
-
return mat4MulPoint(out, m.
|
|
457
|
+
return mat4MulPoint(out, m.eMatrix, px,py,pz);
|
|
559
458
|
|
|
560
|
-
// EYE ↔ SCREEN
|
|
459
|
+
// EYE ↔ SCREEN
|
|
561
460
|
if (from === EYE && to === SCREEN) {
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
461
|
+
const e = m.eMatrix;
|
|
462
|
+
const ex=e[0]*px+e[4]*py+e[8]*pz+e[12],
|
|
463
|
+
ey=e[1]*px+e[5]*py+e[9]*pz+e[13],
|
|
464
|
+
ez=e[2]*px+e[6]*py+e[10]*pz+e[14];
|
|
565
465
|
return _worldToScreen(out, ex,ey,ez, _ensurePV(m), vp, ndcZMin);
|
|
566
466
|
}
|
|
567
467
|
if (from === SCREEN && to === EYE) {
|
|
568
|
-
_screenToWorld(out, px,py,pz, m.
|
|
468
|
+
_screenToWorld(out, px,py,pz, m.ipvMatrix, vp, ndcZMin);
|
|
569
469
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
570
|
-
return mat4MulPoint(out, m.
|
|
470
|
+
return mat4MulPoint(out, m.vMatrix, wx,wy,wz);
|
|
571
471
|
}
|
|
572
472
|
|
|
573
|
-
// EYE ↔ NDC
|
|
473
|
+
// EYE ↔ NDC
|
|
574
474
|
if (from === EYE && to === NDC) {
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
475
|
+
const e = m.eMatrix;
|
|
476
|
+
const ex=e[0]*px+e[4]*py+e[8]*pz+e[12],
|
|
477
|
+
ey=e[1]*px+e[5]*py+e[9]*pz+e[13],
|
|
478
|
+
ez=e[2]*px+e[6]*py+e[10]*pz+e[14];
|
|
578
479
|
return _worldToNDC(out, ex,ey,ez, _ensurePV(m));
|
|
579
480
|
}
|
|
580
481
|
if (from === NDC && to === EYE) {
|
|
581
|
-
_ndcToWorld(out, px,py,pz, m.
|
|
482
|
+
_ndcToWorld(out, px,py,pz, m.ipvMatrix);
|
|
582
483
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
583
|
-
return mat4MulPoint(out, m.
|
|
484
|
+
return mat4MulPoint(out, m.vMatrix, wx,wy,wz);
|
|
584
485
|
}
|
|
585
486
|
|
|
586
487
|
// MATRIX (custom frame) ↔ WORLD
|
|
@@ -591,49 +492,54 @@ function mapLocation(out, px, py, pz, from, to, m, vp, ndcZMin) {
|
|
|
591
492
|
|
|
592
493
|
// MATRIX ↔ EYE
|
|
593
494
|
if (from === MATRIX && to === EYE) {
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
495
|
+
const f = m.fromFrame;
|
|
496
|
+
const fx=f[0]*px+f[4]*py+f[8]*pz+f[12],
|
|
497
|
+
fy=f[1]*px+f[5]*py+f[9]*pz+f[13],
|
|
498
|
+
fz=f[2]*px+f[6]*py+f[10]*pz+f[14];
|
|
499
|
+
return mat4MulPoint(out, m.vMatrix, fx,fy,fz);
|
|
598
500
|
}
|
|
599
501
|
if (from === EYE && to === MATRIX) {
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
502
|
+
const e = m.eMatrix;
|
|
503
|
+
const ex=e[0]*px+e[4]*py+e[8]*pz+e[12],
|
|
504
|
+
ey=e[1]*px+e[5]*py+e[9]*pz+e[13],
|
|
505
|
+
ez=e[2]*px+e[6]*py+e[10]*pz+e[14];
|
|
603
506
|
return mat4MulPoint(out, m.toFrameInv, ex,ey,ez);
|
|
604
507
|
}
|
|
605
508
|
|
|
606
509
|
// MATRIX ↔ SCREEN
|
|
607
510
|
if (from === MATRIX && to === SCREEN) {
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
511
|
+
const f = m.fromFrame;
|
|
512
|
+
const fx=f[0]*px+f[4]*py+f[8]*pz+f[12],
|
|
513
|
+
fy=f[1]*px+f[5]*py+f[9]*pz+f[13],
|
|
514
|
+
fz=f[2]*px+f[6]*py+f[10]*pz+f[14];
|
|
611
515
|
return _worldToScreen(out, fx,fy,fz, _ensurePV(m), vp, ndcZMin);
|
|
612
516
|
}
|
|
613
517
|
if (from === SCREEN && to === MATRIX) {
|
|
614
|
-
_screenToWorld(out, px,py,pz, m.
|
|
518
|
+
_screenToWorld(out, px,py,pz, m.ipvMatrix, vp, ndcZMin);
|
|
615
519
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
616
520
|
return mat4MulPoint(out, m.toFrameInv, wx,wy,wz);
|
|
617
521
|
}
|
|
618
522
|
|
|
619
523
|
// MATRIX ↔ NDC
|
|
620
524
|
if (from === MATRIX && to === NDC) {
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
525
|
+
const f = m.fromFrame;
|
|
526
|
+
const fx=f[0]*px+f[4]*py+f[8]*pz+f[12],
|
|
527
|
+
fy=f[1]*px+f[5]*py+f[9]*pz+f[13],
|
|
528
|
+
fz=f[2]*px+f[6]*py+f[10]*pz+f[14];
|
|
624
529
|
return _worldToNDC(out, fx,fy,fz, _ensurePV(m));
|
|
625
530
|
}
|
|
626
531
|
if (from === NDC && to === MATRIX) {
|
|
627
|
-
_ndcToWorld(out, px,py,pz, m.
|
|
532
|
+
_ndcToWorld(out, px,py,pz, m.ipvMatrix);
|
|
628
533
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
629
534
|
return mat4MulPoint(out, m.toFrameInv, wx,wy,wz);
|
|
630
535
|
}
|
|
631
536
|
|
|
632
537
|
// MATRIX ↔ MATRIX
|
|
633
538
|
if (from === MATRIX && to === MATRIX) {
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
539
|
+
const f = m.fromFrame;
|
|
540
|
+
const fx=f[0]*px+f[4]*py+f[8]*pz+f[12],
|
|
541
|
+
fy=f[1]*px+f[5]*py+f[9]*pz+f[13],
|
|
542
|
+
fz=f[2]*px+f[6]*py+f[10]*pz+f[14];
|
|
637
543
|
return mat4MulPoint(out, m.toFrameInv, fx,fy,fz);
|
|
638
544
|
}
|
|
639
545
|
|
|
@@ -644,7 +550,7 @@ function mapLocation(out, px, py, pz, from, to, m, vp, ndcZMin) {
|
|
|
644
550
|
|
|
645
551
|
// ── Direction helpers ────────────────────────────────────────────────────
|
|
646
552
|
|
|
647
|
-
/** Apply the 3×3 linear part of a mat4 (rotation/scale, no translation) */
|
|
553
|
+
/** Apply the 3×3 linear part of a mat4 (rotation/scale, no translation). */
|
|
648
554
|
function _applyDir(out, mat, dx, dy, dz) {
|
|
649
555
|
out[0]=mat[0]*dx+mat[4]*dy+mat[8]*dz;
|
|
650
556
|
out[1]=mat[1]*dx+mat[5]*dy+mat[9]*dz;
|
|
@@ -652,37 +558,24 @@ function _applyDir(out, mat, dx, dy, dz) {
|
|
|
652
558
|
return out;
|
|
653
559
|
}
|
|
654
560
|
|
|
655
|
-
/**
|
|
656
|
-
* World→Screen direction. Self-contained: reads proj scalars + view mat.
|
|
657
|
-
* The existing p5.tree code nested _direction and _location calls here;
|
|
658
|
-
* this version inlines all math with stack locals.
|
|
659
|
-
*/
|
|
660
561
|
function _worldToScreenDir(out, dx, dy, dz, proj, view, vpW, vpH, ndcZMin) {
|
|
661
|
-
// 1. World → Eye direction: R · d (standard column-major mat × vec)
|
|
662
562
|
const edx = view[0]*dx + view[4]*dy + view[8]*dz;
|
|
663
563
|
const edy = view[1]*dx + view[5]*dy + view[9]*dz;
|
|
664
564
|
const edz = view[2]*dx + view[6]*dy + view[10]*dz;
|
|
665
|
-
|
|
666
565
|
const isPersp = proj[15] === 0;
|
|
667
566
|
let sdx = edx, sdy = edy;
|
|
668
|
-
|
|
669
567
|
if (isPersp) {
|
|
670
|
-
|
|
671
|
-
// view * [0,0,0,1] = column 3 of view
|
|
672
|
-
const zEye = view[8]*0 + view[9]*0 + view[10]*0 + view[14]; // = view[14]
|
|
568
|
+
const zEye = view[14];
|
|
673
569
|
const halfTan = Math.tan(projFov(proj) / 2);
|
|
674
570
|
const k = Math.abs(zEye * halfTan);
|
|
675
571
|
const pixPerUnit = vpH / (2 * k);
|
|
676
572
|
sdx *= pixPerUnit;
|
|
677
573
|
sdy *= pixPerUnit;
|
|
678
574
|
} else {
|
|
679
|
-
// Ortho: pixels per world unit along X/Y
|
|
680
575
|
const orthoW = Math.abs(projRight(proj, ndcZMin) - projLeft(proj, ndcZMin));
|
|
681
576
|
sdx *= vpW / orthoW;
|
|
682
577
|
sdy *= vpH / Math.abs(projTop(proj, ndcZMin) - projBottom(proj, ndcZMin));
|
|
683
578
|
}
|
|
684
|
-
|
|
685
|
-
// Z: map eye-space dz to screen-space dz
|
|
686
579
|
const near = projNear(proj, ndcZMin), far = projFar(proj);
|
|
687
580
|
const depthRange = near - far;
|
|
688
581
|
let sdz;
|
|
@@ -691,7 +584,6 @@ function _worldToScreenDir(out, dx, dy, dz, proj, view, vpW, vpH, ndcZMin) {
|
|
|
691
584
|
} else {
|
|
692
585
|
sdz = edz / (depthRange / (Math.abs(projRight(proj, ndcZMin) - projLeft(proj, ndcZMin)) / vpW));
|
|
693
586
|
}
|
|
694
|
-
|
|
695
587
|
out[0] = sdx; out[1] = sdy; out[2] = sdz;
|
|
696
588
|
return out;
|
|
697
589
|
}
|
|
@@ -699,7 +591,6 @@ function _worldToScreenDir(out, dx, dy, dz, proj, view, vpW, vpH, ndcZMin) {
|
|
|
699
591
|
function _screenToWorldDir(out, dx, dy, dz, proj, view, eye, vpW, vpH, ndcZMin) {
|
|
700
592
|
const isPersp = proj[15] === 0;
|
|
701
593
|
let edx = dx, edy = dy;
|
|
702
|
-
|
|
703
594
|
if (isPersp) {
|
|
704
595
|
const zEye = view[14];
|
|
705
596
|
const halfTan = Math.tan(projFov(proj) / 2);
|
|
@@ -711,7 +602,6 @@ function _screenToWorldDir(out, dx, dy, dz, proj, view, eye, vpW, vpH, ndcZMin)
|
|
|
711
602
|
edx *= orthoW / vpW;
|
|
712
603
|
edy *= Math.abs(projTop(proj, ndcZMin) - projBottom(proj, ndcZMin)) / vpH;
|
|
713
604
|
}
|
|
714
|
-
|
|
715
605
|
const near = projNear(proj, ndcZMin), far = projFar(proj);
|
|
716
606
|
const depthRange = near - far;
|
|
717
607
|
let edz;
|
|
@@ -720,8 +610,6 @@ function _screenToWorldDir(out, dx, dy, dz, proj, view, eye, vpW, vpH, ndcZMin)
|
|
|
720
610
|
} else {
|
|
721
611
|
edz = dz * (depthRange / (Math.abs(projRight(proj, ndcZMin) - projLeft(proj, ndcZMin)) / vpW));
|
|
722
612
|
}
|
|
723
|
-
|
|
724
|
-
// Eye → World direction (dMatrix = upper-left 3×3 of eye = inv(view))
|
|
725
613
|
_applyDir(out, eye, edx, edy, edz);
|
|
726
614
|
return out;
|
|
727
615
|
}
|
|
@@ -743,21 +631,21 @@ function _ndcToScreenDir(out, dx, dy, dz, vpW, vpH, ndcZMin) {
|
|
|
743
631
|
}
|
|
744
632
|
|
|
745
633
|
/**
|
|
746
|
-
* Map a direction between coordinate spaces.
|
|
747
|
-
* Same
|
|
634
|
+
* Map a direction vector between coordinate spaces.
|
|
635
|
+
* Same bag contract as mapLocation.
|
|
748
636
|
*/
|
|
749
637
|
function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
750
638
|
const vpW = Math.abs(vp[2]), vpH = Math.abs(vp[3]);
|
|
751
639
|
|
|
752
|
-
// EYE ↔ WORLD (most common
|
|
753
|
-
if (from === EYE && to === WORLD) return _applyDir(out, m.
|
|
754
|
-
if (from === WORLD && to === EYE) return _applyDir(out, m.
|
|
640
|
+
// EYE ↔ WORLD (most common)
|
|
641
|
+
if (from === EYE && to === WORLD) return _applyDir(out, m.eMatrix, dx, dy, dz);
|
|
642
|
+
if (from === WORLD && to === EYE) return _applyDir(out, m.vMatrix, dx, dy, dz);
|
|
755
643
|
|
|
756
644
|
// WORLD ↔ SCREEN
|
|
757
645
|
if (from === WORLD && to === SCREEN)
|
|
758
|
-
return _worldToScreenDir(out, dx,dy,dz, m.
|
|
646
|
+
return _worldToScreenDir(out, dx,dy,dz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
759
647
|
if (from === SCREEN && to === WORLD)
|
|
760
|
-
return _screenToWorldDir(out, dx,dy,dz, m.
|
|
648
|
+
return _screenToWorldDir(out, dx,dy,dz, m.pMatrix, m.vMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
761
649
|
|
|
762
650
|
// SCREEN ↔ NDC
|
|
763
651
|
if (from === SCREEN && to === NDC)
|
|
@@ -765,45 +653,44 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
765
653
|
if (from === NDC && to === SCREEN)
|
|
766
654
|
return _ndcToScreenDir(out, dx,dy,dz, vpW, vpH, ndcZMin);
|
|
767
655
|
|
|
768
|
-
// WORLD ↔ NDC
|
|
656
|
+
// WORLD ↔ NDC
|
|
769
657
|
if (from === WORLD && to === NDC) {
|
|
770
|
-
_worldToScreenDir(out, dx,dy,dz, m.
|
|
658
|
+
_worldToScreenDir(out, dx,dy,dz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
771
659
|
const sx=out[0],sy=out[1],sz=out[2];
|
|
772
660
|
return _screenToNDCDir(out, sx,sy,sz, vpW, vpH, ndcZMin);
|
|
773
661
|
}
|
|
774
662
|
if (from === NDC && to === WORLD) {
|
|
775
663
|
_ndcToScreenDir(out, dx,dy,dz, vpW, vpH, ndcZMin);
|
|
776
664
|
const sx=out[0],sy=out[1],sz=out[2];
|
|
777
|
-
return _screenToWorldDir(out, sx,sy,sz, m.
|
|
665
|
+
return _screenToWorldDir(out, sx,sy,sz, m.pMatrix, m.vMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
778
666
|
}
|
|
779
667
|
|
|
780
668
|
// EYE ↔ SCREEN
|
|
781
669
|
if (from === EYE && to === SCREEN) {
|
|
782
|
-
|
|
783
|
-
_applyDir(out, m.eye, dx,dy,dz);
|
|
670
|
+
_applyDir(out, m.eMatrix, dx,dy,dz);
|
|
784
671
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
785
|
-
return _worldToScreenDir(out, wx,wy,wz, m.
|
|
672
|
+
return _worldToScreenDir(out, wx,wy,wz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
786
673
|
}
|
|
787
674
|
if (from === SCREEN && to === EYE) {
|
|
788
|
-
_screenToWorldDir(out, dx,dy,dz, m.
|
|
675
|
+
_screenToWorldDir(out, dx,dy,dz, m.pMatrix, m.vMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
789
676
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
790
|
-
return _applyDir(out, m.
|
|
677
|
+
return _applyDir(out, m.vMatrix, wx,wy,wz);
|
|
791
678
|
}
|
|
792
679
|
|
|
793
680
|
// EYE ↔ NDC
|
|
794
681
|
if (from === EYE && to === NDC) {
|
|
795
|
-
_applyDir(out, m.
|
|
682
|
+
_applyDir(out, m.eMatrix, dx,dy,dz);
|
|
796
683
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
797
|
-
_worldToScreenDir(out, wx,wy,wz, m.
|
|
684
|
+
_worldToScreenDir(out, wx,wy,wz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
798
685
|
const sx=out[0],sy=out[1],sz=out[2];
|
|
799
686
|
return _screenToNDCDir(out, sx,sy,sz, vpW, vpH, ndcZMin);
|
|
800
687
|
}
|
|
801
688
|
if (from === NDC && to === EYE) {
|
|
802
689
|
_ndcToScreenDir(out, dx,dy,dz, vpW, vpH, ndcZMin);
|
|
803
690
|
const sx=out[0],sy=out[1],sz=out[2];
|
|
804
|
-
_screenToWorldDir(out, sx,sy,sz, m.
|
|
691
|
+
_screenToWorldDir(out, sx,sy,sz, m.pMatrix, m.vMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
805
692
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
806
|
-
return _applyDir(out, m.
|
|
693
|
+
return _applyDir(out, m.vMatrix, wx,wy,wz);
|
|
807
694
|
}
|
|
808
695
|
|
|
809
696
|
// MATRIX ↔ WORLD
|
|
@@ -814,10 +701,10 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
814
701
|
if (from === MATRIX && to === EYE) {
|
|
815
702
|
_applyDir(out, m.fromFrame, dx,dy,dz);
|
|
816
703
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
817
|
-
return _applyDir(out, m.
|
|
704
|
+
return _applyDir(out, m.vMatrix, wx,wy,wz);
|
|
818
705
|
}
|
|
819
706
|
if (from === EYE && to === MATRIX) {
|
|
820
|
-
_applyDir(out, m.
|
|
707
|
+
_applyDir(out, m.eMatrix, dx,dy,dz);
|
|
821
708
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
822
709
|
return _applyDir(out, m.toFrameInv, wx,wy,wz);
|
|
823
710
|
}
|
|
@@ -826,10 +713,10 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
826
713
|
if (from === MATRIX && to === SCREEN) {
|
|
827
714
|
_applyDir(out, m.fromFrame, dx,dy,dz);
|
|
828
715
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
829
|
-
return _worldToScreenDir(out, wx,wy,wz, m.
|
|
716
|
+
return _worldToScreenDir(out, wx,wy,wz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
830
717
|
}
|
|
831
718
|
if (from === SCREEN && to === MATRIX) {
|
|
832
|
-
_screenToWorldDir(out, dx,dy,dz, m.
|
|
719
|
+
_screenToWorldDir(out, dx,dy,dz, m.pMatrix, m.vMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
833
720
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
834
721
|
return _applyDir(out, m.toFrameInv, wx,wy,wz);
|
|
835
722
|
}
|
|
@@ -838,14 +725,14 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
838
725
|
if (from === MATRIX && to === NDC) {
|
|
839
726
|
_applyDir(out, m.fromFrame, dx,dy,dz);
|
|
840
727
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
841
|
-
_worldToScreenDir(out, wx,wy,wz, m.
|
|
728
|
+
_worldToScreenDir(out, wx,wy,wz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
842
729
|
const sx=out[0],sy=out[1],sz=out[2];
|
|
843
730
|
return _screenToNDCDir(out, sx,sy,sz, vpW, vpH, ndcZMin);
|
|
844
731
|
}
|
|
845
732
|
if (from === NDC && to === MATRIX) {
|
|
846
733
|
_ndcToScreenDir(out, dx,dy,dz, vpW, vpH, ndcZMin);
|
|
847
734
|
const sx=out[0],sy=out[1],sz=out[2];
|
|
848
|
-
_screenToWorldDir(out, sx,sy,sz, m.
|
|
735
|
+
_screenToWorldDir(out, sx,sy,sz, m.pMatrix, m.vMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
849
736
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
850
737
|
return _applyDir(out, m.toFrameInv, wx,wy,wz);
|
|
851
738
|
}
|
|
@@ -868,9 +755,9 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
868
755
|
|
|
869
756
|
/**
|
|
870
757
|
* World-units-per-pixel at a given eye-space Z depth.
|
|
871
|
-
* @param {ArrayLike<number>} proj Projection
|
|
758
|
+
* @param {ArrayLike<number>} proj Projection mat4.
|
|
872
759
|
* @param {number} vpH Viewport height (pixels).
|
|
873
|
-
* @param {number} eyeZ Eye-space Z
|
|
760
|
+
* @param {number} eyeZ Eye-space Z (negative for in-front-of camera).
|
|
874
761
|
* @param {number} ndcZMin WEBGL or WEBGPU.
|
|
875
762
|
*/
|
|
876
763
|
function pixelRatio(proj, vpH, eyeZ, ndcZMin) {
|
|
@@ -1211,46 +1098,165 @@ const _clampS = (x, lo, hi) => x < lo ? lo : (x > hi ? hi : x);
|
|
|
1211
1098
|
|
|
1212
1099
|
function _parseVec3(v) {
|
|
1213
1100
|
if (!v) return null;
|
|
1214
|
-
if (
|
|
1215
|
-
if (
|
|
1101
|
+
if (ArrayBuffer.isView(v) && v.length >= 3) return [v[0], v[1], v[2]];
|
|
1102
|
+
if (Array.isArray(v) && v.length >= 3 && v.every(n => typeof n === 'number')) return [v[0], v[1], v[2]];
|
|
1103
|
+
if (typeof v === 'object' && 'x' in v) return [v.x || 0, v.y || 0, v.z || 0];
|
|
1216
1104
|
return null;
|
|
1217
1105
|
}
|
|
1218
1106
|
|
|
1107
|
+
// Euler: unit axis vectors and the six valid intrinsic orderings.
|
|
1108
|
+
const _EULER_AXES = { X:[1,0,0], Y:[0,1,0], Z:[0,0,1] };
|
|
1109
|
+
const _EULER_ORDERS = new Set(['XYZ','XZY','YXZ','YZX','ZXY','ZYX']);
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Parse any rotation representation into a unit quaternion [x,y,z,w].
|
|
1113
|
+
*
|
|
1114
|
+
* Accepted forms:
|
|
1115
|
+
*
|
|
1116
|
+
* [x,y,z,w]
|
|
1117
|
+
* Raw quaternion array.
|
|
1118
|
+
*
|
|
1119
|
+
* { axis:[x,y,z], angle }
|
|
1120
|
+
* Axis-angle. Axis need not be unit.
|
|
1121
|
+
*
|
|
1122
|
+
* { dir:[x,y,z], up?:[x,y,z] }
|
|
1123
|
+
* Object orientation — forward direction (−Z) with optional up hint.
|
|
1124
|
+
*
|
|
1125
|
+
* { eMatrix: mat4 }
|
|
1126
|
+
* Extract rotation block from an eye (eye→world) matrix.
|
|
1127
|
+
* Column-major Float32Array(16), plain Array, or { mat4 } wrapper.
|
|
1128
|
+
*
|
|
1129
|
+
* { mat3: mat3 }
|
|
1130
|
+
* Column-major 3×3 rotation matrix — Float32Array(9) or plain Array.
|
|
1131
|
+
*
|
|
1132
|
+
* { euler:[rx,ry,rz], order?:'YXZ' }
|
|
1133
|
+
* Intrinsic Euler angles (radians). Angles are indexed by order position:
|
|
1134
|
+
* e[0] rotates around order[0] axis, e[1] around order[1], e[2] around order[2].
|
|
1135
|
+
* Supported orders: YXZ (default), XYZ, ZYX, ZXY, XZY, YZX.
|
|
1136
|
+
* Note: intrinsic ABC = extrinsic CBA with the same angles — to use
|
|
1137
|
+
* extrinsic order ABC, reverse the string and use intrinsic CBA.
|
|
1138
|
+
*
|
|
1139
|
+
* { from:[x,y,z], to:[x,y,z] }
|
|
1140
|
+
* Shortest-arc rotation from one direction onto another.
|
|
1141
|
+
* Both vectors are normalised internally.
|
|
1142
|
+
* Antiparallel input: 180° rotation around a perpendicular axis.
|
|
1143
|
+
*
|
|
1144
|
+
* @param {*} v
|
|
1145
|
+
* @returns {number[]|null} [x,y,z,w] or null if unparseable.
|
|
1146
|
+
*/
|
|
1219
1147
|
function _parseQuat(v) {
|
|
1220
1148
|
if (!v) return null;
|
|
1221
|
-
|
|
1149
|
+
|
|
1150
|
+
// raw [x,y,z,w] — plain array or typed array
|
|
1151
|
+
if ((Array.isArray(v) || ArrayBuffer.isView(v)) && v.length === 4) return [v[0], v[1], v[2], v[3]];
|
|
1152
|
+
|
|
1153
|
+
// { axis, angle }
|
|
1222
1154
|
if (v.axis && typeof v.angle === 'number') {
|
|
1223
1155
|
const a = Array.isArray(v.axis) ? v.axis : [v.axis.x||0, v.axis.y||0, v.axis.z||0];
|
|
1224
1156
|
return qFromAxisAngle([0,0,0,1], a[0],a[1],a[2], v.angle);
|
|
1225
1157
|
}
|
|
1158
|
+
|
|
1159
|
+
// { dir, up? }
|
|
1226
1160
|
if (v.dir) {
|
|
1227
1161
|
const d = Array.isArray(v.dir) ? v.dir : [v.dir.x||0, v.dir.y||0, v.dir.z||0];
|
|
1228
1162
|
const u = v.up ? (Array.isArray(v.up) ? v.up : [v.up.x||0, v.up.y||0, v.up.z||0]) : null;
|
|
1229
1163
|
return qFromLookDir([0,0,0,1], d, u);
|
|
1230
1164
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1165
|
+
|
|
1166
|
+
// { eMatrix } — rotation block from eye (eye→world) matrix, col-major mat4
|
|
1167
|
+
if (v.eMatrix != null) {
|
|
1168
|
+
const m = (ArrayBuffer.isView(v.eMatrix) || Array.isArray(v.eMatrix))
|
|
1169
|
+
? v.eMatrix : (v.eMatrix.mat4 ?? null);
|
|
1170
|
+
if (m && m.length >= 16) return qFromMat4([0,0,0,1], m);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// { mat3 } — column-major 3×3 rotation matrix
|
|
1174
|
+
// col0=[m0,m1,m2], col1=[m3,m4,m5], col2=[m6,m7,m8]
|
|
1175
|
+
// row-major for qFromRotMat3x3: row0=[m0,m3,m6], row1=[m1,m4,m7], row2=[m2,m5,m8]
|
|
1176
|
+
if (v.mat3 != null) {
|
|
1177
|
+
const m = v.mat3;
|
|
1178
|
+
if ((ArrayBuffer.isView(m) || Array.isArray(m)) && m.length >= 9)
|
|
1179
|
+
return qFromRotMat3x3([0,0,0,1], m[0],m[3],m[6], m[1],m[4],m[7], m[2],m[5],m[8]);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// { euler, order? } — intrinsic Euler angles (radians), default order YXZ
|
|
1183
|
+
if (v.euler != null) {
|
|
1184
|
+
const e = v.euler;
|
|
1185
|
+
if (!Array.isArray(e) || e.length < 3) return null;
|
|
1186
|
+
const order = (typeof v.order === 'string' && _EULER_ORDERS.has(v.order))
|
|
1187
|
+
? v.order : 'YXZ';
|
|
1188
|
+
const q = [0,0,0,1];
|
|
1189
|
+
const s = [0,0,0,1]; // scratch — reused each step
|
|
1190
|
+
for (let i = 0; i < 3; i++) {
|
|
1191
|
+
const ax = _EULER_AXES[order[i]];
|
|
1192
|
+
qMul(q, q, qFromAxisAngle(s, ax[0],ax[1],ax[2], e[i]));
|
|
1193
|
+
}
|
|
1194
|
+
return q;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// { from, to } — shortest-arc rotation from one direction onto another
|
|
1198
|
+
if (v.from != null && v.to != null) {
|
|
1199
|
+
const f = Array.isArray(v.from) ? v.from : [v.from.x||0, v.from.y||0, v.from.z||0];
|
|
1200
|
+
const t = Array.isArray(v.to) ? v.to : [v.to.x||0, v.to.y||0, v.to.z||0];
|
|
1201
|
+
const fl = Math.sqrt(f[0]*f[0]+f[1]*f[1]+f[2]*f[2]) || 1;
|
|
1202
|
+
const tl = Math.sqrt(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]) || 1;
|
|
1203
|
+
const fx=f[0]/fl, fy=f[1]/fl, fz=f[2]/fl;
|
|
1204
|
+
const tx=t[0]/tl, ty=t[1]/tl, tz=t[2]/tl;
|
|
1205
|
+
const dot = fx*tx + fy*ty + fz*tz;
|
|
1206
|
+
// parallel — identity
|
|
1207
|
+
if (dot >= 1 - 1e-8) return [0,0,0,1];
|
|
1208
|
+
// antiparallel — 180° around any perpendicular axis
|
|
1209
|
+
if (dot <= -1 + 1e-8) {
|
|
1210
|
+
// cross(from, X=[1,0,0]) = [0, fz, -fy]
|
|
1211
|
+
let px=0, py=fz, pz=-fy;
|
|
1212
|
+
let pl = Math.sqrt(px*px+py*py+pz*pz);
|
|
1213
|
+
if (pl < 1e-8) {
|
|
1214
|
+
// from ≈ ±X; try cross(from, Z=[0,0,1]) = [fy, -fx, 0]
|
|
1215
|
+
px=fy; py=-fx; pz=0;
|
|
1216
|
+
pl = Math.sqrt(px*px+py*py+pz*pz);
|
|
1217
|
+
}
|
|
1218
|
+
if (pl < 1e-8) return [0,0,0,1];
|
|
1219
|
+
return qFromAxisAngle([0,0,0,1], px/pl,py/pl,pz/pl, Math.PI);
|
|
1247
1220
|
}
|
|
1221
|
+
// general case — axis = normalize(cross(from, to))
|
|
1222
|
+
let ax=fy*tz-fz*ty, ay=fz*tx-fx*tz, az=fx*ty-fy*tx;
|
|
1223
|
+
const al = Math.sqrt(ax*ax+ay*ay+az*az) || 1;
|
|
1224
|
+
return qFromAxisAngle([0,0,0,1], ax/al,ay/al,az/al,
|
|
1225
|
+
Math.acos(Math.max(-1, Math.min(1, dot))));
|
|
1248
1226
|
}
|
|
1227
|
+
|
|
1249
1228
|
return null;
|
|
1250
1229
|
}
|
|
1251
1230
|
|
|
1231
|
+
/**
|
|
1232
|
+
* Parse a PoseTrack keyframe spec.
|
|
1233
|
+
*
|
|
1234
|
+
* Accepted forms:
|
|
1235
|
+
*
|
|
1236
|
+
* { mMatrix }
|
|
1237
|
+
* Decompose a column-major mat4 into TRS via mat4ToTransform.
|
|
1238
|
+
* Float32Array(16), plain Array, or { mat4 } wrapper.
|
|
1239
|
+
* pos from col3, scl from column lengths, rot from normalised rotation block.
|
|
1240
|
+
*
|
|
1241
|
+
* { pos, rot, scl }
|
|
1242
|
+
* Explicit TRS. pos and scl are vec3, rot accepts any form from _parseQuat.
|
|
1243
|
+
* All fields are optional — missing pos/scl default to [0,0,0] / [1,1,1],
|
|
1244
|
+
* missing rot defaults to identity.
|
|
1245
|
+
*
|
|
1246
|
+
* @param {Object} spec
|
|
1247
|
+
* @returns {{ pos:number[], rot:number[], scl:number[] }|null}
|
|
1248
|
+
*/
|
|
1252
1249
|
function _parseSpec(spec) {
|
|
1253
1250
|
if (!spec || typeof spec !== 'object') return null;
|
|
1251
|
+
|
|
1252
|
+
// { mMatrix } — full TRS decomposition from model matrix
|
|
1253
|
+
if (spec.mMatrix != null) {
|
|
1254
|
+
const m = (ArrayBuffer.isView(spec.mMatrix) || Array.isArray(spec.mMatrix))
|
|
1255
|
+
? spec.mMatrix : (spec.mMatrix.mat4 ?? null);
|
|
1256
|
+
if (!m || m.length < 16) return null;
|
|
1257
|
+
return mat4ToTransform({ pos:[0,0,0], rot:[0,0,0,1], scl:[1,1,1] }, m);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1254
1260
|
const pos = _parseVec3(spec.pos) || [0,0,0];
|
|
1255
1261
|
const rot = _parseQuat(spec.rot) || [0,0,0,1];
|
|
1256
1262
|
const scl = _parseVec3(spec.scl) || [1,1,1];
|
|
@@ -1272,17 +1278,22 @@ function _sameTransform(a, b) {
|
|
|
1272
1278
|
*
|
|
1273
1279
|
* Accepted forms:
|
|
1274
1280
|
*
|
|
1275
|
-
* { eye, center
|
|
1276
|
-
* Explicit lookat.
|
|
1281
|
+
* { eye, center?, up? }
|
|
1282
|
+
* Explicit lookat. center defaults to [0,0,0], up defaults to [0,1,0].
|
|
1283
|
+
* Both are normalised/stored as-is. eye must be a vec3.
|
|
1284
|
+
*
|
|
1285
|
+
* { vMatrix: mat4 }
|
|
1286
|
+
* Column-major view matrix (world→eye).
|
|
1287
|
+
* eye reconstructed via -R^T·t; center = eye + forward·1; up = [0,1,0].
|
|
1288
|
+
* The matrix's up_ortho (col1) is intentionally NOT used as up —
|
|
1289
|
+
* passing it to cam.camera() shifts orbitControl's orbit reference.
|
|
1290
|
+
* Float32Array(16), plain Array, or { mat4 } wrapper.
|
|
1277
1291
|
*
|
|
1278
|
-
* {
|
|
1279
|
-
* Column-major
|
|
1280
|
-
* eye
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1283
|
-
* NOT extracted; using up_ortho as the hint causes orbitControl drift.
|
|
1284
|
-
* If you need to preserve roll from a rolled camera, pass the live
|
|
1285
|
-
* camera's up hint via capturePose() instead of using { view }.
|
|
1292
|
+
* { eMatrix: mat4 }
|
|
1293
|
+
* Column-major eye matrix (eye→world, i.e. inverse view).
|
|
1294
|
+
* eye read directly from col3; center = eye + forward·1; up = [0,1,0].
|
|
1295
|
+
* Simpler extraction than vMatrix; prefer this form when eMatrix is available.
|
|
1296
|
+
* Float32Array(16), plain Array, or { mat4 } wrapper.
|
|
1286
1297
|
*
|
|
1287
1298
|
* @param {Object} spec
|
|
1288
1299
|
* @returns {{ eye:number[], center:number[], up:number[] }|null}
|
|
@@ -1290,34 +1301,38 @@ function _sameTransform(a, b) {
|
|
|
1290
1301
|
function _parseCameraSpec(spec) {
|
|
1291
1302
|
if (!spec || typeof spec !== 'object') return null;
|
|
1292
1303
|
|
|
1293
|
-
// {
|
|
1294
|
-
if (spec.
|
|
1295
|
-
const m = (ArrayBuffer.isView(spec.
|
|
1296
|
-
? spec.
|
|
1297
|
-
: (spec.view.mat4 ?? null);
|
|
1304
|
+
// { vMatrix } — view matrix (world→eye); reconstruct eye via -R^T·t
|
|
1305
|
+
if (spec.vMatrix != null) {
|
|
1306
|
+
const m = (ArrayBuffer.isView(spec.vMatrix) || Array.isArray(spec.vMatrix))
|
|
1307
|
+
? spec.vMatrix : (spec.vMatrix.mat4 ?? null);
|
|
1298
1308
|
if (!m || m.length < 16) return null;
|
|
1299
|
-
// Extract eye position: eye = -R^T * t
|
|
1300
|
-
// Column-major view mat: col0=right, col1=up_ortho, col2=-fwd, col3=translation
|
|
1301
1309
|
const ex = -(m[0]*m[12] + m[4]*m[13] + m[8]*m[14]);
|
|
1302
1310
|
const ey = -(m[1]*m[12] + m[5]*m[13] + m[9]*m[14]);
|
|
1303
1311
|
const ez = -(m[2]*m[12] + m[6]*m[13] + m[10]*m[14]);
|
|
1304
|
-
// forward = -col2 = [-m[8], -m[9], -m[10]]
|
|
1305
1312
|
const fx=-m[8], fy=-m[9], fz=-m[10];
|
|
1306
1313
|
const fl=Math.sqrt(fx*fx+fy*fy+fz*fz)||1;
|
|
1307
|
-
|
|
1308
|
-
const cx=ex+fx/fl, cy=ey+fy/fl, cz=ez+fz/fl;
|
|
1309
|
-
return { eye:[ex,ey,ez], center:[cx,cy,cz], up:[0,1,0] };
|
|
1314
|
+
return { eye:[ex,ey,ez], center:[ex+fx/fl,ey+fy/fl,ez+fz/fl], up:[0,1,0] };
|
|
1310
1315
|
}
|
|
1311
1316
|
|
|
1312
|
-
// { eye
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1317
|
+
// { eMatrix } — eye matrix (eye→world); eye = col3, forward = -col2
|
|
1318
|
+
if (spec.eMatrix != null) {
|
|
1319
|
+
const m = (ArrayBuffer.isView(spec.eMatrix) || Array.isArray(spec.eMatrix))
|
|
1320
|
+
? spec.eMatrix : (spec.eMatrix.mat4 ?? null);
|
|
1321
|
+
if (!m || m.length < 16) return null;
|
|
1322
|
+
const ex=m[12], ey=m[13], ez=m[14];
|
|
1323
|
+
const fx=-m[8], fy=-m[9], fz=-m[10];
|
|
1324
|
+
const fl=Math.sqrt(fx*fx+fy*fy+fz*fz)||1;
|
|
1325
|
+
return { eye:[ex,ey,ez], center:[ex+fx/fl,ey+fy/fl,ez+fz/fl], up:[0,1,0] };
|
|
1326
|
+
}
|
|
1316
1327
|
|
|
1328
|
+
// { eye, center?, up? } — explicit lookat (eye is a vec3, not a mat4)
|
|
1329
|
+
const eye = _parseVec3(spec.eye);
|
|
1330
|
+
if (!eye) return null;
|
|
1331
|
+
const center = _parseVec3(spec.center) || [0,0,0];
|
|
1317
1332
|
const upRaw = spec.up ? _parseVec3(spec.up) : null;
|
|
1318
1333
|
const up = upRaw || [0,1,0];
|
|
1319
1334
|
const ul = Math.sqrt(up[0]*up[0]+up[1]*up[1]+up[2]*up[2]) || 1;
|
|
1320
|
-
return { eye, center, up:
|
|
1335
|
+
return { eye, center, up:[up[0]/ul, up[1]/ul, up[2]/ul] };
|
|
1321
1336
|
}
|
|
1322
1337
|
|
|
1323
1338
|
function _sameCameraKeyframe(a, b) {
|
|
@@ -1584,13 +1599,19 @@ class Track {
|
|
|
1584
1599
|
* Keyframe shape: { pos:[x,y,z], rot:[x,y,z,w], scl:[x,y,z] }
|
|
1585
1600
|
*
|
|
1586
1601
|
* add() accepts individual specs or a bulk array of specs:
|
|
1587
|
-
*
|
|
1588
|
-
* {
|
|
1589
|
-
* { pos
|
|
1590
|
-
* { pos
|
|
1591
|
-
* { pos
|
|
1592
|
-
* { pos
|
|
1593
|
-
*
|
|
1602
|
+
*
|
|
1603
|
+
* { mMatrix } — full TRS from model matrix
|
|
1604
|
+
* { pos?, rot?, scl? } — direct TRS; all fields optional
|
|
1605
|
+
* { pos?, rot: [x,y,z,w] } — explicit quaternion
|
|
1606
|
+
* { pos?, rot: { axis, angle } } — axis-angle
|
|
1607
|
+
* { pos?, rot: { dir, up? } } — look direction
|
|
1608
|
+
* { pos?, rot: { eMatrix: mat4 } } — rotation from eye matrix
|
|
1609
|
+
* { pos?, rot: { mat3 } } — column-major 3×3 rotation matrix
|
|
1610
|
+
* { pos?, rot: { euler, order? } } — intrinsic Euler angles (default YXZ)
|
|
1611
|
+
* { pos?, rot: { from, to } } — shortest-arc between two directions
|
|
1612
|
+
* [ spec, spec, ... ] — bulk
|
|
1613
|
+
*
|
|
1614
|
+
* Missing fields default to: pos → [0,0,0], rot → [0,0,0,1], scl → [1,1,1].
|
|
1594
1615
|
*
|
|
1595
1616
|
* eval() writes { pos, rot, scl }:
|
|
1596
1617
|
* pos — Catmull-Rom (posInterp='catmullrom') or lerp
|
|
@@ -1599,8 +1620,10 @@ class Track {
|
|
|
1599
1620
|
*
|
|
1600
1621
|
* @example
|
|
1601
1622
|
* const track = new PoseTrack()
|
|
1602
|
-
* track.add({ pos:[0,0,0]
|
|
1603
|
-
* track.add({ pos:[100,0,0], rot:[0,
|
|
1623
|
+
* track.add({ pos:[0,0,0] }) // identity rot, uniform scl
|
|
1624
|
+
* track.add({ pos:[100,0,0], rot: { euler:[0, Math.PI/2, 0] } })
|
|
1625
|
+
* track.add({ rot: { axis:[0,1,0], angle: Math.PI } }) // pos defaults to [0,0,0]
|
|
1626
|
+
* track.add({ mMatrix: someModelMatrix })
|
|
1604
1627
|
* track.play({ loop: true })
|
|
1605
1628
|
* // per frame:
|
|
1606
1629
|
* track.tick()
|
|
@@ -1733,18 +1756,21 @@ class PoseTrack extends Track {
|
|
|
1733
1756
|
* own paths, up nlerped on the unit sphere. This correctly handles cameras
|
|
1734
1757
|
* that always look at a fixed target (center stays at origin throughout)
|
|
1735
1758
|
* as well as free-fly paths where center moves independently.
|
|
1759
|
+
*
|
|
1760
|
+
* Missing fields default to: center → [0,0,0], up → [0,1,0].
|
|
1736
1761
|
*
|
|
1737
1762
|
* add() accepts individual specs or a bulk array of specs:
|
|
1738
|
-
* { eye, center, up? } explicit lookat; up defaults to [0,1,0]
|
|
1739
|
-
* { view: mat4 } view matrix; eye extracted, center = eye+fwd*1,
|
|
1740
|
-
* up = [0,1,0] (safe default — see note below)
|
|
1741
|
-
* [ spec, spec, ... ] bulk
|
|
1742
1763
|
*
|
|
1743
|
-
*
|
|
1744
|
-
*
|
|
1745
|
-
*
|
|
1746
|
-
*
|
|
1747
|
-
*
|
|
1764
|
+
* { eye, center?, up? } explicit lookat; center defaults to [0,0,0], up to [0,1,0]
|
|
1765
|
+
* { vMatrix: mat4 } view matrix (world→eye); eye reconstructed via -R^T·t
|
|
1766
|
+
* { eMatrix: mat4 } eye matrix (eye→world); eye read from col3 directly
|
|
1767
|
+
* [ spec, spec, ... ] bulk
|
|
1768
|
+
*
|
|
1769
|
+
* Note on up for matrix forms:
|
|
1770
|
+
* up is always [0,1,0]. The matrix's col1 (up_ortho) is intentionally
|
|
1771
|
+
* not used — it differs from the hint [0,1,0] for upright cameras and
|
|
1772
|
+
* passing it to cam.camera() shifts orbitControl's orbit reference.
|
|
1773
|
+
* Use capturePose() (p5.tree bridge) when the real up hint is needed.
|
|
1748
1774
|
*
|
|
1749
1775
|
* eval() writes { eye, center, up }:
|
|
1750
1776
|
* eye — Catmull-Rom (eyeInterp='catmullrom') or lerp
|
|
@@ -1753,8 +1779,10 @@ class PoseTrack extends Track {
|
|
|
1753
1779
|
*
|
|
1754
1780
|
* @example
|
|
1755
1781
|
* const track = new CameraTrack()
|
|
1756
|
-
* track.add({ eye:[0,0,500]
|
|
1782
|
+
* track.add({ eye:[0,0,500] }) // center defaults to [0,0,0]
|
|
1757
1783
|
* track.add({ eye:[300,-150,0], center:[0,0,0] })
|
|
1784
|
+
* track.add({ eMatrix: myEyeMatrix })
|
|
1785
|
+
* track.add({ vMatrix: myViewMatrix })
|
|
1758
1786
|
* track.play({ loop: true })
|
|
1759
1787
|
* // per frame:
|
|
1760
1788
|
* track.tick()
|
|
@@ -1789,7 +1817,7 @@ class CameraTrack extends Track {
|
|
|
1789
1817
|
* Append one or more camera keyframes. Adjacent duplicates are skipped by default.
|
|
1790
1818
|
*
|
|
1791
1819
|
* @param {Object|Object[]} spec
|
|
1792
|
-
* { eye, center
|
|
1820
|
+
* { eye, center?, up? } or { vMatrix: mat4 } or { eMatrix: mat4 } or an array of either.
|
|
1793
1821
|
* @param {{ deduplicate?: boolean }} [opts]
|
|
1794
1822
|
*/
|
|
1795
1823
|
add(spec, opts) {
|
|
@@ -2033,5 +2061,5 @@ function boxVisibility(planes, x0, y0, z0, x1, y1, z1) {
|
|
|
2033
2061
|
return allIn ? VISIBLE : SEMIVISIBLE;
|
|
2034
2062
|
}
|
|
2035
2063
|
|
|
2036
|
-
export { CameraTrack, EYE, INVISIBLE, MATRIX, MODEL, NDC, ORIGIN, PLANE_BOTTOM, PLANE_FAR, PLANE_LEFT, PLANE_NEAR, PLANE_RIGHT, PLANE_TOP, PoseTrack, SCREEN, SEMIVISIBLE, VISIBLE, WEBGL, WEBGPU, WORLD, _i, _j, _k, boxVisibility, catmullRomVec3, distanceToPlane, frustumPlanes, i, j, k, lerpVec3, mapDirection, mapLocation, mat3Direction,
|
|
2064
|
+
export { CameraTrack, EYE, INVISIBLE, MATRIX, MODEL, NDC, ORIGIN, PLANE_BOTTOM, PLANE_FAR, PLANE_LEFT, PLANE_NEAR, PLANE_RIGHT, PLANE_TOP, PoseTrack, SCREEN, SEMIVISIBLE, VISIBLE, WEBGL, WEBGPU, WORLD, _i, _j, _k, boxVisibility, catmullRomVec3, distanceToPlane, frustumPlanes, i, j, k, lerpVec3, mapDirection, mapLocation, mat3Direction, mat3NormalFromMat4, mat4Invert, mat4Location, mat4MV, mat4Mul, mat4MulPoint, mat4PV, mat4ToTransform, mat4Transpose, pixelRatio, pointVisibility, projBottom, projFar, projFov, projHfov, projIsOrtho, projLeft, projNear, projRight, projTop, qCopy, qDot, qFromAxisAngle, qFromLookDir, qFromMat4, qFromRotMat3x3, qMul, qNegate, qNlerp, qNormalize, qSet, qSlerp, qToMat4, quatToAxisAngle, sphereVisibility, transformToMat4 };
|
|
2037
2065
|
//# sourceMappingURL=index.js.map
|