@nakednous/tree 0.0.7 → 0.0.9
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 +37 -14
- package/dist/index.js +207 -90
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -822,7 +822,7 @@ function applyPickMatrix(proj, px, py, W, H) {
|
|
|
822
822
|
* qFromAxisAngle qFromLookDir qFromRotMat3x3 qFromMat4 qToMat4
|
|
823
823
|
* quatToAxisAngle
|
|
824
824
|
* Spline / vector helpers
|
|
825
|
-
*
|
|
825
|
+
* hermiteVec3 lerpVec3
|
|
826
826
|
* Transform / mat4 helpers
|
|
827
827
|
* transformToMat4 mat4ToTransform
|
|
828
828
|
* Tracks
|
|
@@ -839,20 +839,22 @@ function applyPickMatrix(proj, px, py, W, H) {
|
|
|
839
839
|
* add() / eval() for their respective data shape.
|
|
840
840
|
*
|
|
841
841
|
* ── Hook architecture ─────────────────────────────────────────────────────────
|
|
842
|
-
*
|
|
843
|
-
*
|
|
842
|
+
* Lib-space hooks (underscore prefix — reserved for host layer / UI layer):
|
|
843
|
+
* _onActivate / _onDeactivate — fire on playing transitions false→true / true→false.
|
|
844
|
+
* _onPlay / _onEnd / _onStop — mirror the user-space hooks; used by the UI layer
|
|
845
|
+
* so it can sync without chaining the public slots.
|
|
844
846
|
*
|
|
845
|
-
*
|
|
847
|
+
* User-space hooks (public):
|
|
846
848
|
* onPlay : fires in play() on false→true transition.
|
|
847
849
|
* onEnd : fires in tick() at natural boundary (once mode only).
|
|
848
850
|
* onStop : fires in stop() / reset() — explicit deactivation.
|
|
849
851
|
* onEnd and onStop are mutually exclusive per event.
|
|
850
852
|
*
|
|
851
853
|
* Firing order:
|
|
852
|
-
* play() → onPlay → _onActivate
|
|
853
|
-
* tick() → onEnd → _onDeactivate
|
|
854
|
-
* stop() → onStop → _onDeactivate
|
|
855
|
-
* reset() → onStop → _onDeactivate
|
|
854
|
+
* play() → onPlay → _onPlay → _onActivate
|
|
855
|
+
* tick() → onEnd → _onEnd → _onDeactivate
|
|
856
|
+
* stop() → onStop → _onStop → _onDeactivate
|
|
857
|
+
* reset() → onStop → _onStop → _onDeactivate
|
|
856
858
|
*
|
|
857
859
|
* ── Playback semantics (rate) ─────────────────────────────────────────────────
|
|
858
860
|
* rate > 0 forward
|
|
@@ -1048,33 +1050,42 @@ function _dist3(a, b) {
|
|
|
1048
1050
|
}
|
|
1049
1051
|
|
|
1050
1052
|
/**
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1053
|
-
* Boundary: p0===p1 or p2===p3 clamps the end tangent.
|
|
1053
|
+
* Cubic Hermite interpolation between p0 and p1 with explicit tangents.
|
|
1054
|
+
* Catmull-Rom is a special case where m0/m1 are auto-computed from neighbors.
|
|
1054
1055
|
* @param {number[]} out 3-element result.
|
|
1055
|
-
* @param {number[]} p0
|
|
1056
|
-
* @param {number[]}
|
|
1057
|
-
* @param {number[]}
|
|
1058
|
-
* @param {number[]}
|
|
1059
|
-
* @param {number} t
|
|
1056
|
+
* @param {number[]} p0 Segment start.
|
|
1057
|
+
* @param {number[]} m0 Outgoing tangent at p0 (world-space, dp/dt scaled to segment).
|
|
1058
|
+
* @param {number[]} p1 Segment end.
|
|
1059
|
+
* @param {number[]} m1 Incoming tangent at p1 (world-space, dp/dt scaled to segment).
|
|
1060
|
+
* @param {number} t Blend [0, 1].
|
|
1060
1061
|
* @returns {number[]} out
|
|
1061
1062
|
*/
|
|
1062
|
-
const
|
|
1063
|
-
const
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1063
|
+
const hermiteVec3 = (out, p0, m0, p1, m1, t) => {
|
|
1064
|
+
const t2=t*t, t3=t2*t;
|
|
1065
|
+
const h00=2*t3-3*t2+1, h10=t3-2*t2+t, h01=-2*t3+3*t2, h11=t3-t2;
|
|
1066
|
+
out[0]=h00*p0[0]+h10*m0[0]+h01*p1[0]+h11*m1[0];
|
|
1067
|
+
out[1]=h00*p0[1]+h10*m0[1]+h01*p1[1]+h11*m1[1];
|
|
1068
|
+
out[2]=h00*p0[2]+h10*m0[2]+h01*p1[2]+h11*m1[2];
|
|
1069
|
+
return out;
|
|
1070
|
+
};
|
|
1071
|
+
|
|
1072
|
+
// Centripetal CR outgoing tangent at p1 for segment p1→p2, scaled by dt1.
|
|
1073
|
+
const _crTanOut = (out, p0, p1, p2, p3) => {
|
|
1074
|
+
const dt0=Math.pow(_dist3(p0,p1),0.5)||1, dt1=Math.pow(_dist3(p1,p2),0.5)||1; Math.pow(_dist3(p2,p3),0.5)||1;
|
|
1075
|
+
for (let i=0;i<3;i++) out[i]=((p1[i]-p0[i])/dt0-(p2[i]-p0[i])/(dt0+dt1)+(p2[i]-p1[i])/dt1)*dt1;
|
|
1076
|
+
return out;
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
// Centripetal CR incoming tangent at p2 for segment p1→p2, scaled by dt1.
|
|
1080
|
+
const _crTanIn = (out, p0, p1, p2, p3) => {
|
|
1081
|
+
Math.pow(_dist3(p0,p1),0.5)||1; const dt1=Math.pow(_dist3(p1,p2),0.5)||1, dt2=Math.pow(_dist3(p2,p3),0.5)||1;
|
|
1082
|
+
for (let i=0;i<3;i++) out[i]=((p2[i]-p1[i])/dt1-(p3[i]-p1[i])/(dt1+dt2)+(p3[i]-p2[i])/dt2)*dt1;
|
|
1075
1083
|
return out;
|
|
1076
1084
|
};
|
|
1077
1085
|
|
|
1086
|
+
// Module-level scratch — shared by eval() across all track instances (non-reentrant hot path).
|
|
1087
|
+
const _m0=[0,0,0], _m1=[0,0,0];
|
|
1088
|
+
|
|
1078
1089
|
/**
|
|
1079
1090
|
* Linear interpolation between two vec3s.
|
|
1080
1091
|
* @param {number[]} out
|
|
@@ -1188,7 +1199,7 @@ const _EULER_ORDERS = new Set(['XYZ','XZY','YXZ','YZX','ZXY','ZYX']);
|
|
|
1188
1199
|
*/
|
|
1189
1200
|
function _parseQuat(v) {
|
|
1190
1201
|
if (!v) return null;
|
|
1191
|
-
|
|
1202
|
+
|
|
1192
1203
|
// raw [x,y,z,w] — plain array or typed array
|
|
1193
1204
|
if ((Array.isArray(v) || ArrayBuffer.isView(v)) && v.length === 4) return [v[0], v[1], v[2], v[3]];
|
|
1194
1205
|
|
|
@@ -1280,13 +1291,15 @@ function _parseQuat(v) {
|
|
|
1280
1291
|
* Float32Array(16), plain Array, or { mat4 } wrapper.
|
|
1281
1292
|
* pos from col3, scl from column lengths, rot from normalised rotation block.
|
|
1282
1293
|
*
|
|
1283
|
-
* { pos
|
|
1294
|
+
* { pos?, rot?, scl?, tanIn?, tanOut? }
|
|
1284
1295
|
* Explicit TRS. pos and scl are vec3, rot accepts any form from _parseQuat.
|
|
1285
1296
|
* All fields are optional — missing pos/scl default to [0,0,0] / [1,1,1],
|
|
1286
1297
|
* missing rot defaults to identity.
|
|
1298
|
+
* tanIn/tanOut are optional vec3 tangents for Hermite interpolation.
|
|
1299
|
+
* When absent, centripetal Catmull-Rom tangents are auto-computed at eval time.
|
|
1287
1300
|
*
|
|
1288
1301
|
* @param {Object} spec
|
|
1289
|
-
* @returns {{ pos:number[], rot:number[], scl:number[] }|null}
|
|
1302
|
+
* @returns {{ pos:number[], rot:number[], scl:number[], tanIn:number[]|null, tanOut:number[]|null }|null}
|
|
1290
1303
|
*/
|
|
1291
1304
|
function _parseSpec(spec) {
|
|
1292
1305
|
if (!spec || typeof spec !== 'object') return null;
|
|
@@ -1296,13 +1309,17 @@ function _parseSpec(spec) {
|
|
|
1296
1309
|
const m = (ArrayBuffer.isView(spec.mMatrix) || Array.isArray(spec.mMatrix))
|
|
1297
1310
|
? spec.mMatrix : (spec.mMatrix.mat4 ?? null);
|
|
1298
1311
|
if (!m || m.length < 16) return null;
|
|
1299
|
-
|
|
1312
|
+
const kf = mat4ToTransform({ pos:[0,0,0], rot:[0,0,0,1], scl:[1,1,1] }, m);
|
|
1313
|
+
kf.tanIn = null; kf.tanOut = null;
|
|
1314
|
+
return kf;
|
|
1300
1315
|
}
|
|
1301
1316
|
|
|
1302
|
-
const pos
|
|
1303
|
-
const rot
|
|
1304
|
-
const scl
|
|
1305
|
-
|
|
1317
|
+
const pos = _parseVec3(spec.pos) || [0,0,0];
|
|
1318
|
+
const rot = _parseQuat(spec.rot) || [0,0,0,1];
|
|
1319
|
+
const scl = _parseVec3(spec.scl) || [1,1,1];
|
|
1320
|
+
const tanIn = _parseVec3(spec.tanIn) || null;
|
|
1321
|
+
const tanOut = _parseVec3(spec.tanOut) || null;
|
|
1322
|
+
return { pos, rot, scl, tanIn, tanOut };
|
|
1306
1323
|
}
|
|
1307
1324
|
|
|
1308
1325
|
function _sameTransform(a, b) {
|
|
@@ -1320,9 +1337,12 @@ function _sameTransform(a, b) {
|
|
|
1320
1337
|
*
|
|
1321
1338
|
* Accepted forms:
|
|
1322
1339
|
*
|
|
1323
|
-
* { eye, center?, up
|
|
1340
|
+
* { eye, center?, up?, fov?, halfHeight?,
|
|
1341
|
+
* eyeTanIn?, eyeTanOut?, centerTanIn?, centerTanOut? }
|
|
1324
1342
|
* Explicit lookat. center defaults to [0,0,0], up defaults to [0,1,0].
|
|
1325
1343
|
* Both are normalised/stored as-is. eye must be a vec3.
|
|
1344
|
+
* eyeTanIn/Out and centerTanIn/Out are optional vec3 tangents for Hermite.
|
|
1345
|
+
* When absent, centripetal Catmull-Rom tangents are auto-computed at eval time.
|
|
1326
1346
|
*
|
|
1327
1347
|
* { vMatrix: mat4 }
|
|
1328
1348
|
* Column-major view matrix (world→eye).
|
|
@@ -1338,7 +1358,10 @@ function _sameTransform(a, b) {
|
|
|
1338
1358
|
* Float32Array(16), plain Array, or { mat4 } wrapper.
|
|
1339
1359
|
*
|
|
1340
1360
|
* @param {Object} spec
|
|
1341
|
-
* @returns {{ eye:number[], center:number[], up:number[]
|
|
1361
|
+
* @returns {{ eye:number[], center:number[], up:number[],
|
|
1362
|
+
* fov:number|null, halfHeight:number|null,
|
|
1363
|
+
* eyeTanIn:number[]|null, eyeTanOut:number[]|null,
|
|
1364
|
+
* centerTanIn:number[]|null, centerTanOut:number[]|null }|null}
|
|
1342
1365
|
*/
|
|
1343
1366
|
function _parseCameraSpec(spec) {
|
|
1344
1367
|
if (!spec || typeof spec !== 'object') return null;
|
|
@@ -1353,7 +1376,9 @@ function _parseCameraSpec(spec) {
|
|
|
1353
1376
|
const ez = -(m[2]*m[12] + m[6]*m[13] + m[10]*m[14]);
|
|
1354
1377
|
const fx=-m[8], fy=-m[9], fz=-m[10];
|
|
1355
1378
|
const fl=Math.sqrt(fx*fx+fy*fy+fz*fz)||1;
|
|
1356
|
-
return { eye:[ex,ey,ez], center:[ex+fx/fl,ey+fy/fl,ez+fz/fl], up:[0,1,0]
|
|
1379
|
+
return { eye:[ex,ey,ez], center:[ex+fx/fl,ey+fy/fl,ez+fz/fl], up:[0,1,0],
|
|
1380
|
+
fov:null, halfHeight:null,
|
|
1381
|
+
eyeTanIn:null, eyeTanOut:null, centerTanIn:null, centerTanOut:null };
|
|
1357
1382
|
}
|
|
1358
1383
|
|
|
1359
1384
|
// { eMatrix } — eye matrix (eye→world); eye = col3, forward = -col2
|
|
@@ -1364,17 +1389,28 @@ function _parseCameraSpec(spec) {
|
|
|
1364
1389
|
const ex=m[12], ey=m[13], ez=m[14];
|
|
1365
1390
|
const fx=-m[8], fy=-m[9], fz=-m[10];
|
|
1366
1391
|
const fl=Math.sqrt(fx*fx+fy*fy+fz*fz)||1;
|
|
1367
|
-
return { eye:[ex,ey,ez], center:[ex+fx/fl,ey+fy/fl,ez+fz/fl], up:[0,1,0]
|
|
1392
|
+
return { eye:[ex,ey,ez], center:[ex+fx/fl,ey+fy/fl,ez+fz/fl], up:[0,1,0],
|
|
1393
|
+
fov:null, halfHeight:null,
|
|
1394
|
+
eyeTanIn:null, eyeTanOut:null, centerTanIn:null, centerTanOut:null };
|
|
1368
1395
|
}
|
|
1369
1396
|
|
|
1370
1397
|
// { eye, center?, up? } — explicit lookat (eye is a vec3, not a mat4)
|
|
1371
1398
|
const eye = _parseVec3(spec.eye);
|
|
1372
1399
|
if (!eye) return null;
|
|
1373
1400
|
const center = _parseVec3(spec.center) || [0,0,0];
|
|
1374
|
-
const upRaw
|
|
1375
|
-
const up
|
|
1376
|
-
const ul
|
|
1377
|
-
return {
|
|
1401
|
+
const upRaw = spec.up ? _parseVec3(spec.up) : null;
|
|
1402
|
+
const up = upRaw || [0,1,0];
|
|
1403
|
+
const ul = Math.sqrt(up[0]*up[0]+up[1]*up[1]+up[2]*up[2]) || 1;
|
|
1404
|
+
return {
|
|
1405
|
+
eye, center,
|
|
1406
|
+
up: [up[0]/ul, up[1]/ul, up[2]/ul],
|
|
1407
|
+
fov: typeof spec.fov === 'number' ? spec.fov : null,
|
|
1408
|
+
halfHeight: typeof spec.halfHeight === 'number' ? spec.halfHeight : null,
|
|
1409
|
+
eyeTanIn: _parseVec3(spec.eyeTanIn) || null,
|
|
1410
|
+
eyeTanOut: _parseVec3(spec.eyeTanOut) || null,
|
|
1411
|
+
centerTanIn: _parseVec3(spec.centerTanIn) || null,
|
|
1412
|
+
centerTanOut:_parseVec3(spec.centerTanOut)|| null,
|
|
1413
|
+
};
|
|
1378
1414
|
}
|
|
1379
1415
|
|
|
1380
1416
|
function _sameCameraKeyframe(a, b) {
|
|
@@ -1383,6 +1419,8 @@ function _sameCameraKeyframe(a, b) {
|
|
|
1383
1419
|
if (a.center[i]!==b.center[i]) return false;
|
|
1384
1420
|
if (a.up[i]!==b.up[i]) return false;
|
|
1385
1421
|
}
|
|
1422
|
+
if (a.fov !== b.fov) return false;
|
|
1423
|
+
if (a.halfHeight !== b.halfHeight) return false;
|
|
1386
1424
|
return true;
|
|
1387
1425
|
}
|
|
1388
1426
|
|
|
@@ -1418,6 +1456,10 @@ class Track {
|
|
|
1418
1456
|
// Lib-space hooks (set by host layer, e.g. p5 bridge)
|
|
1419
1457
|
/** @type {Function|null} */ this._onActivate = null;
|
|
1420
1458
|
/** @type {Function|null} */ this._onDeactivate = null;
|
|
1459
|
+
// Lib-space event mirrors — set by UI layer (trackUI), never touched by user code
|
|
1460
|
+
/** @type {Function|null} */ this._onPlay = null;
|
|
1461
|
+
/** @type {Function|null} */ this._onEnd = null;
|
|
1462
|
+
/** @type {Function|null} */ this._onStop = null;
|
|
1421
1463
|
}
|
|
1422
1464
|
|
|
1423
1465
|
/** Playback rate. Assigning never starts/stops playback. @type {number} */
|
|
@@ -1465,6 +1507,7 @@ class Track {
|
|
|
1465
1507
|
this.playing = true;
|
|
1466
1508
|
if (!wasPlaying) {
|
|
1467
1509
|
if (typeof this.onPlay === 'function') { try { this.onPlay(this); } catch (_) {} }
|
|
1510
|
+
this._onPlay?.();
|
|
1468
1511
|
this._onActivate?.();
|
|
1469
1512
|
}
|
|
1470
1513
|
return this;
|
|
@@ -1480,6 +1523,7 @@ class Track {
|
|
|
1480
1523
|
this.playing = false;
|
|
1481
1524
|
if (wasPlaying) {
|
|
1482
1525
|
if (typeof this.onStop === 'function') { try { this.onStop(this); } catch (_) {} }
|
|
1526
|
+
this._onStop?.();
|
|
1483
1527
|
this._onDeactivate?.();
|
|
1484
1528
|
if (rewind && this.keyframes.length > 1) this.seek(this._rate < 0 ? 1 : 0);
|
|
1485
1529
|
}
|
|
@@ -1495,6 +1539,7 @@ class Track {
|
|
|
1495
1539
|
this.playing = false;
|
|
1496
1540
|
if (wasPlaying) {
|
|
1497
1541
|
if (typeof this.onStop === 'function') { try { this.onStop(this); } catch (_) {} }
|
|
1542
|
+
this._onStop?.();
|
|
1498
1543
|
this._onDeactivate?.();
|
|
1499
1544
|
}
|
|
1500
1545
|
this.keyframes.length = 0;
|
|
@@ -1605,6 +1650,7 @@ class Track {
|
|
|
1605
1650
|
this._setCursorFromScalar(0);
|
|
1606
1651
|
this.playing = false;
|
|
1607
1652
|
if (typeof this.onEnd === 'function') { try { this.onEnd(this); } catch (_) {} }
|
|
1653
|
+
this._onEnd?.();
|
|
1608
1654
|
this._onDeactivate?.();
|
|
1609
1655
|
return false;
|
|
1610
1656
|
}
|
|
@@ -1612,6 +1658,7 @@ class Track {
|
|
|
1612
1658
|
this._setCursorFromScalar(total);
|
|
1613
1659
|
this.playing = false;
|
|
1614
1660
|
if (typeof this.onEnd === 'function') { try { this.onEnd(this); } catch (_) {} }
|
|
1661
|
+
this._onEnd?.();
|
|
1615
1662
|
this._onDeactivate?.();
|
|
1616
1663
|
return false;
|
|
1617
1664
|
}
|
|
@@ -1638,12 +1685,18 @@ class Track {
|
|
|
1638
1685
|
/**
|
|
1639
1686
|
* Renderer-agnostic TRS keyframe track.
|
|
1640
1687
|
*
|
|
1641
|
-
* Keyframe shape: { pos:[x,y,z], rot:[x,y,z,w], scl:[x,y,z]
|
|
1688
|
+
* Keyframe shape: { pos:[x,y,z], rot:[x,y,z,w], scl:[x,y,z],
|
|
1689
|
+
* tanIn?:[x,y,z], tanOut?:[x,y,z] }
|
|
1690
|
+
*
|
|
1691
|
+
* tanIn — incoming position tangent at this keyframe (Hermite mode).
|
|
1692
|
+
* tanOut — outgoing position tangent at this keyframe (Hermite mode).
|
|
1693
|
+
* When only one is supplied, the other mirrors it.
|
|
1694
|
+
* When neither is supplied, centripetal Catmull-Rom tangents are auto-computed.
|
|
1642
1695
|
*
|
|
1643
1696
|
* add() accepts individual specs or a bulk array of specs:
|
|
1644
1697
|
*
|
|
1645
1698
|
* { mMatrix } — full TRS from model matrix
|
|
1646
|
-
* { pos?, rot?, scl? }
|
|
1699
|
+
* { pos?, rot?, scl?, tanIn?, tanOut? } — direct TRS; all fields optional
|
|
1647
1700
|
* { pos?, rot: [x,y,z,w] } — explicit quaternion
|
|
1648
1701
|
* { pos?, rot: { axis, angle } } — axis-angle
|
|
1649
1702
|
* { pos?, rot: { dir, up? } } — look direction
|
|
@@ -1656,16 +1709,15 @@ class Track {
|
|
|
1656
1709
|
* Missing fields default to: pos → [0,0,0], rot → [0,0,0,1], scl → [1,1,1].
|
|
1657
1710
|
*
|
|
1658
1711
|
* eval() writes { pos, rot, scl }:
|
|
1659
|
-
* pos —
|
|
1660
|
-
* rot — slerp (rotInterp='slerp') or nlerp
|
|
1712
|
+
* pos — Hermite (tanIn/tanOut per keyframe; auto-CR when absent) or linear or step
|
|
1713
|
+
* rot — slerp (rotInterp='slerp') or nlerp or step
|
|
1661
1714
|
* scl — lerp
|
|
1662
1715
|
*
|
|
1663
1716
|
* @example
|
|
1664
1717
|
* const track = new PoseTrack()
|
|
1665
|
-
* track.add({ pos:[0,0,0] })
|
|
1666
|
-
* track.add({ pos:[100,0,0],
|
|
1667
|
-
* track.add({
|
|
1668
|
-
* track.add({ mMatrix: someModelMatrix })
|
|
1718
|
+
* track.add({ pos:[0,0,0] })
|
|
1719
|
+
* track.add({ pos:[100,0,0], tanOut:[0,50,0] }) // leave heading +Y
|
|
1720
|
+
* track.add({ pos:[200,0,0] })
|
|
1669
1721
|
* track.play({ loop: true })
|
|
1670
1722
|
* // per frame:
|
|
1671
1723
|
* track.tick()
|
|
@@ -1677,14 +1729,19 @@ class PoseTrack extends Track {
|
|
|
1677
1729
|
super();
|
|
1678
1730
|
/**
|
|
1679
1731
|
* Position interpolation mode.
|
|
1680
|
-
*
|
|
1732
|
+
* - 'hermite' — cubic Hermite; uses tanIn/tanOut per keyframe when present,
|
|
1733
|
+
* auto-computes centripetal Catmull-Rom tangents when absent (default)
|
|
1734
|
+
* - 'linear' — lerp
|
|
1735
|
+
* - 'step' — snap to k0 value; useful for discrete state changes
|
|
1736
|
+
* @type {'hermite'|'linear'|'step'}
|
|
1681
1737
|
*/
|
|
1682
|
-
this.posInterp = '
|
|
1738
|
+
this.posInterp = 'hermite';
|
|
1683
1739
|
/**
|
|
1684
1740
|
* Rotation interpolation mode.
|
|
1685
1741
|
* - 'slerp' — constant angular velocity (default)
|
|
1686
1742
|
* - 'nlerp' — normalised lerp; cheaper, slightly non-constant speed
|
|
1687
|
-
*
|
|
1743
|
+
* - 'step' — snap to k0 quaternion; useful for discrete state changes
|
|
1744
|
+
* @type {'slerp'|'nlerp'|'step'}
|
|
1688
1745
|
*/
|
|
1689
1746
|
this.rotInterp = 'slerp';
|
|
1690
1747
|
// Scratch for toMatrix() — avoids hot-path allocations
|
|
@@ -1752,17 +1809,29 @@ class PoseTrack extends Track {
|
|
|
1752
1809
|
const k0 = this.keyframes[seg];
|
|
1753
1810
|
const k1 = this.keyframes[seg + 1];
|
|
1754
1811
|
|
|
1755
|
-
// pos —
|
|
1756
|
-
if (this.posInterp === '
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
catmullRomVec3(out.pos, p0, k0.pos, k1.pos, p3, t);
|
|
1760
|
-
} else {
|
|
1812
|
+
// pos — Hermite (auto-CR tangents when none stored), linear, or step
|
|
1813
|
+
if (this.posInterp === 'step') {
|
|
1814
|
+
out.pos[0]=k0.pos[0]; out.pos[1]=k0.pos[1]; out.pos[2]=k0.pos[2];
|
|
1815
|
+
} else if (this.posInterp === 'linear') {
|
|
1761
1816
|
lerpVec3(out.pos, k0.pos, k1.pos, t);
|
|
1817
|
+
} else {
|
|
1818
|
+
const p0 = seg > 0 ? this.keyframes[seg - 1].pos : k0.pos;
|
|
1819
|
+
const p3 = seg + 2 < n ? this.keyframes[seg + 2].pos : k1.pos;
|
|
1820
|
+
// tanOut on k0: use stored, else symmetric from tanIn, else auto-CR
|
|
1821
|
+
const m0 = k0.tanOut != null ? k0.tanOut
|
|
1822
|
+
: k0.tanIn != null ? k0.tanIn
|
|
1823
|
+
: _crTanOut(_m0, p0, k0.pos, k1.pos, p3);
|
|
1824
|
+
// tanIn on k1: use stored, else symmetric from tanOut, else auto-CR
|
|
1825
|
+
const m1 = k1.tanIn != null ? k1.tanIn
|
|
1826
|
+
: k1.tanOut != null ? k1.tanOut
|
|
1827
|
+
: _crTanIn(_m1, p0, k0.pos, k1.pos, p3);
|
|
1828
|
+
hermiteVec3(out.pos, k0.pos, m0, k1.pos, m1, t);
|
|
1762
1829
|
}
|
|
1763
1830
|
|
|
1764
|
-
// rot — slerp or nlerp
|
|
1765
|
-
if (this.rotInterp === '
|
|
1831
|
+
// rot — step, slerp, or nlerp
|
|
1832
|
+
if (this.rotInterp === 'step') {
|
|
1833
|
+
out.rot[0]=k0.rot[0]; out.rot[1]=k0.rot[1]; out.rot[2]=k0.rot[2]; out.rot[3]=k0.rot[3];
|
|
1834
|
+
} else if (this.rotInterp === 'nlerp') {
|
|
1766
1835
|
qNlerp(out.rot, k0.rot, k1.rot, t);
|
|
1767
1836
|
} else {
|
|
1768
1837
|
qSlerp(out.rot, k0.rot, k1.rot, t);
|
|
@@ -1792,18 +1861,33 @@ class PoseTrack extends Track {
|
|
|
1792
1861
|
/**
|
|
1793
1862
|
* Lookat camera keyframe track.
|
|
1794
1863
|
*
|
|
1795
|
-
* Keyframe shape: { eye:[x,y,z], center:[x,y,z], up:[x,y,z]
|
|
1864
|
+
* Keyframe shape: { eye:[x,y,z], center:[x,y,z], up:[x,y,z],
|
|
1865
|
+
* fov?:number, halfHeight?:number,
|
|
1866
|
+
* eyeTanIn?:[x,y,z], eyeTanOut?:[x,y,z],
|
|
1867
|
+
* centerTanIn?:[x,y,z], centerTanOut?:[x,y,z] }
|
|
1868
|
+
*
|
|
1869
|
+
* fov — vertical fov (radians) for perspective cameras; null for ortho.
|
|
1870
|
+
* halfHeight — world-unit half-height of ortho frustum; null for perspective.
|
|
1871
|
+
* Both are optional and nullable. eval() lerps each only when both adjacent
|
|
1872
|
+
* keyframes carry a non-null value for that field.
|
|
1873
|
+
*
|
|
1874
|
+
* eyeTanIn/Out and centerTanIn/Out are optional vec3 tangents for Hermite
|
|
1875
|
+
* interpolation of the eye and center paths respectively.
|
|
1876
|
+
* When absent, centripetal Catmull-Rom tangents are auto-computed at eval time.
|
|
1796
1877
|
*
|
|
1797
1878
|
* Each field is independently interpolated — eye and center along their
|
|
1798
1879
|
* own paths, up nlerped on the unit sphere. This correctly handles cameras
|
|
1799
1880
|
* that always look at a fixed target (center stays at origin throughout)
|
|
1800
1881
|
* as well as free-fly paths where center moves independently.
|
|
1801
|
-
*
|
|
1882
|
+
*
|
|
1802
1883
|
* Missing fields default to: center → [0,0,0], up → [0,1,0].
|
|
1803
1884
|
*
|
|
1804
1885
|
* add() accepts individual specs or a bulk array of specs:
|
|
1805
1886
|
*
|
|
1806
|
-
* { eye, center?, up
|
|
1887
|
+
* { eye, center?, up?, fov?, halfHeight?,
|
|
1888
|
+
* eyeTanIn?, eyeTanOut?, centerTanIn?, centerTanOut? }
|
|
1889
|
+
* explicit lookat; center defaults to [0,0,0], up to [0,1,0].
|
|
1890
|
+
* fov and halfHeight are mutually exclusive nullable scalars.
|
|
1807
1891
|
* { vMatrix: mat4 } view matrix (world→eye); eye reconstructed via -R^T·t
|
|
1808
1892
|
* { eMatrix: mat4 } eye matrix (eye→world); eye read from col3 directly
|
|
1809
1893
|
* [ spec, spec, ... ] bulk
|
|
@@ -1814,10 +1898,12 @@ class PoseTrack extends Track {
|
|
|
1814
1898
|
* passing it to cam.camera() shifts orbitControl's orbit reference.
|
|
1815
1899
|
* Use capturePose() (p5.tree bridge) when the real up hint is needed.
|
|
1816
1900
|
*
|
|
1817
|
-
* eval() writes { eye, center, up }:
|
|
1818
|
-
* eye
|
|
1819
|
-
* center
|
|
1820
|
-
* up
|
|
1901
|
+
* eval() writes { eye, center, up, fov, halfHeight }:
|
|
1902
|
+
* eye — Hermite (auto-CR when no tangents stored) or linear or step
|
|
1903
|
+
* center — Hermite (auto-CR when no tangents stored) or linear or step
|
|
1904
|
+
* up — nlerp (normalize-after-lerp on unit sphere)
|
|
1905
|
+
* fov — lerp when both keyframes carry non-null fov; else null
|
|
1906
|
+
* halfHeight — lerp when both keyframes carry non-null halfHeight; else null
|
|
1821
1907
|
*
|
|
1822
1908
|
* @example
|
|
1823
1909
|
* const track = new CameraTrack()
|
|
@@ -1839,14 +1925,20 @@ class CameraTrack extends Track {
|
|
|
1839
1925
|
super();
|
|
1840
1926
|
/**
|
|
1841
1927
|
* Eye position interpolation mode.
|
|
1842
|
-
*
|
|
1928
|
+
* - 'hermite' — cubic Hermite; auto-CR tangents when none stored (default)
|
|
1929
|
+
* - 'linear' — lerp
|
|
1930
|
+
* - 'step' — snap to k0 eye
|
|
1931
|
+
* @type {'hermite'|'linear'|'step'}
|
|
1843
1932
|
*/
|
|
1844
|
-
this.eyeInterp = '
|
|
1933
|
+
this.eyeInterp = 'hermite';
|
|
1845
1934
|
/**
|
|
1846
1935
|
* Center (lookat target) interpolation mode.
|
|
1847
|
-
* 'linear' suits fixed or predictably moving targets.
|
|
1848
|
-
* '
|
|
1849
|
-
*
|
|
1936
|
+
* 'linear' suits fixed or predictably moving targets (default).
|
|
1937
|
+
* 'hermite' gives smoother paths when center is also flying freely.
|
|
1938
|
+
* - 'hermite' — cubic Hermite; auto-CR tangents when none stored
|
|
1939
|
+
* - 'linear' — lerp
|
|
1940
|
+
* - 'step' — snap to k0 center
|
|
1941
|
+
* @type {'hermite'|'linear'|'step'}
|
|
1850
1942
|
*/
|
|
1851
1943
|
this.centerInterp = 'linear';
|
|
1852
1944
|
// Scratch for toCamera() — avoids hot-path allocations
|
|
@@ -1898,7 +1990,7 @@ class CameraTrack extends Track {
|
|
|
1898
1990
|
* @returns {{ eye:number[], center:number[], up:number[] }} out
|
|
1899
1991
|
*/
|
|
1900
1992
|
eval(out) {
|
|
1901
|
-
out = out || { eye:[0,0,0], center:[0,0,0], up:[0,1,0] };
|
|
1993
|
+
out = out || { eye:[0,0,0], center:[0,0,0], up:[0,1,0], fov:null, halfHeight:null };
|
|
1902
1994
|
const n = this.keyframes.length;
|
|
1903
1995
|
if (n === 0) return out;
|
|
1904
1996
|
|
|
@@ -1907,6 +1999,8 @@ class CameraTrack extends Track {
|
|
|
1907
1999
|
out.eye[0]=k.eye[0]; out.eye[1]=k.eye[1]; out.eye[2]=k.eye[2];
|
|
1908
2000
|
out.center[0]=k.center[0]; out.center[1]=k.center[1]; out.center[2]=k.center[2];
|
|
1909
2001
|
out.up[0]=k.up[0]; out.up[1]=k.up[1]; out.up[2]=k.up[2];
|
|
2002
|
+
out.fov = k.fov;
|
|
2003
|
+
out.halfHeight = k.halfHeight;
|
|
1910
2004
|
return out;
|
|
1911
2005
|
}
|
|
1912
2006
|
|
|
@@ -1917,22 +2011,38 @@ class CameraTrack extends Track {
|
|
|
1917
2011
|
const k0 = this.keyframes[seg];
|
|
1918
2012
|
const k1 = this.keyframes[seg + 1];
|
|
1919
2013
|
|
|
1920
|
-
// eye —
|
|
1921
|
-
if (this.eyeInterp === '
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
catmullRomVec3(out.eye, p0, k0.eye, k1.eye, p3, t);
|
|
1925
|
-
} else {
|
|
2014
|
+
// eye — Hermite (auto-CR tangents when none stored), linear, or step
|
|
2015
|
+
if (this.eyeInterp === 'step') {
|
|
2016
|
+
out.eye[0]=k0.eye[0]; out.eye[1]=k0.eye[1]; out.eye[2]=k0.eye[2];
|
|
2017
|
+
} else if (this.eyeInterp === 'linear') {
|
|
1926
2018
|
lerpVec3(out.eye, k0.eye, k1.eye, t);
|
|
2019
|
+
} else {
|
|
2020
|
+
const p0 = seg > 0 ? this.keyframes[seg - 1].eye : k0.eye;
|
|
2021
|
+
const p3 = seg + 2 < n ? this.keyframes[seg + 2].eye : k1.eye;
|
|
2022
|
+
const m0 = k0.eyeTanOut != null ? k0.eyeTanOut
|
|
2023
|
+
: k0.eyeTanIn != null ? k0.eyeTanIn
|
|
2024
|
+
: _crTanOut(_m0, p0, k0.eye, k1.eye, p3);
|
|
2025
|
+
const m1 = k1.eyeTanIn != null ? k1.eyeTanIn
|
|
2026
|
+
: k1.eyeTanOut != null ? k1.eyeTanOut
|
|
2027
|
+
: _crTanIn(_m1, p0, k0.eye, k1.eye, p3);
|
|
2028
|
+
hermiteVec3(out.eye, k0.eye, m0, k1.eye, m1, t);
|
|
1927
2029
|
}
|
|
1928
2030
|
|
|
1929
|
-
// center —
|
|
1930
|
-
if (this.centerInterp === '
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
catmullRomVec3(out.center, c0, k0.center, k1.center, c3, t);
|
|
1934
|
-
} else {
|
|
2031
|
+
// center — Hermite, linear, or step (independent lookat target)
|
|
2032
|
+
if (this.centerInterp === 'step') {
|
|
2033
|
+
out.center[0]=k0.center[0]; out.center[1]=k0.center[1]; out.center[2]=k0.center[2];
|
|
2034
|
+
} else if (this.centerInterp === 'linear') {
|
|
1935
2035
|
lerpVec3(out.center, k0.center, k1.center, t);
|
|
2036
|
+
} else {
|
|
2037
|
+
const c0 = seg > 0 ? this.keyframes[seg - 1].center : k0.center;
|
|
2038
|
+
const c3 = seg + 2 < n ? this.keyframes[seg + 2].center : k1.center;
|
|
2039
|
+
const m0 = k0.centerTanOut != null ? k0.centerTanOut
|
|
2040
|
+
: k0.centerTanIn != null ? k0.centerTanIn
|
|
2041
|
+
: _crTanOut(_m0, c0, k0.center, k1.center, c3);
|
|
2042
|
+
const m1 = k1.centerTanIn != null ? k1.centerTanIn
|
|
2043
|
+
: k1.centerTanOut != null ? k1.centerTanOut
|
|
2044
|
+
: _crTanIn(_m1, c0, k0.center, k1.center, c3);
|
|
2045
|
+
hermiteVec3(out.center, k0.center, m0, k1.center, m1, t);
|
|
1936
2046
|
}
|
|
1937
2047
|
|
|
1938
2048
|
// up — nlerp (normalize after lerp; correct for typical near-upright cameras)
|
|
@@ -1942,6 +2052,13 @@ class CameraTrack extends Track {
|
|
|
1942
2052
|
const ul = Math.sqrt(ux*ux+uy*uy+uz*uz) || 1;
|
|
1943
2053
|
out.up[0]=ux/ul; out.up[1]=uy/ul; out.up[2]=uz/ul;
|
|
1944
2054
|
|
|
2055
|
+
// fov — lerp (perspective); null when either keyframe lacks it
|
|
2056
|
+
out.fov = (k0.fov !== null && k1.fov !== null)
|
|
2057
|
+
? k0.fov + t * (k1.fov - k0.fov) : null;
|
|
2058
|
+
// halfHeight — lerp (ortho); null when either keyframe lacks it
|
|
2059
|
+
out.halfHeight = (k0.halfHeight !== null && k1.halfHeight !== null)
|
|
2060
|
+
? k0.halfHeight + t * (k1.halfHeight - k0.halfHeight) : null;
|
|
2061
|
+
|
|
1945
2062
|
return out;
|
|
1946
2063
|
}
|
|
1947
2064
|
}
|
|
@@ -2103,5 +2220,5 @@ function boxVisibility(planes, x0, y0, z0, x1, y1, z1) {
|
|
|
2103
2220
|
return allIn ? VISIBLE : SEMIVISIBLE;
|
|
2104
2221
|
}
|
|
2105
2222
|
|
|
2106
|
-
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, applyPickMatrix, boxVisibility,
|
|
2223
|
+
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, applyPickMatrix, boxVisibility, distanceToPlane, frustumPlanes, hermiteVec3, 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 };
|
|
2107
2224
|
//# sourceMappingURL=index.js.map
|