@nakednous/tree 0.0.11 → 0.0.13
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 +44 -20
- package/dist/index.js +417 -589
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -238,23 +238,27 @@ const quatToAxisAngle = (q, out) => {
|
|
|
238
238
|
* from specs requires only scalar arithmetic and quaternion conversions.
|
|
239
239
|
* Callers compose the resulting matrices using query.js (mat4Mul etc.).
|
|
240
240
|
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
241
|
+
* ── NDC Z convention ──────────────────────────────────────────────────────
|
|
242
|
+
* Controlled by `ndcZMin` in every projection constructor:
|
|
243
|
+
* WEBGL = −1 near → NDC z = −1, far → NDC z = +1
|
|
244
|
+
* WEBGPU = 0 near → NDC z = 0, far → NDC z = +1
|
|
245
|
+
*
|
|
246
|
+
* ── NDC Y convention ──────────────────────────────────────────────────────
|
|
247
|
+
* Controlled by `ndcYSign` in every projection constructor (default +1):
|
|
248
|
+
* +1 NDC y-up — standard: OpenGL / WebGL / WebGPU browser / Three.js / p5v2
|
|
249
|
+
* −1 NDC y-down — native Vulkan clip space
|
|
250
|
+
*
|
|
251
|
+
* Negating ndcYSign flips row 1 of the projection matrix (elements
|
|
252
|
+
* out[1], out[5], out[9], out[13]), reversing the y-axis in clip space.
|
|
253
|
+
* mat4View, mat4Eye, and all non-projection constructors are convention-
|
|
254
|
+
* agnostic — they produce the same matrix regardless of the NDC y direction.
|
|
255
|
+
*
|
|
256
|
+
* ── Screen Y convention ───────────────────────────────────────────────────
|
|
257
|
+
* Screen-y direction (DOM y-down vs OpenGL y-up) is a separate concern from
|
|
258
|
+
* NDC-y direction and is handled in query.js via the signed viewport height.
|
|
259
|
+
* See the query.js module header for details.
|
|
255
260
|
*
|
|
256
261
|
* All functions follow the out-first, zero-allocation contract.
|
|
257
|
-
* Returns null on degeneracy where applicable.
|
|
258
262
|
*/
|
|
259
263
|
|
|
260
264
|
|
|
@@ -264,8 +268,6 @@ const quatToAxisAngle = (q, out) => {
|
|
|
264
268
|
|
|
265
269
|
/**
|
|
266
270
|
* Rigid frame from orthonormal basis + translation.
|
|
267
|
-
* The primitive that lookat constructors use internally.
|
|
268
|
-
*
|
|
269
271
|
* Column-major layout: col0=right, col1=up, col2=forward, col3=translation.
|
|
270
272
|
*
|
|
271
273
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
@@ -273,7 +275,6 @@ const quatToAxisAngle = (q, out) => {
|
|
|
273
275
|
* @param {number} ux,uy,uz Up vector (col 1).
|
|
274
276
|
* @param {number} fx,fy,fz Forward vec (col 2).
|
|
275
277
|
* @param {number} tx,ty,tz Translation (col 3).
|
|
276
|
-
* @returns {Float32Array|number[]} out
|
|
277
278
|
*/
|
|
278
279
|
function mat4FromBasis(out, rx,ry,rz, ux,uy,uz, fx,fy,fz, tx,ty,tz) {
|
|
279
280
|
out[0]=rx; out[1]=ry; out[2]=rz; out[3]=0;
|
|
@@ -285,28 +286,22 @@ function mat4FromBasis(out, rx,ry,rz, ux,uy,uz, fx,fy,fz, tx,ty,tz) {
|
|
|
285
286
|
|
|
286
287
|
/**
|
|
287
288
|
* View matrix (world→eye) from lookat parameters.
|
|
289
|
+
* Camera looks along −Z in eye space; right = normalize(up × (−Z)).
|
|
288
290
|
* Cheaper than building the eye matrix and inverting.
|
|
289
291
|
*
|
|
290
|
-
* Convention: −Z axis points toward center (camera looks along −Z in eye space).
|
|
291
|
-
*
|
|
292
292
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
293
293
|
* @param {number} ex,ey,ez Eye (camera) position.
|
|
294
|
-
* @param {number} cx,cy,cz
|
|
294
|
+
* @param {number} cx,cy,cz Look-at target.
|
|
295
295
|
* @param {number} ux,uy,uz World up hint (need not be unit).
|
|
296
|
-
* @returns {Float32Array|number[]} out
|
|
297
296
|
*/
|
|
298
|
-
function
|
|
299
|
-
// z = normalize(eye - center) (camera +Z away from target)
|
|
297
|
+
function mat4View(out, ex,ey,ez, cx,cy,cz, ux,uy,uz) {
|
|
300
298
|
let zx=ex-cx, zy=ey-cy, zz=ez-cz;
|
|
301
299
|
const zl=Math.sqrt(zx*zx+zy*zy+zz*zz)||1;
|
|
302
300
|
zx/=zl; zy/=zl; zz/=zl;
|
|
303
|
-
// x = normalize(up × z) (right)
|
|
304
301
|
let xx=uy*zz-uz*zy, xy=uz*zx-ux*zz, xz=ux*zy-uy*zx;
|
|
305
302
|
const xl=Math.sqrt(xx*xx+xy*xy+xz*xz)||1;
|
|
306
303
|
xx/=xl; xy/=xl; xz/=xl;
|
|
307
|
-
// y = z × x (up_ortho, guaranteed perpendicular)
|
|
308
304
|
const yx=zy*xz-zz*xy, yy=zz*xx-zx*xz, yz=zx*xy-zy*xx;
|
|
309
|
-
// View = [R | -R·t] (column-major)
|
|
310
305
|
out[0]=xx; out[1]=yx; out[2]=zx; out[3]=0;
|
|
311
306
|
out[4]=xy; out[5]=yy; out[6]=zy; out[7]=0;
|
|
312
307
|
out[8]=xz; out[9]=yz; out[10]=zz; out[11]=0;
|
|
@@ -320,16 +315,14 @@ function mat4LookAt(out, ex,ey,ez, cx,cy,cz, ux,uy,uz) {
|
|
|
320
315
|
/**
|
|
321
316
|
* Eye matrix (eye→world) from lookat parameters.
|
|
322
317
|
* Transpose of the rotation block + direct translation column.
|
|
323
|
-
* Same
|
|
318
|
+
* Same parameters as mat4View.
|
|
324
319
|
*
|
|
325
320
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
326
|
-
* @param {number} ex,ey,ez Eye
|
|
327
|
-
* @param {number} cx,cy,cz
|
|
328
|
-
* @param {number} ux,uy,uz World up hint
|
|
329
|
-
* @returns {Float32Array|number[]} out
|
|
321
|
+
* @param {number} ex,ey,ez Eye position.
|
|
322
|
+
* @param {number} cx,cy,cz Look-at target.
|
|
323
|
+
* @param {number} ux,uy,uz World up hint.
|
|
330
324
|
*/
|
|
331
|
-
function
|
|
332
|
-
// Same basis computation as mat4LookAt.
|
|
325
|
+
function mat4Eye(out, ex,ey,ez, cx,cy,cz, ux,uy,uz) {
|
|
333
326
|
let zx=ex-cx, zy=ey-cy, zz=ez-cz;
|
|
334
327
|
const zl=Math.sqrt(zx*zx+zy*zy+zz*zz)||1;
|
|
335
328
|
zx/=zl; zy/=zl; zz/=zl;
|
|
@@ -337,7 +330,6 @@ function mat4EyeMatrix(out, ex,ey,ez, cx,cy,cz, ux,uy,uz) {
|
|
|
337
330
|
const xl=Math.sqrt(xx*xx+xy*xy+xz*xz)||1;
|
|
338
331
|
xx/=xl; xy/=xl; xz/=xl;
|
|
339
332
|
const yx=zy*xz-zz*xy, yy=zz*xx-zx*xz, yz=zx*xy-zy*xx;
|
|
340
|
-
// Eye matrix = [R^T | t] (rotation transposed, translation = eye position)
|
|
341
333
|
out[0]=xx; out[1]=xy; out[2]=xz; out[3]=0;
|
|
342
334
|
out[4]=yx; out[5]=yy; out[6]=yz; out[7]=0;
|
|
343
335
|
out[8]=zx; out[9]=zy; out[10]=zz; out[11]=0;
|
|
@@ -350,22 +342,20 @@ function mat4EyeMatrix(out, ex,ey,ez, cx,cy,cz, ux,uy,uz) {
|
|
|
350
342
|
// =========================================================================
|
|
351
343
|
|
|
352
344
|
/**
|
|
353
|
-
* Column-major mat4 from flat TRS scalars.
|
|
354
|
-
* No struct allocation — all components passed as plain numbers.
|
|
345
|
+
* Column-major mat4 from flat TRS scalars. No struct allocation.
|
|
355
346
|
*
|
|
356
347
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
357
348
|
* @param {number} tx,ty,tz Translation.
|
|
358
349
|
* @param {number} qx,qy,qz,qw Rotation quaternion [x,y,z,w].
|
|
359
350
|
* @param {number} sx,sy,sz Scale.
|
|
360
|
-
* @returns {Float32Array|number[]} out
|
|
361
351
|
*/
|
|
362
352
|
function mat4FromTRS(out, tx,ty,tz, qx,qy,qz,qw, sx,sy,sz) {
|
|
363
353
|
const x2=qx+qx,y2=qy+qy,z2=qz+qz;
|
|
364
354
|
const xx=qx*x2,xy=qx*y2,xz=qx*z2,yy=qy*y2,yz=qy*z2,zz=qz*z2;
|
|
365
355
|
const wx=qw*x2,wy=qw*y2,wz=qw*z2;
|
|
366
|
-
out[0]=(1-(yy+zz))*sx; out[1]=(xy+wz)*sx;
|
|
367
|
-
out[4]=(xy-wz)*sy; out[5]=(1-(xx+zz))*sy;
|
|
368
|
-
out[8]=(xz+wy)*sz; out[9]=(yz-wx)*sz;
|
|
356
|
+
out[0]=(1-(yy+zz))*sx; out[1]=(xy+wz)*sx; out[2]=(xz-wy)*sx; out[3]=0;
|
|
357
|
+
out[4]=(xy-wz)*sy; out[5]=(1-(xx+zz))*sy; out[6]=(yz+wx)*sy; out[7]=0;
|
|
358
|
+
out[8]=(xz+wy)*sz; out[9]=(yz-wx)*sz; out[10]=(1-(xx+yy))*sz; out[11]=0;
|
|
369
359
|
out[12]=tx; out[13]=ty; out[14]=tz; out[15]=1;
|
|
370
360
|
return out;
|
|
371
361
|
}
|
|
@@ -374,12 +364,11 @@ function mat4FromTRS(out, tx,ty,tz, qx,qy,qz,qw, sx,sy,sz) {
|
|
|
374
364
|
* Translation-only mat4.
|
|
375
365
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
376
366
|
* @param {number} tx,ty,tz
|
|
377
|
-
* @returns {Float32Array|number[]} out
|
|
378
367
|
*/
|
|
379
368
|
function mat4FromTranslation(out, tx,ty,tz) {
|
|
380
|
-
out[0]=1;
|
|
381
|
-
out[4]=0;
|
|
382
|
-
out[8]=0;
|
|
369
|
+
out[0]=1; out[1]=0; out[2]=0; out[3]=0;
|
|
370
|
+
out[4]=0; out[5]=1; out[6]=0; out[7]=0;
|
|
371
|
+
out[8]=0; out[9]=0; out[10]=1; out[11]=0;
|
|
383
372
|
out[12]=tx; out[13]=ty; out[14]=tz; out[15]=1;
|
|
384
373
|
return out;
|
|
385
374
|
}
|
|
@@ -388,11 +377,10 @@ function mat4FromTranslation(out, tx,ty,tz) {
|
|
|
388
377
|
* Scale-only mat4.
|
|
389
378
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
390
379
|
* @param {number} sx,sy,sz
|
|
391
|
-
* @returns {Float32Array|number[]} out
|
|
392
380
|
*/
|
|
393
381
|
function mat4FromScale(out, sx,sy,sz) {
|
|
394
|
-
out[0]=sx; out[1]=0; out[2]=0;
|
|
395
|
-
out[4]=0; out[5]=sy; out[6]=0;
|
|
382
|
+
out[0]=sx; out[1]=0; out[2]=0; out[3]=0;
|
|
383
|
+
out[4]=0; out[5]=sy; out[6]=0; out[7]=0;
|
|
396
384
|
out[8]=0; out[9]=0; out[10]=sz; out[11]=0;
|
|
397
385
|
out[12]=0; out[13]=0; out[14]=0; out[15]=1;
|
|
398
386
|
return out;
|
|
@@ -405,22 +393,19 @@ function mat4FromScale(out, sx,sy,sz) {
|
|
|
405
393
|
/**
|
|
406
394
|
* Perspective projection matrix.
|
|
407
395
|
*
|
|
408
|
-
* NDC convention: ndcZMin = WEBGL (−1) or WEBGPU (0).
|
|
409
|
-
* near maps to ndcZMin, far maps to +1.
|
|
410
|
-
*
|
|
411
396
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
412
|
-
* @param {number} fov
|
|
413
|
-
* @param {number} aspect
|
|
414
|
-
* @param {number} near
|
|
415
|
-
* @param {number} far
|
|
416
|
-
* @param {number} ndcZMin
|
|
417
|
-
* @
|
|
397
|
+
* @param {number} fov Vertical field of view (radians).
|
|
398
|
+
* @param {number} aspect Width / height.
|
|
399
|
+
* @param {number} near Near plane distance (positive).
|
|
400
|
+
* @param {number} far Far plane distance (positive, > near).
|
|
401
|
+
* @param {number} ndcZMin −1 (WEBGL) or 0 (WEBGPU).
|
|
402
|
+
* @param {number} [ndcYSign=1] +1 = NDC y-up (default); −1 = NDC y-down (native Vulkan).
|
|
418
403
|
*/
|
|
419
|
-
function mat4Perspective(out, fov, aspect, near, far, ndcZMin) {
|
|
404
|
+
function mat4Perspective(out, fov, aspect, near, far, ndcZMin, ndcYSign=1) {
|
|
420
405
|
const f = 1 / Math.tan(fov * 0.5);
|
|
421
|
-
out[0]=f/aspect;
|
|
422
|
-
out[4]=0;
|
|
423
|
-
out[8]=0;
|
|
406
|
+
out[0]=f/aspect; out[1]=0; out[2]=0; out[3]=0;
|
|
407
|
+
out[4]=0; out[5]=ndcYSign*f; out[6]=0; out[7]=0;
|
|
408
|
+
out[8]=0; out[9]=0;
|
|
424
409
|
out[10]=(ndcZMin*near-far)/(far-near);
|
|
425
410
|
out[11]=-1;
|
|
426
411
|
out[12]=0; out[13]=0;
|
|
@@ -432,22 +417,18 @@ function mat4Perspective(out, fov, aspect, near, far, ndcZMin) {
|
|
|
432
417
|
/**
|
|
433
418
|
* Orthographic projection matrix.
|
|
434
419
|
*
|
|
435
|
-
* NDC convention: ndcZMin = WEBGL (−1) or WEBGPU (0).
|
|
436
|
-
*
|
|
437
420
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
438
421
|
* @param {number} left,right,bottom,top Frustum extents.
|
|
439
422
|
* @param {number} near,far Clip plane distances (positive).
|
|
440
|
-
* @param {number} ndcZMin
|
|
441
|
-
* @
|
|
423
|
+
* @param {number} ndcZMin −1 (WEBGL) or 0 (WEBGPU).
|
|
424
|
+
* @param {number} [ndcYSign=1] +1 = NDC y-up (default); −1 = NDC y-down (native Vulkan).
|
|
442
425
|
*/
|
|
443
|
-
function mat4Ortho(out, left, right, bottom, top, near, far, ndcZMin) {
|
|
426
|
+
function mat4Ortho(out, left, right, bottom, top, near, far, ndcZMin, ndcYSign=1) {
|
|
444
427
|
const rl=1/(right-left), tb=1/(top-bottom), fn=1/(far-near);
|
|
445
|
-
out[0]=2*rl; out[1]=0;
|
|
446
|
-
out[4]=0; out[5]=2*tb;
|
|
447
|
-
out[8]=0; out[9]=0;
|
|
448
|
-
out[
|
|
449
|
-
out[11]=0;
|
|
450
|
-
out[12]=-(right+left)*rl; out[13]=-(top+bottom)*tb;
|
|
428
|
+
out[0]=2*rl; out[1]=0; out[2]=0; out[3]=0;
|
|
429
|
+
out[4]=0; out[5]=ndcYSign*2*tb; out[6]=0; out[7]=0;
|
|
430
|
+
out[8]=0; out[9]=0; out[10]=(ndcZMin-1)*fn; out[11]=0;
|
|
431
|
+
out[12]=-(right+left)*rl; out[13]=ndcYSign*(-(top+bottom)*tb);
|
|
451
432
|
out[14]=(ndcZMin*far-near)*fn;
|
|
452
433
|
out[15]=1;
|
|
453
434
|
return out;
|
|
@@ -456,19 +437,17 @@ function mat4Ortho(out, left, right, bottom, top, near, far, ndcZMin) {
|
|
|
456
437
|
/**
|
|
457
438
|
* Frustum (off-centre perspective) projection matrix.
|
|
458
439
|
*
|
|
459
|
-
* NDC convention: ndcZMin = WEBGL (−1) or WEBGPU (0).
|
|
460
|
-
*
|
|
461
440
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
462
441
|
* @param {number} left,right,bottom,top Near-plane extents.
|
|
463
442
|
* @param {number} near,far Clip plane distances (positive).
|
|
464
|
-
* @param {number} ndcZMin
|
|
465
|
-
* @
|
|
443
|
+
* @param {number} ndcZMin −1 (WEBGL) or 0 (WEBGPU).
|
|
444
|
+
* @param {number} [ndcYSign=1] +1 = NDC y-up (default); −1 = NDC y-down (native Vulkan).
|
|
466
445
|
*/
|
|
467
|
-
function mat4Frustum(out, left, right, bottom, top, near, far, ndcZMin) {
|
|
446
|
+
function mat4Frustum(out, left, right, bottom, top, near, far, ndcZMin, ndcYSign=1) {
|
|
468
447
|
const rl=1/(right-left), tb=1/(top-bottom);
|
|
469
|
-
out[0]=2*near*rl;
|
|
470
|
-
out[4]=0;
|
|
471
|
-
out[8]=(right+left)*rl;
|
|
448
|
+
out[0]=2*near*rl; out[1]=0; out[2]=0; out[3]=0;
|
|
449
|
+
out[4]=0; out[5]=ndcYSign*2*near*tb; out[6]=0; out[7]=0;
|
|
450
|
+
out[8]=(right+left)*rl; out[9]=ndcYSign*(top+bottom)*tb;
|
|
472
451
|
out[10]=(ndcZMin*near-far)/(far-near);
|
|
473
452
|
out[11]=-1;
|
|
474
453
|
out[12]=0; out[13]=0;
|
|
@@ -482,29 +461,25 @@ function mat4Frustum(out, left, right, bottom, top, near, far, ndcZMin) {
|
|
|
482
461
|
// =========================================================================
|
|
483
462
|
|
|
484
463
|
/**
|
|
485
|
-
* Bias matrix: remaps xyz from NDC to texture/UV space [0,1].
|
|
486
|
-
* xy
|
|
487
|
-
* Used to
|
|
488
|
-
*
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
* [ 0.5 0 0 0
|
|
497
|
-
* [ 0 0
|
|
498
|
-
* [ 0 0 1 0 ]
|
|
499
|
-
* [ 0 0 0 1 ]
|
|
464
|
+
* Bias matrix: remaps xyz from NDC to texture/UV space [0, 1].
|
|
465
|
+
* xy remap from [−1, 1]; z remaps from [ndcZMin, 1].
|
|
466
|
+
* Used to convert light-space NDC coordinates to shadow map UV.
|
|
467
|
+
*
|
|
468
|
+
* Convention note: the standard bias maps NDC y = −1 → texture v = 0 and
|
|
469
|
+
* NDC y = +1 → texture v = 1. This is correct for both NDC y-up and y-down
|
|
470
|
+
* conventions because the shadow map was rendered with the same projection.
|
|
471
|
+
*
|
|
472
|
+
* Column-major (WEBGL, ndcZMin=−1): Column-major (WEBGPU, ndcZMin=0):
|
|
473
|
+
* [ 0.5 0 0 0.5 ] [ 0.5 0 0 0.5 ]
|
|
474
|
+
* [ 0 0.5 0 0.5 ] [ 0 0.5 0 0.5 ]
|
|
475
|
+
* [ 0 0 0.5 0.5 ] [ 0 0 1 0 ]
|
|
476
|
+
* [ 0 0 0 1 ] [ 0 0 0 1 ]
|
|
500
477
|
*
|
|
501
478
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
502
479
|
* @param {number} ndcZMin WEBGL (−1) or WEBGPU (0).
|
|
503
|
-
* @returns {Float32Array|number[]} out
|
|
504
480
|
*/
|
|
505
481
|
function mat4Bias(out, ndcZMin) {
|
|
506
|
-
const sz
|
|
507
|
-
const tz = -ndcZMin / (1 - ndcZMin);
|
|
482
|
+
const sz=1/(1-ndcZMin), tz=-ndcZMin/(1-ndcZMin);
|
|
508
483
|
out[0]=0.5; out[1]=0; out[2]=0; out[3]=0;
|
|
509
484
|
out[4]=0; out[5]=0.5; out[6]=0; out[7]=0;
|
|
510
485
|
out[8]=0; out[9]=0; out[10]=sz; out[11]=0;
|
|
@@ -519,13 +494,12 @@ function mat4Bias(out, ndcZMin) {
|
|
|
519
494
|
* @param {Float32Array|number[]} out 16-element destination.
|
|
520
495
|
* @param {number} nx,ny,nz Unit plane normal.
|
|
521
496
|
* @param {number} d Plane offset (dot(point_on_plane, normal)).
|
|
522
|
-
* @returns {Float32Array|number[]} out
|
|
523
497
|
*/
|
|
524
498
|
function mat4Reflect(out, nx,ny,nz,d) {
|
|
525
|
-
out[0]=1-2*nx*nx;
|
|
526
|
-
out[4]=-2*nx*ny;
|
|
527
|
-
out[8]=-2*nx*nz;
|
|
528
|
-
out[12]=2*d*nx;
|
|
499
|
+
out[0]=1-2*nx*nx; out[1]=-2*ny*nx; out[2]=-2*nz*nx; out[3]=0;
|
|
500
|
+
out[4]=-2*nx*ny; out[5]=1-2*ny*ny; out[6]=-2*nz*ny; out[7]=0;
|
|
501
|
+
out[8]=-2*nx*nz; out[9]=-2*ny*nz; out[10]=1-2*nz*nz; out[11]=0;
|
|
502
|
+
out[12]=2*d*nx; out[13]=2*d*ny; out[14]=2*d*nz; out[15]=1;
|
|
529
503
|
return out;
|
|
530
504
|
}
|
|
531
505
|
|
|
@@ -537,7 +511,6 @@ function mat4Reflect(out, nx,ny,nz,d) {
|
|
|
537
511
|
* Extract translation from a column-major mat4 (column 3).
|
|
538
512
|
* @param {Float32Array|number[]} out3 3-element destination.
|
|
539
513
|
* @param {Float32Array|number[]} m 16-element source.
|
|
540
|
-
* @returns {Float32Array|number[]} out3
|
|
541
514
|
*/
|
|
542
515
|
function mat4ToTranslation(out3, m) {
|
|
543
516
|
out3[0]=m[12]; out3[1]=m[13]; out3[2]=m[14];
|
|
@@ -545,11 +518,10 @@ function mat4ToTranslation(out3, m) {
|
|
|
545
518
|
}
|
|
546
519
|
|
|
547
520
|
/**
|
|
548
|
-
* Extract scale from a column-major mat4 (column lengths of rotation block).
|
|
521
|
+
* Extract scale from a column-major mat4 (column lengths of the rotation block).
|
|
549
522
|
* Assumes no shear.
|
|
550
523
|
* @param {Float32Array|number[]} out3 3-element destination.
|
|
551
524
|
* @param {Float32Array|number[]} m 16-element source.
|
|
552
|
-
* @returns {Float32Array|number[]} out3
|
|
553
525
|
*/
|
|
554
526
|
function mat4ToScale(out3, m) {
|
|
555
527
|
out3[0]=Math.sqrt(m[0]*m[0]+m[1]*m[1]+m[2]*m[2]);
|
|
@@ -560,11 +532,9 @@ function mat4ToScale(out3, m) {
|
|
|
560
532
|
|
|
561
533
|
/**
|
|
562
534
|
* Extract rotation as a unit quaternion from a column-major mat4.
|
|
563
|
-
* Scale is factored out from each column
|
|
564
|
-
* Assumes no shear.
|
|
535
|
+
* Scale is factored out from each column. Assumes no shear.
|
|
565
536
|
* @param {number[]} out4 4-element [x,y,z,w] destination.
|
|
566
537
|
* @param {Float32Array|number[]} m 16-element source.
|
|
567
|
-
* @returns {number[]} out4
|
|
568
538
|
*/
|
|
569
539
|
function mat4ToRotation(out4, m) {
|
|
570
540
|
const sx=Math.sqrt(m[0]*m[0]+m[1]*m[1]+m[2]*m[2])||1;
|
|
@@ -581,28 +551,43 @@ function mat4ToRotation(out4, m) {
|
|
|
581
551
|
* @module tree/query
|
|
582
552
|
* @license AGPL-3.0-only
|
|
583
553
|
*
|
|
584
|
-
* The operative layer — receives
|
|
554
|
+
* The operative layer — receives matrices and extracts information.
|
|
585
555
|
* Contrast with form.js which constructs matrices from specs.
|
|
586
556
|
*
|
|
587
|
-
* form.js —
|
|
588
|
-
* query.js
|
|
589
|
-
*
|
|
590
|
-
* No dependency on form.js. Operating on matrices requires no knowledge
|
|
591
|
-
* of how they were constructed.
|
|
557
|
+
* form.js — specs → matrix
|
|
558
|
+
* query.js — matrix → information
|
|
592
559
|
*
|
|
593
560
|
* Storage: column-major Float32Array / ArrayLike<number>.
|
|
594
|
-
* Element [col*4 + row] = M[row, col].
|
|
595
|
-
*
|
|
596
561
|
* Multiply: mat4Mul(out, A, B) = A · B (standard math order).
|
|
597
|
-
*
|
|
598
562
|
* Pipeline: clip = P · V · M · v
|
|
599
|
-
* P = projection (eye → clip)
|
|
600
|
-
* V = view (world → eye)
|
|
601
|
-
* M = model (local → world)
|
|
602
563
|
*
|
|
603
|
-
* NDC convention
|
|
604
|
-
*
|
|
605
|
-
*
|
|
564
|
+
* ── NDC Z convention ──────────────────────────────────────────────────────
|
|
565
|
+
* Passed as `ndcZMin` to every space-transform function:
|
|
566
|
+
* WEBGL = −1 z ∈ [−1, 1]
|
|
567
|
+
* WEBGPU = 0 z ∈ [ 0, 1]
|
|
568
|
+
*
|
|
569
|
+
* ── NDC Y convention ──────────────────────────────────────────────────────
|
|
570
|
+
* Standard (OpenGL / WebGL / WebGPU browser / Three.js / p5v2):
|
|
571
|
+
* NDC y-up — y = +1 at top, y = −1 at bottom.
|
|
572
|
+
* Native Vulkan: NDC y-down — projections constructed with ndcYSign = −1
|
|
573
|
+
* (see form.js). Query functions are convention-agnostic: they work on
|
|
574
|
+
* whatever matrices are passed in.
|
|
575
|
+
*
|
|
576
|
+
* ── Viewport convention ───────────────────────────────────────────────────
|
|
577
|
+
* vp = [x, y, w, h] — w and h are SIGNED.
|
|
578
|
+
*
|
|
579
|
+
* The sign of h encodes the relationship between NDC y and screen y:
|
|
580
|
+
* h < 0 (e.g. −canvasH): screen y-DOWN (DOM / p5 mouseX·mouseY / Vulkan surface)
|
|
581
|
+
* NDC y=+1 → screen y=0 (top)
|
|
582
|
+
* NDC y=−1 → screen y=H (bottom)
|
|
583
|
+
* h > 0 (e.g. +canvasH): screen y-UP (OpenGL desktop / WebGL gl_FragCoord)
|
|
584
|
+
* NDC y=−1 → screen y=0 (bottom)
|
|
585
|
+
* NDC y=+1 → screen y=H (top)
|
|
586
|
+
*
|
|
587
|
+
* Pass [0, canvasH, canvasW, −canvasH] for p5/DOM coordinates.
|
|
588
|
+
* Pass [0, 0, canvasW, canvasH] for WebGL gl_FragCoord / OpenGL bottom-left.
|
|
589
|
+
* All helpers use vp[2]/vp[3] signed — no Math.abs — so both conventions
|
|
590
|
+
* work automatically without any branching.
|
|
606
591
|
*
|
|
607
592
|
* All functions follow the out-first, zero-allocation contract.
|
|
608
593
|
* Returns null on degeneracy (singular matrix, etc.).
|
|
@@ -610,10 +595,10 @@ function mat4ToRotation(out4, m) {
|
|
|
610
595
|
|
|
611
596
|
|
|
612
597
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
613
|
-
// Mat4
|
|
598
|
+
// Mat4 arithmetic
|
|
614
599
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
615
600
|
|
|
616
|
-
/** out = A · B (column-major
|
|
601
|
+
/** out = A · B (column-major) */
|
|
617
602
|
function mat4Mul(out, A, B) {
|
|
618
603
|
const a0=A[0],a1=A[1],a2=A[2],a3=A[3],
|
|
619
604
|
a4=A[4],a5=A[5],a6=A[6],a7=A[7],
|
|
@@ -642,229 +627,150 @@ function mat4Mul(out, A, B) {
|
|
|
642
627
|
return out;
|
|
643
628
|
}
|
|
644
629
|
|
|
645
|
-
/** out = inverse(src).
|
|
630
|
+
/** out = inverse(src). Returns null if singular (|det| < 1e-12). */
|
|
646
631
|
function mat4Invert(out, src) {
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
b08=a20*a33-a23*a30,b09=a21*a32-a22*a31,
|
|
657
|
-
b10=a21*a33-a23*a31,b11=a22*a33-a23*a32;
|
|
658
|
-
let det=b00*b11-b01*b10+b02*b09+b03*b08-b04*b07+b05*b06;
|
|
632
|
+
const s0=src[0],s1=src[1],s2=src[2],s3=src[3],
|
|
633
|
+
s4=src[4],s5=src[5],s6=src[6],s7=src[7],
|
|
634
|
+
s8=src[8],s9=src[9],s10=src[10],s11=src[11],
|
|
635
|
+
s12=src[12],s13=src[13],s14=src[14],s15=src[15];
|
|
636
|
+
const b0=s0*s5-s1*s4, b1=s0*s6-s2*s4, b2=s0*s7-s3*s4,
|
|
637
|
+
b3=s1*s6-s2*s5, b4=s1*s7-s3*s5, b5=s2*s7-s3*s6,
|
|
638
|
+
b6=s8*s13-s9*s12, b7=s8*s14-s10*s12, b8=s8*s15-s11*s12,
|
|
639
|
+
b9=s9*s14-s10*s13, b10=s9*s15-s11*s13, b11=s10*s15-s11*s14;
|
|
640
|
+
let det=b0*b11-b1*b10+b2*b9+b3*b8-b4*b7+b5*b6;
|
|
659
641
|
if (Math.abs(det) < 1e-12) return null;
|
|
660
|
-
det=1/det;
|
|
661
|
-
out[0]=(
|
|
662
|
-
out[1]=(
|
|
663
|
-
out[2]=(
|
|
664
|
-
out[3]=(
|
|
665
|
-
out[4]=(
|
|
666
|
-
out[5]=(
|
|
667
|
-
out[6]=(
|
|
668
|
-
out[7]=(
|
|
669
|
-
out[8]=(
|
|
670
|
-
out[9]=(
|
|
671
|
-
out[10]=(
|
|
672
|
-
out[11]=(
|
|
673
|
-
out[12]=(
|
|
674
|
-
out[13]=(
|
|
675
|
-
out[14]=(
|
|
676
|
-
out[15]=(
|
|
677
|
-
return out;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/** out = transpose(src) */
|
|
681
|
-
function mat4Transpose(out, src) {
|
|
682
|
-
if (out === src) {
|
|
683
|
-
let t;
|
|
684
|
-
t=src[1];out[1]=src[4];out[4]=t;
|
|
685
|
-
t=src[2];out[2]=src[8];out[8]=t;
|
|
686
|
-
t=src[3];out[3]=src[12];out[12]=t;
|
|
687
|
-
t=src[6];out[6]=src[9];out[9]=t;
|
|
688
|
-
t=src[7];out[7]=src[13];out[13]=t;
|
|
689
|
-
t=src[11];out[11]=src[14];out[14]=t;
|
|
690
|
-
} else {
|
|
691
|
-
out[0]=src[0];out[1]=src[4];out[2]=src[8];out[3]=src[12];
|
|
692
|
-
out[4]=src[1];out[5]=src[5];out[6]=src[9];out[7]=src[13];
|
|
693
|
-
out[8]=src[2];out[9]=src[6];out[10]=src[10];out[11]=src[14];
|
|
694
|
-
out[12]=src[3];out[13]=src[7];out[14]=src[11];out[15]=src[15];
|
|
695
|
-
}
|
|
642
|
+
det = 1/det;
|
|
643
|
+
out[0]=(s5*b11-s6*b10+s7*b9)*det;
|
|
644
|
+
out[1]=(s2*b10-s1*b11-s3*b9)*det;
|
|
645
|
+
out[2]=(s13*b5-s14*b4+s15*b3)*det;
|
|
646
|
+
out[3]=(s10*b4-s9*b5-s11*b3)*det;
|
|
647
|
+
out[4]=(s6*b8-s4*b11-s7*b7)*det;
|
|
648
|
+
out[5]=(s0*b11-s2*b8+s3*b7)*det;
|
|
649
|
+
out[6]=(s14*b2-s12*b5-s15*b1)*det;
|
|
650
|
+
out[7]=(s8*b5-s10*b2+s11*b1)*det;
|
|
651
|
+
out[8]=(s4*b10-s5*b8+s7*b6)*det;
|
|
652
|
+
out[9]=(s1*b8-s0*b10-s3*b6)*det;
|
|
653
|
+
out[10]=(s12*b4-s13*b2+s15*b0)*det;
|
|
654
|
+
out[11]=(s9*b2-s8*b4-s11*b0)*det;
|
|
655
|
+
out[12]=(s5*b7-s4*b9-s6*b6)*det;
|
|
656
|
+
out[13]=(s0*b9-s1*b7+s2*b6)*det;
|
|
657
|
+
out[14]=(s13*b1-s12*b3-s14*b0)*det;
|
|
658
|
+
out[15]=(s8*b3-s9*b1+s10*b0)*det;
|
|
696
659
|
return out;
|
|
697
660
|
}
|
|
698
661
|
|
|
699
|
-
/**
|
|
662
|
+
/**
|
|
663
|
+
* Normal matrix: inverseTranspose(upper-left 3×3 of src).
|
|
664
|
+
* On degeneracy writes zeros and returns out.
|
|
665
|
+
* @param {Float32Array|number[]} out 9-element destination.
|
|
666
|
+
* @param {Float32Array|number[]} src 16-element mat4.
|
|
667
|
+
*/
|
|
700
668
|
function mat3NormalFromMat4(out, src) {
|
|
701
669
|
const a00=src[0],a01=src[1],a02=src[2],
|
|
702
670
|
a10=src[4],a11=src[5],a12=src[6],
|
|
703
671
|
a20=src[8],a21=src[9],a22=src[10];
|
|
704
|
-
const b01=a22*a11-a12*a21,
|
|
705
|
-
b11=-a22*a01+a02*a21,
|
|
706
|
-
b21=a12*a01-a02*a11;
|
|
672
|
+
const b01=a22*a11-a12*a21, b11=-a22*a01+a02*a21, b21=a12*a01-a02*a11;
|
|
707
673
|
let det=a00*b01+a10*b11+a20*b21;
|
|
708
674
|
if (Math.abs(det) < 1e-12) { for(let i=0;i<9;i++)out[i]=0; return out; }
|
|
709
675
|
det=1/det;
|
|
710
|
-
out[0]=b01*det;
|
|
711
|
-
out[
|
|
712
|
-
out[
|
|
713
|
-
out[3]=b11*det;
|
|
714
|
-
out[4]=(a22*a00-a02*a20)*det;
|
|
715
|
-
out[5]=(-a21*a00+a01*a20)*det;
|
|
716
|
-
out[6]=b21*det;
|
|
717
|
-
out[7]=(-a12*a00+a02*a10)*det;
|
|
718
|
-
out[8]=(a11*a00-a01*a10)*det;
|
|
676
|
+
out[0]=b01*det; out[1]=(-a22*a10+a12*a20)*det; out[2]=(a21*a10-a11*a20)*det;
|
|
677
|
+
out[3]=b11*det; out[4]=(a22*a00-a02*a20)*det; out[5]=(-a21*a00+a01*a20)*det;
|
|
678
|
+
out[6]=b21*det; out[7]=(-a12*a00+a02*a10)*det; out[8]=(a11*a00-a01*a10)*det;
|
|
719
679
|
return out;
|
|
720
680
|
}
|
|
721
681
|
|
|
722
|
-
/** out = mat4 * [x,y,z,1], perspective-divides, writes xyz */
|
|
682
|
+
/** out = mat4 * [x,y,z,1], perspective-divides, writes xyz. */
|
|
723
683
|
function mat4MulPoint(out, m, x, y, z) {
|
|
724
|
-
const rx
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
if (rw !== 0 && rw !== 1) {
|
|
729
|
-
out[0] = rx/rw; out[1] = ry/rw; out[2] = rz/rw;
|
|
730
|
-
} else {
|
|
731
|
-
out[0] = rx; out[1] = ry; out[2] = rz;
|
|
732
|
-
}
|
|
684
|
+
const rx=m[0]*x+m[4]*y+m[8]*z+m[12], ry=m[1]*x+m[5]*y+m[9]*z+m[13],
|
|
685
|
+
rz=m[2]*x+m[6]*y+m[10]*z+m[14], rw=m[3]*x+m[7]*y+m[11]*z+m[15];
|
|
686
|
+
if (rw!==0&&rw!==1) { out[0]=rx/rw; out[1]=ry/rw; out[2]=rz/rw; }
|
|
687
|
+
else { out[0]=rx; out[1]=ry; out[2]=rz; }
|
|
733
688
|
return out;
|
|
734
689
|
}
|
|
735
690
|
|
|
736
691
|
/**
|
|
737
|
-
* Apply only the 3×3 linear block of a mat4 to a direction
|
|
738
|
-
*
|
|
739
|
-
* when the matrix is known to be orthogonal (use mat3NormalFromMat4 for normals
|
|
740
|
-
* under non-uniform scale).
|
|
741
|
-
*
|
|
742
|
-
* @param {Float32Array|number[]} out 3-element destination.
|
|
743
|
-
* @param {Float32Array|number[]} m 16-element mat4.
|
|
744
|
-
* @param {number} dx,dy,dz Input direction.
|
|
745
|
-
* @returns {Float32Array|number[]} out
|
|
692
|
+
* Apply only the 3×3 linear block of a mat4 to a direction (no translation,
|
|
693
|
+
* no perspective divide). Use mat3NormalFromMat4 for normals under non-uniform scale.
|
|
746
694
|
*/
|
|
747
695
|
function mat4MulDir(out, m, dx, dy, dz) {
|
|
748
|
-
out[0]
|
|
749
|
-
out[1]
|
|
750
|
-
out[2]
|
|
696
|
+
out[0]=m[0]*dx+m[4]*dy+m[8]*dz;
|
|
697
|
+
out[1]=m[1]*dx+m[5]*dy+m[9]*dz;
|
|
698
|
+
out[2]=m[2]*dx+m[6]*dy+m[10]*dz;
|
|
751
699
|
return out;
|
|
752
700
|
}
|
|
753
701
|
|
|
754
702
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
755
|
-
// Projection queries
|
|
703
|
+
// Projection queries
|
|
756
704
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
757
705
|
|
|
758
|
-
/** @returns {boolean} true if orthographic */
|
|
706
|
+
/** @returns {boolean} true if orthographic. */
|
|
759
707
|
function projIsOrtho(p) { return p[15] !== 0; }
|
|
760
708
|
|
|
761
709
|
/**
|
|
762
710
|
* Near plane distance.
|
|
763
|
-
* @param {ArrayLike<number>} p
|
|
764
|
-
* @param {number}
|
|
711
|
+
* @param {ArrayLike<number>} p Projection mat4.
|
|
712
|
+
* @param {number} ndcZMin WEBGL (−1) or WEBGPU (0).
|
|
765
713
|
*/
|
|
766
714
|
function projNear(p, ndcZMin) {
|
|
767
|
-
return p[15]
|
|
768
|
-
? p[14] / (p[10] + ndcZMin)
|
|
769
|
-
: (p[14] - ndcZMin) / p[10];
|
|
715
|
+
return p[15]===0 ? p[14]/(p[10]+ndcZMin) : (p[14]-ndcZMin)/p[10];
|
|
770
716
|
}
|
|
771
717
|
|
|
772
|
-
/** Far plane distance (
|
|
718
|
+
/** Far plane distance (far always maps to NDC z = 1, convention-independent). */
|
|
773
719
|
function projFar(p) {
|
|
774
|
-
return p[15]
|
|
775
|
-
? p[14] / (1 + p[10])
|
|
776
|
-
: (p[14] - 1) / p[10];
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
function projLeft(p, ndcZMin) {
|
|
780
|
-
return p[15] === 1
|
|
781
|
-
? -(1 + p[12]) / p[0]
|
|
782
|
-
: projNear(p, ndcZMin) * (p[8] - 1) / p[0];
|
|
720
|
+
return p[15]===0 ? p[14]/(1+p[10]) : (p[14]-1)/p[10];
|
|
783
721
|
}
|
|
784
722
|
|
|
785
|
-
function
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
}
|
|
723
|
+
function projLeft (p, ndcZMin) { return p[15]===1 ? -(1+p[12])/p[0] : projNear(p,ndcZMin)*(p[8]-1)/p[0]; }
|
|
724
|
+
function projRight (p, ndcZMin) { return p[15]===1 ? (1-p[12])/p[0] : projNear(p,ndcZMin)*(1+p[8])/p[0]; }
|
|
725
|
+
function projTop (p, ndcZMin) { return p[15]===1 ? (p[13]-1)/p[5] : projNear(p,ndcZMin)*(p[9]-1)/p[5]; }
|
|
726
|
+
function projBottom(p, ndcZMin) { return p[15]===1 ? (1+p[13])/p[5] : projNear(p,ndcZMin)*(1+p[9])/p[5]; }
|
|
790
727
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
function projBottom(p, ndcZMin) {
|
|
798
|
-
return p[15] === 1
|
|
799
|
-
? (1 + p[13]) / p[5]
|
|
800
|
-
: projNear(p, ndcZMin) * (1 + p[9]) / p[5];
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
/** Vertical fov (radians, perspective only). */
|
|
804
|
-
function projFov(p) {
|
|
805
|
-
return Math.abs(2 * Math.atan(1 / p[5]));
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
/** Horizontal fov (radians, perspective only). */
|
|
809
|
-
function projHfov(p) {
|
|
810
|
-
return Math.abs(2 * Math.atan(1 / p[0]));
|
|
811
|
-
}
|
|
728
|
+
/** Vertical field of view in radians (perspective only). */
|
|
729
|
+
function projFov (p) { return Math.abs(2*Math.atan(1/p[5])); }
|
|
730
|
+
/** Horizontal field of view in radians (perspective only). */
|
|
731
|
+
function projHfov(p) { return Math.abs(2*Math.atan(1/p[0])); }
|
|
812
732
|
|
|
813
733
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
814
|
-
// Derived matrices
|
|
734
|
+
// Derived matrices
|
|
815
735
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
816
736
|
|
|
817
737
|
/** out = P · V */
|
|
818
|
-
function mat4PV(out, proj, view)
|
|
819
|
-
|
|
738
|
+
function mat4PV(out, proj, view) { return mat4Mul(out, proj, view); }
|
|
820
739
|
/** out = V · M */
|
|
821
740
|
function mat4MV(out, model, view) { return mat4Mul(out, view, model); }
|
|
822
741
|
|
|
823
742
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
824
|
-
//
|
|
743
|
+
// Frame-relative transforms
|
|
825
744
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
826
745
|
|
|
827
746
|
/**
|
|
828
|
-
*
|
|
829
|
-
* @
|
|
830
|
-
* @param {ArrayLike<number>} from Source frame transform.
|
|
831
|
-
* @param {ArrayLike<number>} to Destination frame transform.
|
|
832
|
-
* @returns {ArrayLike<number>|null} out, or null if to is singular.
|
|
747
|
+
* Location transform between frames: out = inv(to) · from.
|
|
748
|
+
* @returns {ArrayLike<number>|null} out, or null if `to` is singular.
|
|
833
749
|
*/
|
|
834
750
|
function mat4Location(out, from, to) {
|
|
835
751
|
return mat4Invert(out, to) && mat4Mul(out, out, from);
|
|
836
752
|
}
|
|
837
753
|
|
|
838
754
|
/**
|
|
839
|
-
*
|
|
840
|
-
* Uses only the upper-left 3×3 blocks,
|
|
841
|
-
* @
|
|
842
|
-
* @param {ArrayLike<number>} from Source frame transform.
|
|
843
|
-
* @param {ArrayLike<number>} to Destination frame transform.
|
|
844
|
-
* @returns {ArrayLike<number>|null} out, or null if from is singular.
|
|
755
|
+
* Direction transform between frames: out = to₃ · inv(from₃).
|
|
756
|
+
* Uses only the upper-left 3×3 blocks (rotation/scale, no translation).
|
|
757
|
+
* @returns {ArrayLike<number>|null} out, or null if `from` is singular.
|
|
845
758
|
*/
|
|
846
759
|
function mat3Direction(out, from, to) {
|
|
847
|
-
const a00=from[0],
|
|
848
|
-
a10=from[4],
|
|
849
|
-
a20=from[8],
|
|
850
|
-
const b01=a22*a11-a12*a21,
|
|
851
|
-
b11=a12*a20-a22*a10,
|
|
852
|
-
b21=a21*a10-a11*a20;
|
|
760
|
+
const a00=from[0],a01=from[1],a02=from[2],
|
|
761
|
+
a10=from[4],a11=from[5],a12=from[6],
|
|
762
|
+
a20=from[8],a21=from[9],a22=from[10];
|
|
763
|
+
const b01=a22*a11-a12*a21, b11=a12*a20-a22*a10, b21=a21*a10-a11*a20;
|
|
853
764
|
let det=a00*b01+a01*b11+a02*b21;
|
|
854
765
|
if (Math.abs(det) < 1e-12) return null;
|
|
855
766
|
det=1/det;
|
|
856
|
-
const i00=b01*det,
|
|
857
|
-
const i10=b11*det,
|
|
858
|
-
const i20=b21*det,
|
|
859
|
-
const t00=to[0],
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
const m10=t00*i10+t10*i11+t20*i12, m11=t01*i10+t11*i11+t21*i12, m12=t02*i10+t12*i11+t22*i12;
|
|
864
|
-
const m20=t00*i20+t10*i21+t20*i22, m21=t01*i20+t11*i21+t21*i22, m22=t02*i20+t12*i21+t22*i22;
|
|
865
|
-
out[0]=m00; out[1]=m10; out[2]=m20;
|
|
866
|
-
out[3]=m01; out[4]=m11; out[5]=m21;
|
|
867
|
-
out[6]=m02; out[7]=m12; out[8]=m22;
|
|
767
|
+
const i00=b01*det, i01=(a02*a21-a22*a01)*det, i02=(a12*a01-a02*a11)*det;
|
|
768
|
+
const i10=b11*det, i11=(a22*a00-a02*a20)*det, i12=(a02*a10-a12*a00)*det;
|
|
769
|
+
const i20=b21*det, i21=(a01*a20-a21*a00)*det, i22=(a11*a00-a01*a10)*det;
|
|
770
|
+
const t00=to[0],t01=to[1],t02=to[2], t10=to[4],t11=to[5],t12=to[6], t20=to[8],t21=to[9],t22=to[10];
|
|
771
|
+
out[0]=t00*i00+t10*i01+t20*i02; out[1]=t01*i00+t11*i01+t21*i02; out[2]=t02*i00+t12*i01+t22*i02;
|
|
772
|
+
out[3]=t00*i10+t10*i11+t20*i12; out[4]=t01*i10+t11*i11+t21*i12; out[5]=t02*i10+t12*i11+t22*i12;
|
|
773
|
+
out[6]=t00*i20+t10*i21+t20*i22; out[7]=t01*i20+t11*i21+t21*i22; out[8]=t02*i20+t12*i21+t22*i22;
|
|
868
774
|
return out;
|
|
869
775
|
}
|
|
870
776
|
|
|
@@ -872,215 +778,177 @@ function mat3Direction(out, from, to) {
|
|
|
872
778
|
// Space transforms — mapLocation / mapDirection
|
|
873
779
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
874
780
|
//
|
|
875
|
-
//
|
|
876
|
-
//
|
|
877
|
-
// All intermediates are stack locals (zero shared state).
|
|
781
|
+
// Flat dispatch: every from→to pair is a self-contained leaf with only stack
|
|
782
|
+
// locals — no reentrancy, no shared state between calls.
|
|
878
783
|
//
|
|
879
|
-
// Matrices bag m
|
|
880
|
-
//
|
|
881
|
-
//
|
|
882
|
-
//
|
|
883
|
-
//
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
//
|
|
887
|
-
//
|
|
888
|
-
//
|
|
784
|
+
// Matrices bag `m`:
|
|
785
|
+
// mat4Proj Float32Array(16) projection (eye → clip)
|
|
786
|
+
// mat4View Float32Array(16) view (world → eye)
|
|
787
|
+
// mat4Eye? Float32Array(16) eye (eye → world); caller fills before passing
|
|
788
|
+
// mat4PV? Float32Array(16) P · V; caller fills or _ensurePV allocates once
|
|
789
|
+
// mat4PVInv? Float32Array(16) inv(P · V); caller fills
|
|
790
|
+
// fromFrame? Float32Array(16) MATRIX source frame
|
|
791
|
+
// toFrameInv? Float32Array(16) inv(MATRIX dest frame)
|
|
792
|
+
//
|
|
793
|
+
// Viewport `vp` = [x, y, w, h]:
|
|
794
|
+
// Use SIGNED h to encode screen-y direction (see module header).
|
|
795
|
+
// Core formula: screen = (ndc*0.5+0.5)*vp[k] + vp[k-2] (k=2 for x, k=3 for y)
|
|
796
|
+
// Inverse: ndc = ((screen-vp[k-2])/vp[k])*2 - 1
|
|
797
|
+
// Negative vp[3] flips NDC y-up to screen y-down automatically.
|
|
889
798
|
//
|
|
890
799
|
|
|
891
|
-
// ── Location
|
|
800
|
+
// ── Location helpers ─────────────────────────────────────────────────────
|
|
892
801
|
|
|
893
802
|
function _worldToScreen(out, px, py, pz, pv, vp, ndcZMin) {
|
|
894
|
-
const x
|
|
895
|
-
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
const vpX=vp[0], vpY=vp[1], vpW=Math.abs(vp[2]), vpH=Math.abs(vp[3]);
|
|
901
|
-
out[0] = vpX + vpW * (nx + 1) * 0.5;
|
|
902
|
-
out[1] = vpY + vpH * (1 - (ny + 1) * 0.5);
|
|
903
|
-
out[2] = (nz - ndcZMin) / (1 - ndcZMin);
|
|
803
|
+
const x=pv[0]*px+pv[4]*py+pv[8]*pz+pv[12], y=pv[1]*px+pv[5]*py+pv[9]*pz+pv[13],
|
|
804
|
+
z=pv[2]*px+pv[6]*py+pv[10]*pz+pv[14], w=pv[3]*px+pv[7]*py+pv[11]*pz+pv[15];
|
|
805
|
+
const xi=(w!==0&&w!==1)?1/w:1;
|
|
806
|
+
out[0]=(x*xi*0.5+0.5)*vp[2]+vp[0];
|
|
807
|
+
out[1]=(y*xi*0.5+0.5)*vp[3]+vp[1];
|
|
808
|
+
out[2]=(z*xi-ndcZMin)/(1-ndcZMin);
|
|
904
809
|
return out;
|
|
905
810
|
}
|
|
906
811
|
|
|
907
812
|
function _screenToWorld(out, sx, sy, sz, ipv, vp, ndcZMin) {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
return mat4MulPoint(out, ipv, nx, ny, nz);
|
|
813
|
+
return mat4MulPoint(out, ipv,
|
|
814
|
+
((sx-vp[0])/vp[2])*2-1,
|
|
815
|
+
((sy-vp[1])/vp[3])*2-1,
|
|
816
|
+
sz*(1-ndcZMin)+ndcZMin);
|
|
913
817
|
}
|
|
914
818
|
|
|
915
819
|
function _worldToNDC(out, px, py, pz, pv) {
|
|
916
|
-
const x=pv[0]*px+pv[4]*py+pv[8]*pz+pv[12]
|
|
917
|
-
|
|
918
|
-
const
|
|
919
|
-
const w=pv[3]*px+pv[7]*py+pv[11]*pz+pv[15];
|
|
920
|
-
const xi = (w !== 0 && w !== 1) ? 1/w : 1;
|
|
820
|
+
const x=pv[0]*px+pv[4]*py+pv[8]*pz+pv[12], y=pv[1]*px+pv[5]*py+pv[9]*pz+pv[13],
|
|
821
|
+
z=pv[2]*px+pv[6]*py+pv[10]*pz+pv[14], w=pv[3]*px+pv[7]*py+pv[11]*pz+pv[15];
|
|
822
|
+
const xi=(w!==0&&w!==1)?1/w:1;
|
|
921
823
|
out[0]=x*xi; out[1]=y*xi; out[2]=z*xi;
|
|
922
824
|
return out;
|
|
923
825
|
}
|
|
924
826
|
|
|
925
|
-
function _ndcToWorld(out, nx, ny, nz, ipv) {
|
|
926
|
-
return mat4MulPoint(out, ipv, nx, ny, nz);
|
|
927
|
-
}
|
|
827
|
+
function _ndcToWorld(out, nx, ny, nz, ipv) { return mat4MulPoint(out,ipv,nx,ny,nz); }
|
|
928
828
|
|
|
929
829
|
function _screenToNDC(out, sx, sy, sz, vp, ndcZMin) {
|
|
930
|
-
|
|
931
|
-
out[
|
|
932
|
-
out[
|
|
933
|
-
out[2] = sz * (1 - ndcZMin) + ndcZMin;
|
|
830
|
+
out[0]=((sx-vp[0])/vp[2])*2-1;
|
|
831
|
+
out[1]=((sy-vp[1])/vp[3])*2-1;
|
|
832
|
+
out[2]=sz*(1-ndcZMin)+ndcZMin;
|
|
934
833
|
return out;
|
|
935
834
|
}
|
|
936
835
|
|
|
937
836
|
function _ndcToScreen(out, nx, ny, nz, vp, ndcZMin) {
|
|
938
|
-
|
|
939
|
-
out[
|
|
940
|
-
out[
|
|
941
|
-
out[2] = (nz - ndcZMin) / (1 - ndcZMin);
|
|
837
|
+
out[0]=(nx*0.5+0.5)*vp[2]+vp[0];
|
|
838
|
+
out[1]=(ny*0.5+0.5)*vp[3]+vp[1];
|
|
839
|
+
out[2]=(nz-ndcZMin)/(1-ndcZMin);
|
|
942
840
|
return out;
|
|
943
841
|
}
|
|
944
842
|
|
|
945
843
|
function _ensurePV(m) {
|
|
946
|
-
if (m.
|
|
947
|
-
m.
|
|
948
|
-
mat4Mul(m.
|
|
949
|
-
return m.
|
|
844
|
+
if (m.mat4PV) return m.mat4PV;
|
|
845
|
+
m.mat4PV = new Float32Array(16);
|
|
846
|
+
mat4Mul(m.mat4PV, m.mat4Proj, m.mat4View);
|
|
847
|
+
return m.mat4PV;
|
|
950
848
|
}
|
|
951
849
|
|
|
952
850
|
/**
|
|
953
851
|
* Map a point between named coordinate spaces.
|
|
954
852
|
*
|
|
955
|
-
* @param {
|
|
956
|
-
* @param {number}
|
|
957
|
-
* @param {string}
|
|
958
|
-
* @param {string}
|
|
959
|
-
* @param {object}
|
|
960
|
-
*
|
|
961
|
-
* @param {
|
|
962
|
-
* @
|
|
853
|
+
* @param {number[]} out 3-element destination — written and returned.
|
|
854
|
+
* @param {number} px,py,pz Input point.
|
|
855
|
+
* @param {string} from Source space (WORLD, EYE, SCREEN, NDC, MATRIX).
|
|
856
|
+
* @param {string} to Destination space.
|
|
857
|
+
* @param {object} m Matrices bag — see module header.
|
|
858
|
+
* @param {number[]} vp Viewport [x, y, w, h]; sign of h encodes screen-y direction.
|
|
859
|
+
* @param {number} ndcZMin WEBGL (−1) or WEBGPU (0).
|
|
860
|
+
* @returns {number[]} out
|
|
963
861
|
*/
|
|
964
862
|
function mapLocation(out, px, py, pz, from, to, m, vp, ndcZMin) {
|
|
965
|
-
|
|
966
|
-
if (from
|
|
967
|
-
|
|
968
|
-
if (from
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
if (from
|
|
973
|
-
|
|
974
|
-
if (from
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
return
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
// WORLD ↔ EYE
|
|
984
|
-
if (from === WORLD && to === EYE)
|
|
985
|
-
return mat4MulPoint(out, m.vMatrix, px,py,pz);
|
|
986
|
-
if (from === EYE && to === WORLD)
|
|
987
|
-
return mat4MulPoint(out, m.eMatrix, px,py,pz);
|
|
988
|
-
|
|
989
|
-
// EYE ↔ SCREEN
|
|
990
|
-
if (from === EYE && to === SCREEN) {
|
|
991
|
-
const e = m.eMatrix;
|
|
992
|
-
const ex=e[0]*px+e[4]*py+e[8]*pz+e[12],
|
|
993
|
-
ey=e[1]*px+e[5]*py+e[9]*pz+e[13],
|
|
994
|
-
ez=e[2]*px+e[6]*py+e[10]*pz+e[14];
|
|
995
|
-
return _worldToScreen(out, ex,ey,ez, _ensurePV(m), vp, ndcZMin);
|
|
863
|
+
if (from===WORLD && to===SCREEN) return _worldToScreen(out,px,py,pz,_ensurePV(m),vp,ndcZMin);
|
|
864
|
+
if (from===SCREEN && to===WORLD) return _screenToWorld(out,px,py,pz,m.mat4PVInv,vp,ndcZMin);
|
|
865
|
+
|
|
866
|
+
if (from===WORLD && to===NDC) return _worldToNDC(out,px,py,pz,_ensurePV(m));
|
|
867
|
+
if (from===NDC && to===WORLD) return _ndcToWorld(out,px,py,pz,m.mat4PVInv);
|
|
868
|
+
|
|
869
|
+
if (from===SCREEN && to===NDC) return _screenToNDC(out,px,py,pz,vp,ndcZMin);
|
|
870
|
+
if (from===NDC && to===SCREEN) return _ndcToScreen(out,px,py,pz,vp,ndcZMin);
|
|
871
|
+
|
|
872
|
+
if (from===WORLD && to===EYE) return mat4MulPoint(out,m.mat4View,px,py,pz);
|
|
873
|
+
if (from===EYE && to===WORLD) return mat4MulPoint(out,m.mat4Eye,px,py,pz);
|
|
874
|
+
|
|
875
|
+
if (from===EYE && to===SCREEN) {
|
|
876
|
+
const e=m.mat4Eye;
|
|
877
|
+
return _worldToScreen(out,e[0]*px+e[4]*py+e[8]*pz+e[12],
|
|
878
|
+
e[1]*px+e[5]*py+e[9]*pz+e[13],
|
|
879
|
+
e[2]*px+e[6]*py+e[10]*pz+e[14],_ensurePV(m),vp,ndcZMin);
|
|
996
880
|
}
|
|
997
|
-
if (from
|
|
998
|
-
_screenToWorld(out,
|
|
999
|
-
|
|
1000
|
-
return mat4MulPoint(out, m.vMatrix, wx,wy,wz);
|
|
881
|
+
if (from===SCREEN && to===EYE) {
|
|
882
|
+
_screenToWorld(out,px,py,pz,m.mat4PVInv,vp,ndcZMin);
|
|
883
|
+
return mat4MulPoint(out,m.mat4View,out[0],out[1],out[2]);
|
|
1001
884
|
}
|
|
1002
885
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
ez=e[2]*px+e[6]*py+e[10]*pz+e[14];
|
|
1009
|
-
return _worldToNDC(out, ex,ey,ez, _ensurePV(m));
|
|
886
|
+
if (from===EYE && to===NDC) {
|
|
887
|
+
const e=m.mat4Eye;
|
|
888
|
+
return _worldToNDC(out,e[0]*px+e[4]*py+e[8]*pz+e[12],
|
|
889
|
+
e[1]*px+e[5]*py+e[9]*pz+e[13],
|
|
890
|
+
e[2]*px+e[6]*py+e[10]*pz+e[14],_ensurePV(m));
|
|
1010
891
|
}
|
|
1011
|
-
if (from
|
|
1012
|
-
_ndcToWorld(out,
|
|
1013
|
-
|
|
1014
|
-
return mat4MulPoint(out, m.vMatrix, wx,wy,wz);
|
|
892
|
+
if (from===NDC && to===EYE) {
|
|
893
|
+
_ndcToWorld(out,px,py,pz,m.mat4PVInv);
|
|
894
|
+
return mat4MulPoint(out,m.mat4View,out[0],out[1],out[2]);
|
|
1015
895
|
}
|
|
1016
896
|
|
|
1017
|
-
|
|
1018
|
-
if (from
|
|
1019
|
-
|
|
1020
|
-
if (from
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
const f = m.fromFrame;
|
|
1026
|
-
const fx=f[0]*px+f[4]*py+f[8]*pz+f[12],
|
|
1027
|
-
fy=f[1]*px+f[5]*py+f[9]*pz+f[13],
|
|
1028
|
-
fz=f[2]*px+f[6]*py+f[10]*pz+f[14];
|
|
1029
|
-
return mat4MulPoint(out, m.vMatrix, fx,fy,fz);
|
|
897
|
+
if (from===MATRIX && to===WORLD) return mat4MulPoint(out,m.fromFrame,px,py,pz);
|
|
898
|
+
if (from===WORLD && to===MATRIX) return mat4MulPoint(out,m.toFrameInv,px,py,pz);
|
|
899
|
+
|
|
900
|
+
if (from===MATRIX && to===EYE) {
|
|
901
|
+
const f=m.fromFrame;
|
|
902
|
+
return mat4MulPoint(out,m.mat4View,f[0]*px+f[4]*py+f[8]*pz+f[12],
|
|
903
|
+
f[1]*px+f[5]*py+f[9]*pz+f[13],
|
|
904
|
+
f[2]*px+f[6]*py+f[10]*pz+f[14]);
|
|
1030
905
|
}
|
|
1031
|
-
if (from
|
|
1032
|
-
const e
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
return mat4MulPoint(out, m.toFrameInv, ex,ey,ez);
|
|
906
|
+
if (from===EYE && to===MATRIX) {
|
|
907
|
+
const e=m.mat4Eye;
|
|
908
|
+
return mat4MulPoint(out,m.toFrameInv,e[0]*px+e[4]*py+e[8]*pz+e[12],
|
|
909
|
+
e[1]*px+e[5]*py+e[9]*pz+e[13],
|
|
910
|
+
e[2]*px+e[6]*py+e[10]*pz+e[14]);
|
|
1037
911
|
}
|
|
1038
912
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
fz=f[2]*px+f[6]*py+f[10]*pz+f[14];
|
|
1045
|
-
return _worldToScreen(out, fx,fy,fz, _ensurePV(m), vp, ndcZMin);
|
|
913
|
+
if (from===MATRIX && to===SCREEN) {
|
|
914
|
+
const f=m.fromFrame;
|
|
915
|
+
return _worldToScreen(out,f[0]*px+f[4]*py+f[8]*pz+f[12],
|
|
916
|
+
f[1]*px+f[5]*py+f[9]*pz+f[13],
|
|
917
|
+
f[2]*px+f[6]*py+f[10]*pz+f[14],_ensurePV(m),vp,ndcZMin);
|
|
1046
918
|
}
|
|
1047
|
-
if (from
|
|
1048
|
-
_screenToWorld(out,
|
|
1049
|
-
|
|
1050
|
-
return mat4MulPoint(out, m.toFrameInv, wx,wy,wz);
|
|
919
|
+
if (from===SCREEN && to===MATRIX) {
|
|
920
|
+
_screenToWorld(out,px,py,pz,m.mat4PVInv,vp,ndcZMin);
|
|
921
|
+
return mat4MulPoint(out,m.toFrameInv,out[0],out[1],out[2]);
|
|
1051
922
|
}
|
|
1052
923
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
fz=f[2]*px+f[6]*py+f[10]*pz+f[14];
|
|
1059
|
-
return _worldToNDC(out, fx,fy,fz, _ensurePV(m));
|
|
924
|
+
if (from===MATRIX && to===NDC) {
|
|
925
|
+
const f=m.fromFrame;
|
|
926
|
+
return _worldToNDC(out,f[0]*px+f[4]*py+f[8]*pz+f[12],
|
|
927
|
+
f[1]*px+f[5]*py+f[9]*pz+f[13],
|
|
928
|
+
f[2]*px+f[6]*py+f[10]*pz+f[14],_ensurePV(m));
|
|
1060
929
|
}
|
|
1061
|
-
if (from
|
|
1062
|
-
_ndcToWorld(out,
|
|
1063
|
-
|
|
1064
|
-
return mat4MulPoint(out, m.toFrameInv, wx,wy,wz);
|
|
930
|
+
if (from===NDC && to===MATRIX) {
|
|
931
|
+
_ndcToWorld(out,px,py,pz,m.mat4PVInv);
|
|
932
|
+
return mat4MulPoint(out,m.toFrameInv,out[0],out[1],out[2]);
|
|
1065
933
|
}
|
|
1066
934
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
fz=f[2]*px+f[6]*py+f[10]*pz+f[14];
|
|
1073
|
-
return mat4MulPoint(out, m.toFrameInv, fx,fy,fz);
|
|
935
|
+
if (from===MATRIX && to===MATRIX) {
|
|
936
|
+
const f=m.fromFrame;
|
|
937
|
+
return mat4MulPoint(out,m.toFrameInv,f[0]*px+f[4]*py+f[8]*pz+f[12],
|
|
938
|
+
f[1]*px+f[5]*py+f[9]*pz+f[13],
|
|
939
|
+
f[2]*px+f[6]*py+f[10]*pz+f[14]);
|
|
1074
940
|
}
|
|
1075
941
|
|
|
1076
|
-
// Fallback
|
|
1077
942
|
out[0]=px; out[1]=py; out[2]=pz;
|
|
1078
943
|
return out;
|
|
1079
944
|
}
|
|
1080
945
|
|
|
1081
|
-
// ── Direction
|
|
946
|
+
// ── Direction helpers ────────────────────────────────────────────────────
|
|
947
|
+
//
|
|
948
|
+
// Directions use only the linear 3×3 block — no translation, no w-divide.
|
|
949
|
+
// The signed vp[2]/vp[3] carries the y-convention automatically.
|
|
950
|
+
//
|
|
1082
951
|
|
|
1083
|
-
/** Apply the 3×3 linear part of a mat4 (rotation/scale, no translation). */
|
|
1084
952
|
function _applyDir(out, m, dx, dy, dz) {
|
|
1085
953
|
out[0]=m[0]*dx+m[4]*dy+m[8]*dz;
|
|
1086
954
|
out[1]=m[1]*dx+m[5]*dy+m[9]*dz;
|
|
@@ -1089,157 +957,123 @@ function _applyDir(out, m, dx, dy, dz) {
|
|
|
1089
957
|
}
|
|
1090
958
|
|
|
1091
959
|
function _worldToScreenDir(out, dx, dy, dz, proj, view, vpW, vpH, ndcZMin) {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
// NDC→screen scale (direction, no offset).
|
|
1100
|
-
out[0]=cx*vpW*0.5; out[1]=-cy*vpH*0.5;
|
|
1101
|
-
out[2]=cz*(1-ndcZMin)*0.5;
|
|
960
|
+
const vx=view[0]*dx+view[4]*dy+view[8]*dz,
|
|
961
|
+
vy=view[1]*dx+view[5]*dy+view[9]*dz,
|
|
962
|
+
vz=view[2]*dx+view[6]*dy+view[10]*dz;
|
|
963
|
+
// vpH is signed — negative flips y component automatically.
|
|
964
|
+
out[0]=(proj[0]*vx+proj[4]*vy+proj[8]*vz)*vpW*0.5;
|
|
965
|
+
out[1]=(proj[1]*vx+proj[5]*vy+proj[9]*vz)*vpH*0.5;
|
|
966
|
+
out[2]=(proj[2]*vx+proj[6]*vy+proj[10]*vz)*(1-ndcZMin)*0.5;
|
|
1102
967
|
return out;
|
|
1103
968
|
}
|
|
1104
969
|
|
|
1105
|
-
function _screenToWorldDir(out, dx, dy, dz, proj,
|
|
1106
|
-
//
|
|
1107
|
-
|
|
1108
|
-
const nz=dz/((1-ndcZMin)*0.5);
|
|
1109
|
-
// NDC direction → eye direction (inverse projection, linear only).
|
|
1110
|
-
const ex=nx/proj[0], ey=ny/proj[5], ez=nz;
|
|
1111
|
-
// Eye direction → world direction.
|
|
1112
|
-
_applyDir(out, eMatrix, ex, ey, ez);
|
|
970
|
+
function _screenToWorldDir(out, dx, dy, dz, proj, eye, vpW, vpH, ndcZMin) {
|
|
971
|
+
// Inverse of _worldToScreenDir; signed vpW/vpH cancel the y-flip.
|
|
972
|
+
_applyDir(out, eye, dx/(vpW*0.5)/proj[0], dy/(vpH*0.5)/proj[5], dz/((1-ndcZMin)*0.5));
|
|
1113
973
|
return out;
|
|
1114
974
|
}
|
|
1115
975
|
|
|
1116
976
|
function _screenToNDCDir(out, dx, dy, dz, vpW, vpH, ndcZMin) {
|
|
1117
|
-
out[0]=dx/(vpW*0.5); out[1]
|
|
1118
|
-
out[2]=dz/((1-ndcZMin)*0.5);
|
|
977
|
+
out[0]=dx/(vpW*0.5); out[1]=dy/(vpH*0.5); out[2]=dz/((1-ndcZMin)*0.5);
|
|
1119
978
|
return out;
|
|
1120
979
|
}
|
|
1121
980
|
|
|
1122
981
|
function _ndcToScreenDir(out, dx, dy, dz, vpW, vpH, ndcZMin) {
|
|
1123
|
-
out[0]=dx*vpW*0.5; out[1]
|
|
1124
|
-
out[2]=dz*(1-ndcZMin)*0.5;
|
|
982
|
+
out[0]=dx*vpW*0.5; out[1]=dy*vpH*0.5; out[2]=dz*(1-ndcZMin)*0.5;
|
|
1125
983
|
return out;
|
|
1126
984
|
}
|
|
1127
985
|
|
|
1128
986
|
/**
|
|
1129
987
|
* Map a direction between named coordinate spaces.
|
|
1130
|
-
* Same bag contract as mapLocation.
|
|
988
|
+
* Same bag and viewport contract as mapLocation.
|
|
989
|
+
*
|
|
990
|
+
* @param {number[]} out 3-element destination.
|
|
991
|
+
* @param {number} dx,dy,dz Input direction.
|
|
992
|
+
* @param {string} from Source space.
|
|
993
|
+
* @param {string} to Destination space.
|
|
994
|
+
* @param {object} m Matrices bag — see module header.
|
|
995
|
+
* @param {number[]} vp Viewport [x, y, w, h]; sign of h encodes screen-y direction.
|
|
996
|
+
* @param {number} ndcZMin WEBGL (−1) or WEBGPU (0).
|
|
997
|
+
* @returns {number[]} out
|
|
1131
998
|
*/
|
|
1132
999
|
function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
1133
|
-
const vpW
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
if (from
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
return
|
|
1142
|
-
if (from
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
return _screenToNDCDir(out, dx,dy,dz, vpW, vpH, ndcZMin);
|
|
1148
|
-
if (from === NDC && to === SCREEN)
|
|
1149
|
-
return _ndcToScreenDir(out, dx,dy,dz, vpW, vpH, ndcZMin);
|
|
1150
|
-
|
|
1151
|
-
// WORLD ↔ NDC
|
|
1152
|
-
if (from === WORLD && to === NDC) {
|
|
1153
|
-
_worldToScreenDir(out, dx,dy,dz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
1154
|
-
const sx=out[0],sy=out[1],sz=out[2];
|
|
1155
|
-
return _screenToNDCDir(out, sx,sy,sz, vpW, vpH, ndcZMin);
|
|
1000
|
+
const vpW=vp[2], vpH=vp[3]; // signed — carry y-convention through all helpers
|
|
1001
|
+
|
|
1002
|
+
if (from===EYE && to===WORLD) return _applyDir(out,m.mat4Eye, dx,dy,dz);
|
|
1003
|
+
if (from===WORLD && to===EYE) return _applyDir(out,m.mat4View,dx,dy,dz);
|
|
1004
|
+
|
|
1005
|
+
if (from===WORLD && to===SCREEN) return _worldToScreenDir(out,dx,dy,dz,m.mat4Proj,m.mat4View,vpW,vpH,ndcZMin);
|
|
1006
|
+
if (from===SCREEN && to===WORLD) return _screenToWorldDir(out,dx,dy,dz,m.mat4Proj,m.mat4Eye, vpW,vpH,ndcZMin);
|
|
1007
|
+
|
|
1008
|
+
if (from===SCREEN && to===NDC) return _screenToNDCDir(out,dx,dy,dz,vpW,vpH,ndcZMin);
|
|
1009
|
+
if (from===NDC && to===SCREEN) return _ndcToScreenDir(out,dx,dy,dz,vpW,vpH,ndcZMin);
|
|
1010
|
+
|
|
1011
|
+
if (from===WORLD && to===NDC) {
|
|
1012
|
+
_worldToScreenDir(out,dx,dy,dz,m.mat4Proj,m.mat4View,vpW,vpH,ndcZMin);
|
|
1013
|
+
return _screenToNDCDir(out,out[0],out[1],out[2],vpW,vpH,ndcZMin);
|
|
1156
1014
|
}
|
|
1157
|
-
if (from
|
|
1158
|
-
_ndcToScreenDir(out,
|
|
1159
|
-
|
|
1160
|
-
return _screenToWorldDir(out, sx,sy,sz, m.pMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
1015
|
+
if (from===NDC && to===WORLD) {
|
|
1016
|
+
_ndcToScreenDir(out,dx,dy,dz,vpW,vpH,ndcZMin);
|
|
1017
|
+
return _screenToWorldDir(out,out[0],out[1],out[2],m.mat4Proj,m.mat4Eye,vpW,vpH,ndcZMin);
|
|
1161
1018
|
}
|
|
1162
1019
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
const wx=out[0],wy=out[1],wz=out[2];
|
|
1167
|
-
return _worldToScreenDir(out, wx,wy,wz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
1020
|
+
if (from===EYE && to===SCREEN) {
|
|
1021
|
+
_applyDir(out,m.mat4Eye,dx,dy,dz);
|
|
1022
|
+
return _worldToScreenDir(out,out[0],out[1],out[2],m.mat4Proj,m.mat4View,vpW,vpH,ndcZMin);
|
|
1168
1023
|
}
|
|
1169
|
-
if (from
|
|
1170
|
-
_screenToWorldDir(out,
|
|
1171
|
-
|
|
1172
|
-
return _applyDir(out, m.vMatrix, wx,wy,wz);
|
|
1024
|
+
if (from===SCREEN && to===EYE) {
|
|
1025
|
+
_screenToWorldDir(out,dx,dy,dz,m.mat4Proj,m.mat4Eye,vpW,vpH,ndcZMin);
|
|
1026
|
+
return _applyDir(out,m.mat4View,out[0],out[1],out[2]);
|
|
1173
1027
|
}
|
|
1174
1028
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
_worldToScreenDir(out, wx,wy,wz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
1180
|
-
const sx=out[0],sy=out[1],sz=out[2];
|
|
1181
|
-
return _screenToNDCDir(out, sx,sy,sz, vpW, vpH, ndcZMin);
|
|
1029
|
+
if (from===EYE && to===NDC) {
|
|
1030
|
+
_applyDir(out,m.mat4Eye,dx,dy,dz);
|
|
1031
|
+
_worldToScreenDir(out,out[0],out[1],out[2],m.mat4Proj,m.mat4View,vpW,vpH,ndcZMin);
|
|
1032
|
+
return _screenToNDCDir(out,out[0],out[1],out[2],vpW,vpH,ndcZMin);
|
|
1182
1033
|
}
|
|
1183
|
-
if (from
|
|
1184
|
-
_ndcToScreenDir(out,
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
const wx=out[0],wy=out[1],wz=out[2];
|
|
1188
|
-
return _applyDir(out, m.vMatrix, wx,wy,wz);
|
|
1034
|
+
if (from===NDC && to===EYE) {
|
|
1035
|
+
_ndcToScreenDir(out,dx,dy,dz,vpW,vpH,ndcZMin);
|
|
1036
|
+
_screenToWorldDir(out,out[0],out[1],out[2],m.mat4Proj,m.mat4Eye,vpW,vpH,ndcZMin);
|
|
1037
|
+
return _applyDir(out,m.mat4View,out[0],out[1],out[2]);
|
|
1189
1038
|
}
|
|
1190
1039
|
|
|
1191
|
-
|
|
1192
|
-
if (from
|
|
1193
|
-
if (from === WORLD && to === MATRIX) return _applyDir(out, m.toFrameInv, dx,dy,dz);
|
|
1040
|
+
if (from===MATRIX && to===WORLD) return _applyDir(out,m.fromFrame, dx,dy,dz);
|
|
1041
|
+
if (from===WORLD && to===MATRIX) return _applyDir(out,m.toFrameInv,dx,dy,dz);
|
|
1194
1042
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
_applyDir(out,
|
|
1198
|
-
const wx=out[0],wy=out[1],wz=out[2];
|
|
1199
|
-
return _applyDir(out, m.vMatrix, wx,wy,wz);
|
|
1043
|
+
if (from===MATRIX && to===EYE) {
|
|
1044
|
+
_applyDir(out,m.fromFrame,dx,dy,dz);
|
|
1045
|
+
return _applyDir(out,m.mat4View,out[0],out[1],out[2]);
|
|
1200
1046
|
}
|
|
1201
|
-
if (from
|
|
1202
|
-
_applyDir(out,
|
|
1203
|
-
|
|
1204
|
-
return _applyDir(out, m.toFrameInv, wx,wy,wz);
|
|
1047
|
+
if (from===EYE && to===MATRIX) {
|
|
1048
|
+
_applyDir(out,m.mat4Eye,dx,dy,dz);
|
|
1049
|
+
return _applyDir(out,m.toFrameInv,out[0],out[1],out[2]);
|
|
1205
1050
|
}
|
|
1206
1051
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
const wx=out[0],wy=out[1],wz=out[2];
|
|
1211
|
-
return _worldToScreenDir(out, wx,wy,wz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
1052
|
+
if (from===MATRIX && to===SCREEN) {
|
|
1053
|
+
_applyDir(out,m.fromFrame,dx,dy,dz);
|
|
1054
|
+
return _worldToScreenDir(out,out[0],out[1],out[2],m.mat4Proj,m.mat4View,vpW,vpH,ndcZMin);
|
|
1212
1055
|
}
|
|
1213
|
-
if (from
|
|
1214
|
-
_screenToWorldDir(out,
|
|
1215
|
-
|
|
1216
|
-
return _applyDir(out, m.toFrameInv, wx,wy,wz);
|
|
1056
|
+
if (from===SCREEN && to===MATRIX) {
|
|
1057
|
+
_screenToWorldDir(out,dx,dy,dz,m.mat4Proj,m.mat4Eye,vpW,vpH,ndcZMin);
|
|
1058
|
+
return _applyDir(out,m.toFrameInv,out[0],out[1],out[2]);
|
|
1217
1059
|
}
|
|
1218
1060
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
_worldToScreenDir(out, wx,wy,wz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
1224
|
-
const sx=out[0],sy=out[1],sz=out[2];
|
|
1225
|
-
return _screenToNDCDir(out, sx,sy,sz, vpW, vpH, ndcZMin);
|
|
1061
|
+
if (from===MATRIX && to===NDC) {
|
|
1062
|
+
_applyDir(out,m.fromFrame,dx,dy,dz);
|
|
1063
|
+
_worldToScreenDir(out,out[0],out[1],out[2],m.mat4Proj,m.mat4View,vpW,vpH,ndcZMin);
|
|
1064
|
+
return _screenToNDCDir(out,out[0],out[1],out[2],vpW,vpH,ndcZMin);
|
|
1226
1065
|
}
|
|
1227
|
-
if (from
|
|
1228
|
-
_ndcToScreenDir(out,
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const wx=out[0],wy=out[1],wz=out[2];
|
|
1232
|
-
return _applyDir(out, m.toFrameInv, wx,wy,wz);
|
|
1066
|
+
if (from===NDC && to===MATRIX) {
|
|
1067
|
+
_ndcToScreenDir(out,dx,dy,dz,vpW,vpH,ndcZMin);
|
|
1068
|
+
_screenToWorldDir(out,out[0],out[1],out[2],m.mat4Proj,m.mat4Eye,vpW,vpH,ndcZMin);
|
|
1069
|
+
return _applyDir(out,m.toFrameInv,out[0],out[1],out[2]);
|
|
1233
1070
|
}
|
|
1234
1071
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
_applyDir(out,
|
|
1238
|
-
const wx=out[0],wy=out[1],wz=out[2];
|
|
1239
|
-
return _applyDir(out, m.toFrameInv, wx,wy,wz);
|
|
1072
|
+
if (from===MATRIX && to===MATRIX) {
|
|
1073
|
+
_applyDir(out,m.fromFrame,dx,dy,dz);
|
|
1074
|
+
return _applyDir(out,m.toFrameInv,out[0],out[1],out[2]);
|
|
1240
1075
|
}
|
|
1241
1076
|
|
|
1242
|
-
// Fallback
|
|
1243
1077
|
out[0]=dx; out[1]=dy; out[2]=dz;
|
|
1244
1078
|
return out;
|
|
1245
1079
|
}
|
|
@@ -1250,16 +1084,15 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
1250
1084
|
|
|
1251
1085
|
/**
|
|
1252
1086
|
* World-units-per-pixel at a given eye-space Z depth.
|
|
1253
|
-
* @param {ArrayLike<number>} proj
|
|
1254
|
-
* @param {number}
|
|
1255
|
-
* @param {number}
|
|
1256
|
-
* @param {number}
|
|
1087
|
+
* @param {ArrayLike<number>} proj Projection mat4.
|
|
1088
|
+
* @param {number} vpH Viewport height in pixels (positive).
|
|
1089
|
+
* @param {number} eyeZ Eye-space Z — negative means in front of camera.
|
|
1090
|
+
* @param {number} ndcZMin WEBGL (−1) or WEBGPU (0).
|
|
1257
1091
|
*/
|
|
1258
1092
|
function pixelRatio(proj, vpH, eyeZ, ndcZMin) {
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
return 2 * Math.abs(eyeZ) * Math.tan(projFov(proj) / 2) / vpH;
|
|
1093
|
+
return projIsOrtho(proj)
|
|
1094
|
+
? Math.abs(projTop(proj,ndcZMin)-projBottom(proj,ndcZMin)) / vpH
|
|
1095
|
+
: 2*Math.abs(eyeZ)*Math.tan(projFov(proj)/2) / vpH;
|
|
1263
1096
|
}
|
|
1264
1097
|
|
|
1265
1098
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -1267,40 +1100,35 @@ function pixelRatio(proj, vpH, eyeZ, ndcZMin) {
|
|
|
1267
1100
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1268
1101
|
|
|
1269
1102
|
/**
|
|
1270
|
-
*
|
|
1103
|
+
* Mutate a projection matrix in-place so that the pixel at (px, py) maps to
|
|
1104
|
+
* the full NDC square — making a 1×1 FBO render contain exactly that pixel.
|
|
1105
|
+
*
|
|
1106
|
+
* Premultiplies by M_pick (column-major, rows 2 and 3 unchanged):
|
|
1271
1107
|
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
1108
|
+
* ┌ sx 0 0 tx ┐ sx = |vp[2]|, sy = |vp[3]|
|
|
1109
|
+
* │ 0 sy 0 ty │ cx = ((px−vp[0])/vp[2])·2 − 1 (NDC x of pixel centre)
|
|
1110
|
+
* │ 0 0 1 0 │ cy = ((py−vp[1])/vp[3])·2 − 1 (NDC y, sign-aware)
|
|
1111
|
+
* └ 0 0 0 1 ┘ tx = −cx·sx, ty = −cy·sy
|
|
1275
1112
|
*
|
|
1276
|
-
* M_pick
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1279
|
-
* [ 0 0 1 0 ] cy = NDC Y of pixel centre = 1 − 2*(py+0.5)/H
|
|
1280
|
-
* [ 0 0 0 1 ] tx = −cx·W, ty = −cy·H
|
|
1113
|
+
* Result: P_pick = M_pick · P_original.
|
|
1114
|
+
* The viewport sign convention (vp[3] < 0 for screen y-down) is preserved
|
|
1115
|
+
* automatically through cx/cy — no separate flip needed.
|
|
1281
1116
|
*
|
|
1282
1117
|
* @param {Float32Array} proj Projection mat4 — mutated in place.
|
|
1283
|
-
* @param {number} px Query X
|
|
1284
|
-
* @param {number} py Query Y
|
|
1285
|
-
* @param {number}
|
|
1286
|
-
* @param {number} H Canvas height (CSS pixels).
|
|
1287
|
-
* @returns {Float32Array} proj (same reference)
|
|
1118
|
+
* @param {number} px Query pixel X in screen coordinates.
|
|
1119
|
+
* @param {number} py Query pixel Y in screen coordinates.
|
|
1120
|
+
* @param {number[]} vp Viewport [x, y, w, h]; same signed convention as mapLocation.
|
|
1288
1121
|
*/
|
|
1289
|
-
function mat4Pick(proj, px, py,
|
|
1290
|
-
const cx
|
|
1291
|
-
const cy
|
|
1292
|
-
const sx =
|
|
1293
|
-
const sy
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
const b = proj[j * 4 + 1];
|
|
1299
|
-
const d = proj[j * 4 + 3];
|
|
1300
|
-
proj[j * 4] = sx * a + tx * d;
|
|
1301
|
-
proj[j * 4 + 1] = sy * b + ty * d;
|
|
1122
|
+
function mat4Pick(proj, px, py, vp) {
|
|
1123
|
+
const cx=((px-vp[0])/vp[2])*2-1;
|
|
1124
|
+
const cy=((py-vp[1])/vp[3])*2-1;
|
|
1125
|
+
const sx=Math.abs(vp[2]), sy=Math.abs(vp[3]);
|
|
1126
|
+
const tx=-cx*sx, ty=-cy*sy;
|
|
1127
|
+
for (let j=0; j<4; j++) {
|
|
1128
|
+
const a=proj[j*4], b=proj[j*4+1], d=proj[j*4+3];
|
|
1129
|
+
proj[j*4] = sx*a + tx*d;
|
|
1130
|
+
proj[j*4+1] = sy*b + ty*d;
|
|
1302
1131
|
}
|
|
1303
|
-
return proj;
|
|
1304
1132
|
}
|
|
1305
1133
|
|
|
1306
1134
|
/**
|
|
@@ -2445,5 +2273,5 @@ function boxVisibility(planes, x0, y0, z0, x1, y1, z1) {
|
|
|
2445
2273
|
return allIn ? VISIBLE : SEMIVISIBLE;
|
|
2446
2274
|
}
|
|
2447
2275
|
|
|
2448
|
-
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, distanceToPlane, frustumPlanes, hermiteVec3, i, j, k, lerpVec3, mapDirection, mapLocation, mat3Direction, mat3NormalFromMat4, mat4Bias,
|
|
2276
|
+
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, distanceToPlane, frustumPlanes, hermiteVec3, i, j, k, lerpVec3, mapDirection, mapLocation, mat3Direction, mat3NormalFromMat4, mat4Bias, mat4Eye, mat4FromBasis, mat4FromScale, mat4FromTRS, mat4FromTranslation, mat4Frustum, mat4Invert, mat4Location, mat4MV, mat4Mul, mat4MulDir, mat4MulPoint, mat4Ortho, mat4PV, mat4Perspective, mat4Pick, mat4Reflect, mat4ToRotation, mat4ToScale, mat4ToTransform, mat4ToTranslation, mat4View, 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 };
|
|
2449
2277
|
//# sourceMappingURL=index.js.map
|