@nakednous/tree 0.0.15 → 0.0.17

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 CHANGED
@@ -82,10 +82,10 @@ Playback features: signed `rate` (negative reverses), `loop`, `bounce`, `seek(t)
82
82
  `add()` accepts flexible specs. Top-level forms:
83
83
 
84
84
  ```js
85
- track.add({ pos, rot, scl }) // explicit TRS — rot accepts any form below
86
- track.add({ pos, rot, scl, tanIn, tanOut }) // with Hermite tangents (vec3, optional)
87
- track.add({ mMatrix: mat4 }) // decompose a column-major model matrix into TRS
88
- track.add([ spec, spec, ... ]) // bulk
85
+ track.add({ pos, rot, scl }) // explicit TRS — rot accepts any form below
86
+ track.add({ pos, rot, scl, tanIn, tanOut }) // with Hermite tangents (vec3, optional)
87
+ track.add({ mat4Model: mat4 }) // decompose a column-major model matrix into TRS
88
+ track.add([ spec, spec, ... ]) // bulk
89
89
  ```
90
90
 
91
91
  `tanIn` is the incoming position tangent at this keyframe; `tanOut` is the outgoing tangent. When only one is given, the other mirrors it. When neither is given, centripetal Catmull-Rom tangents are auto-computed from neighboring keyframes.
@@ -100,16 +100,16 @@ track.add({ pos:[300,0,0] }) // auto tangents
100
100
  `rot` sub-forms — all normalised internally:
101
101
 
102
102
  ```js
103
- rot: [x,y,z,w] // raw quaternion
104
- rot: { axis:[x,y,z], angle } // axis-angle
105
- rot: { dir:[x,y,z], up?:[x,y,z] } // look direction (−Z forward)
106
- rot: { euler:[rx,ry,rz], order?:'YXZ' } // intrinsic Euler angles (radians)
107
- // orders: YXZ (default), XYZ, ZYX,
108
- // ZXY, XZY, YZX
109
- // extrinsic ABC = intrinsic CBA
110
- rot: { from:[x,y,z], to:[x,y,z] } // shortest-arc between directions
111
- rot: { mat3: Float32Array|Array } // column-major 3×3 rotation matrix
112
- rot: { eMatrix: mat4 } // rotation block of an eye matrix
103
+ rot: [x,y,z,w] // raw quaternion
104
+ rot: { axis:[x,y,z], angle } // axis-angle
105
+ rot: { dir:[x,y,z], up?:[x,y,z] } // look direction (−Z forward)
106
+ rot: { euler:[rx,ry,rz], order?:'YXZ' } // intrinsic Euler angles (radians)
107
+ // orders: YXZ (default), XYZ, ZYX,
108
+ // ZXY, XZY, YZX
109
+ // extrinsic ABC = intrinsic CBA
110
+ rot: { from:[x,y,z], to:[x,y,z] } // shortest-arc between directions
111
+ rot: { mat3: Float32Array|Array } // column-major 3×3 rotation matrix
112
+ rot: { mat4Eye: mat4 } // rotation block of an eye matrix
113
113
  ```
114
114
 
115
115
  ---
@@ -160,7 +160,7 @@ track.add({ eye, center?, up?, fov?, halfHeight?,
160
160
  track.add([ spec, spec, ... ]) // bulk
161
161
  ```
162
162
 
163
- For matrix-based capture use `PoseTrack.add({ mMatrix: eMatrix })` for full-fidelity TRS including roll, or `cam.capturePose()` (p5.tree bridge) for lookat-style capture.
163
+ For matrix-based capture use `track.add({ mat4Model: mat4Eye })` for full-fidelity TRS including roll, or `cam.capturePose()` (p5.tree bridge) for lookat-style capture.
164
164
 
165
165
  `fov` and `halfHeight` are lerped between keyframes only when both adjacent keyframes carry a non-null value for that field. Mixed or null entries pass `null` through — the bridge leaves the projection unchanged.
166
166
 
@@ -284,6 +284,8 @@ pointVisibility(planes, px, py, pz)
284
284
 
285
285
  Three-state result: `VISIBLE` (fully inside), `SEMIVISIBLE` (intersecting), `INVISIBLE` (fully outside).
286
286
 
287
+ **Sign contract:** `top > 0`, `bottom < 0`, `right > 0`, `left < 0` for standard y-up camera.
288
+
287
289
  ---
288
290
 
289
291
  ### Quaternion and matrix math
@@ -323,6 +325,10 @@ mat4Ortho — orthographic projection (ndcZMin, ndcYSign)
323
325
  mat4Frustum — off-centre perspective (ndcZMin, ndcYSign)
324
326
  mat4Bias — NDC→texture/UV remap [0,1] for shadow mapping
325
327
  mat4Reflect — reflection across a plane
328
+ ```
329
+
330
+ **Mat4 decomposition** (`query.js`):
331
+ ```
326
332
  mat4ToTranslation — extract translation (col 3)
327
333
  mat4ToScale — extract scale (column lengths)
328
334
  mat4ToRotation — extract rotation as unit quaternion
package/dist/index.js CHANGED
@@ -346,7 +346,7 @@ function mat4Eye(out, ex,ey,ez, cx,cy,cz, ux,uy,uz) {
346
346
  *
347
347
  * @param {Float32Array|number[]} out 16-element destination.
348
348
  * @param {number} tx,ty,tz Translation.
349
- * @param {number} qx,qy,qz,qw Rotation quaternion [x,y,z,w].
349
+ * @param {number} qx,qy,qz,qw Rotation quaternion [x,y,z,w].
350
350
  * @param {number} sx,sy,sz Scale.
351
351
  */
352
352
  function mat4FromTRS(out, tx,ty,tz, qx,qy,qz,qw, sx,sy,sz) {
@@ -503,49 +503,6 @@ function mat4Reflect(out, nx,ny,nz,d) {
503
503
  return out;
504
504
  }
505
505
 
506
- // =========================================================================
507
- // Partial decomposition (mat4To___ mirrors mat4From___)
508
- // =========================================================================
509
-
510
- /**
511
- * Extract translation from a column-major mat4 (column 3).
512
- * @param {Float32Array|number[]} out3 3-element destination.
513
- * @param {Float32Array|number[]} m 16-element source.
514
- */
515
- function mat4ToTranslation(out3, m) {
516
- out3[0]=m[12]; out3[1]=m[13]; out3[2]=m[14];
517
- return out3;
518
- }
519
-
520
- /**
521
- * Extract scale from a column-major mat4 (column lengths of the rotation block).
522
- * Assumes no shear.
523
- * @param {Float32Array|number[]} out3 3-element destination.
524
- * @param {Float32Array|number[]} m 16-element source.
525
- */
526
- function mat4ToScale(out3, m) {
527
- out3[0]=Math.sqrt(m[0]*m[0]+m[1]*m[1]+m[2]*m[2]);
528
- out3[1]=Math.sqrt(m[4]*m[4]+m[5]*m[5]+m[6]*m[6]);
529
- out3[2]=Math.sqrt(m[8]*m[8]+m[9]*m[9]+m[10]*m[10]);
530
- return out3;
531
- }
532
-
533
- /**
534
- * Extract rotation as a unit quaternion from a column-major mat4.
535
- * Scale is factored out from each column. Assumes no shear.
536
- * @param {number[]} out4 4-element [x,y,z,w] destination.
537
- * @param {Float32Array|number[]} m 16-element source.
538
- */
539
- function mat4ToRotation(out4, m) {
540
- const sx=Math.sqrt(m[0]*m[0]+m[1]*m[1]+m[2]*m[2])||1;
541
- const sy=Math.sqrt(m[4]*m[4]+m[5]*m[5]+m[6]*m[6])||1;
542
- const sz=Math.sqrt(m[8]*m[8]+m[9]*m[9]+m[10]*m[10])||1;
543
- return qFromRotMat3x3(out4,
544
- m[0]/sx, m[4]/sy, m[8]/sz,
545
- m[1]/sx, m[5]/sy, m[9]/sz,
546
- m[2]/sx, m[6]/sy, m[10]/sz);
547
- }
548
-
549
506
  /**
550
507
  * @file Matrix arithmetic, space-transform dispatch, and projection queries.
551
508
  * @module tree/query
@@ -763,10 +720,30 @@ function mat4MV(out, model, view) { return mat4Mul(out, view, model); }
763
720
 
764
721
  /**
765
722
  * Location transform between frames: out = inv(to) · from.
723
+ * Assumes both matrices are affine (bottom row [0,0,0,1]).
766
724
  * @returns {ArrayLike<number>|null} out, or null if `to` is singular.
767
725
  */
768
726
  function mat4Location(out, from, to) {
769
- return mat4Invert(out, to) && mat4Mul(out, out, from);
727
+ // Same as: return mat4Invert(out, to) && mat4Mul(out, out, from);
728
+ const a00=to[0],a01=to[1],a02=to[2],
729
+ a10=to[4],a11=to[5],a12=to[6],
730
+ a20=to[8],a21=to[9],a22=to[10];
731
+ const b01=a22*a11-a12*a21, b11=a12*a20-a22*a10, b21=a21*a10-a11*a20;
732
+ let det=a00*b01+a01*b11+a02*b21;
733
+ if (Math.abs(det) < 1e-12) return null;
734
+ det=1/det;
735
+ const i00=b01*det, i01=(a02*a21-a22*a01)*det, i02=(a12*a01-a02*a11)*det;
736
+ const i10=b11*det, i11=(a22*a00-a02*a20)*det, i12=(a02*a10-a12*a00)*det;
737
+ const i20=b21*det, i21=(a01*a20-a21*a00)*det, i22=(a11*a00-a01*a10)*det;
738
+ const f00=from[0],f01=from[1],f02=from[2],
739
+ f10=from[4],f11=from[5],f12=from[6],
740
+ f20=from[8],f21=from[9],f22=from[10];
741
+ out[0]=i00*f00+i01*f01+i02*f02; out[1]=i10*f00+i11*f01+i12*f02; out[2]=i20*f00+i21*f01+i22*f02; out[3]=0;
742
+ out[4]=i00*f10+i01*f11+i02*f12; out[5]=i10*f10+i11*f11+i12*f12; out[6]=i20*f10+i21*f11+i22*f12; out[7]=0;
743
+ out[8]=i00*f20+i01*f21+i02*f22; out[9]=i10*f20+i11*f21+i12*f22; out[10]=i20*f20+i21*f21+i22*f22; out[11]=0;
744
+ const dx=from[12]-to[12], dy=from[13]-to[13], dz=from[14]-to[14];
745
+ out[12]=i00*dx+i01*dy+i02*dz; out[13]=i10*dx+i11*dy+i12*dz; out[14]=i20*dx+i21*dy+i22*dz; out[15]=1;
746
+ return out;
770
747
  }
771
748
 
772
749
  /**
@@ -1149,6 +1126,49 @@ function mat4Pick(proj, px, py, vp) {
1149
1126
  }
1150
1127
  }
1151
1128
 
1129
+ // =========================================================================
1130
+ // Decomposition
1131
+ // =========================================================================
1132
+
1133
+ /**
1134
+ * Extract translation from a column-major mat4 (column 3).
1135
+ * @param {Float32Array|number[]} out3 3-element destination.
1136
+ * @param {Float32Array|number[]} m 16-element source.
1137
+ */
1138
+ function mat4ToTranslation(out3, m) {
1139
+ out3[0]=m[12]; out3[1]=m[13]; out3[2]=m[14];
1140
+ return out3;
1141
+ }
1142
+
1143
+ /**
1144
+ * Extract scale from a column-major mat4 (column lengths of the rotation block).
1145
+ * Assumes no shear.
1146
+ * @param {Float32Array|number[]} out3 3-element destination.
1147
+ * @param {Float32Array|number[]} m 16-element source.
1148
+ */
1149
+ function mat4ToScale(out3, m) {
1150
+ out3[0]=Math.sqrt(m[0]*m[0]+m[1]*m[1]+m[2]*m[2]);
1151
+ out3[1]=Math.sqrt(m[4]*m[4]+m[5]*m[5]+m[6]*m[6]);
1152
+ out3[2]=Math.sqrt(m[8]*m[8]+m[9]*m[9]+m[10]*m[10]);
1153
+ return out3;
1154
+ }
1155
+
1156
+ /**
1157
+ * Extract rotation as a unit quaternion from a column-major mat4.
1158
+ * Scale is factored out from each column. Assumes no shear.
1159
+ * @param {number[]} out4 4-element [x,y,z,w] destination.
1160
+ * @param {Float32Array|number[]} m 16-element source.
1161
+ */
1162
+ function mat4ToRotation(out4, m) {
1163
+ const sx=Math.sqrt(m[0]*m[0]+m[1]*m[1]+m[2]*m[2])||1;
1164
+ const sy=Math.sqrt(m[4]*m[4]+m[5]*m[5]+m[6]*m[6])||1;
1165
+ const sz=Math.sqrt(m[8]*m[8]+m[9]*m[9]+m[10]*m[10])||1;
1166
+ return qFromRotMat3x3(out4,
1167
+ m[0]/sx, m[4]/sy, m[8]/sz,
1168
+ m[1]/sx, m[5]/sy, m[9]/sz,
1169
+ m[2]/sx, m[6]/sy, m[10]/sz);
1170
+ }
1171
+
1152
1172
  /**
1153
1173
  * @file Spline math and keyframe animation state machines.
1154
1174
  * @module tree/track
@@ -1357,11 +1377,11 @@ const _EULER_ORDERS = new Set(['XYZ','XZY','YXZ','YZX','ZXY','ZYX']);
1357
1377
  *
1358
1378
  * [x,y,z,w] — raw quaternion array
1359
1379
  * { axis:[x,y,z], angle } — axis-angle
1360
- * { dir:[x,y,z], up?:[x,y,z] } — forward direction (−Z) with optional up
1361
- * { eMatrix: mat4 } — rotation block of an eye matrix
1380
+ * { dir:[x,y,z], up?:[x,y,z] } — forward direction (−Z) with optional up
1381
+ * { mat4Eye: mat4 } — rotation block of an eye matrix
1362
1382
  * { mat3: mat3 } — column-major 3×3 rotation matrix
1363
1383
  * { euler:[rx,ry,rz], order? } — intrinsic Euler (default order: YXZ)
1364
- * { from:[x,y,z], to:[x,y,z] } — shortest-arc rotation
1384
+ * { from:[x,y,z], to:[x,y,z] } — shortest-arc rotation
1365
1385
  *
1366
1386
  * @param {*} v
1367
1387
  * @returns {number[]|null} [x,y,z,w] or null if unparseable.
@@ -1388,10 +1408,10 @@ function _parseQuat(v) {
1388
1408
  return qFromLookDir([0,0,0,1], d, u);
1389
1409
  }
1390
1410
 
1391
- // { eMatrix }
1392
- if (v.eMatrix != null) {
1393
- const m = (ArrayBuffer.isView(v.eMatrix) || Array.isArray(v.eMatrix))
1394
- ? v.eMatrix : (v.eMatrix.mat4 ?? null);
1411
+ // { mat4Eye }
1412
+ if (v.mat4Eye != null) {
1413
+ const m = (ArrayBuffer.isView(v.mat4Eye) || Array.isArray(v.mat4Eye))
1414
+ ? v.mat4Eye : (v.mat4Eye.mat4 ?? null);
1395
1415
  if (!m || m.length < 16) return null;
1396
1416
  return qFromRotMat3x3([0,0,0,1], m[0],m[4],m[8], m[1],m[5],m[9], m[2],m[6],m[10]);
1397
1417
  }
@@ -1449,7 +1469,7 @@ function _parseQuat(v) {
1449
1469
  *
1450
1470
  * Accepted forms:
1451
1471
  *
1452
- * { mMatrix }
1472
+ * { mat4Model }
1453
1473
  * Decompose a column-major mat4 into TRS via mat4ToTransform.
1454
1474
  * Float32Array(16), plain Array, or { mat4 } wrapper.
1455
1475
  *
@@ -1465,10 +1485,10 @@ function _parseQuat(v) {
1465
1485
  function _parseSpec(spec) {
1466
1486
  if (!spec || typeof spec !== 'object') return null;
1467
1487
 
1468
- // { mMatrix } — full TRS decomposition from model matrix
1469
- if (spec.mMatrix != null) {
1470
- const m = (ArrayBuffer.isView(spec.mMatrix) || Array.isArray(spec.mMatrix))
1471
- ? spec.mMatrix : (spec.mMatrix.mat4 ?? null);
1488
+ // { mat4Model } — full TRS decomposition from model matrix
1489
+ if (spec.mat4Model != null) {
1490
+ const m = (ArrayBuffer.isView(spec.mat4Model) || Array.isArray(spec.mat4Model))
1491
+ ? spec.mat4Model : (spec.mat4Model.mat4 ?? null);
1472
1492
  if (!m || m.length < 16) return null;
1473
1493
  const kf = mat4ToTransform({ pos:[0,0,0], rot:[0,0,0,1], scl:[1,1,1] }, m);
1474
1494
  kf.tanIn = null; kf.tanOut = null;
@@ -1505,7 +1525,7 @@ function _sameTransform(a, b) {
1505
1525
  * When absent, centripetal Catmull-Rom tangents are auto-computed at eval time.
1506
1526
  *
1507
1527
  * Removed forms (task 2):
1508
- * { vMatrix } and { eMatrix } — use PoseTrack.add({ mMatrix: eMatrix }) for
1528
+ * { mat4View } and { mat4Eye } — use PoseTrack.add({ mat4Model: mat4Eye }) for
1509
1529
  * full-fidelity capture including roll, or cam.capturePose() for lookat-style.
1510
1530
  *
1511
1531
  * @param {Object} spec
@@ -2004,7 +2024,7 @@ class PoseTrack extends Track {
2004
2024
  * { eye, center?, up?, fov?, halfHeight?,
2005
2025
  * eyeTanIn?, eyeTanOut?, centerTanIn?, centerTanOut? }
2006
2026
  *
2007
- * To capture a matrix-based pose, use PoseTrack.add({ mMatrix: eMatrix })
2027
+ * To capture a matrix-based pose, use PoseTrack.add({ mat4Model: mat4Eye })
2008
2028
  * for full-fidelity including roll, or cam.capturePose() for lookat-style.
2009
2029
  */
2010
2030
  class CameraTrack extends Track {