@nakednous/host 0.0.6 → 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 +1 -1
- package/dist/index.js +109 -18
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ dependency first and an options object last, and registers with the context so
|
|
|
79
79
|
| `host.handle(opts)` | `Handle` — a draggable core constraint (`SPHERE` · `PLANE` · `AXIS` · `DIAL` · `VIEW`, or a contract object); `update()`, `value(out, opts)`, snap, hover, cancel, `from` |
|
|
80
80
|
| `host.router(handles, opts)` | `PointerRouter` — one shared pick over overlapping handles, nearest proxy wins; shared hover |
|
|
81
81
|
| `host.cameraHelm(cam, opts)` · `host.poseHelm(opts)` | a core `PoseHelm` on the players: body-fly a camera state, or integrate into a pose in `WORLD` · `EYE` · `SELF` · a mat4 and `bind()` a target |
|
|
82
|
-
| `host.poseTrack(opts)` · `host.cameraTrack(cam, opts)` | core tracks ticked while they play; `{ handles }` builds `TrackHandles`, one handle per keyframe |
|
|
82
|
+
| `host.poseTrack(opts)` · `host.cameraTrack(cam, opts)` | core tracks ticked while they play; `{ handles }` builds `TrackHandles`, one handle per keyframe. `add(depth)` — the transport panel's `+` — authors where the camera is looking: a pose track places the pose at the centre of the installed camera's frustum, `depth` `0`…`1` from the near plane to the far one, aimed along that camera (the view bag's matrices; never deduplicated), a camera track captures its camera state, a placement depth being nothing a camera keyframe could use. `remove()` — the transport panel's `−` — retracts the last keyframe, one per call; `remove(i)` is the core track's own |
|
|
83
83
|
| `host.hid(opts)` · `host.gamepad(opts)` | rate streams — `lin[3]` / `ang[3]` in raw device units, `bind(helm)` feeds it each tick; `available`, `connected` |
|
|
84
84
|
| `host.image(url)` · `host.video(opts)` · `host.raster(draw, w, h)` | texture sources — an `ImageBitmap`, a hidden `<video>` from a file or the camera with a `ready` promise, a Canvas2D drawing |
|
|
85
85
|
| `host.mesh(url, opts)` | one mesh — the arrays shape `{ position, indices, normal?, tangent?, texcoord?, color?, joints?, weights? }` with its `bounds` `{ min, max, center, diag }` beside them — from an OBJ file, a one-part glTF 2.0 file (`.glb`, `.gltf`), or any format through `{ parse: (arrayBuffer) => arrays }`; what a bridge's buffer upload takes as it is, and `size / mesh.bounds.diag` fits any file to `size`. A file of several parts rejects, naming `model`. `{ format }` when the URL does not say. **`normals`**: left out, the file's normals are kept and a mesh without any gets smooth ones; `'smooth'` always recomputes them, summed per position, so a faceted file or a texture seam smooths too; `'flat'` always recomputes them on the flattened mesh (every triangle owning its vertices: more vertices, indices counting up); `false` computes nothing. The procedure, its limits and sources are tree's `mesh` module's; glTF viewers compute flat normals for a file without any — here that is `'flat'` |
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WEBGL, cameraView, cameraProj, mat4Mul, mat4Invert, PLANE, createConstraint, WORLD, AXIS, DIAL, POINT, mapDirection, pixelRatio, rayHitSphere, unproject, SPHERE, dirFromAzEl, DIRECTION, mapLocation, MATRIX, PoseHelm, cameraToPose, EYE, SELF, qToMat4, cameraEye, cameraFromPose, CameraTrack, PoseTrack, qFromAxisAngle, mat4ToTransform, meshBounds, meshFlatten, meshNormals, meshGroups, SCREEN, cameraCopy, createCamera, coastAlive, coastStep, cameraOrbit, cameraPan, cameraDolly } from '@nakednous/tree';
|
|
1
|
+
import { WEBGL, cameraView, cameraProj, mat4Mul, mat4Invert, PLANE, createConstraint, WORLD, AXIS, DIAL, POINT, mapDirection, pixelRatio, rayHitSphere, unproject, SPHERE, dirFromAzEl, DIRECTION, mapLocation, MATRIX, PoseHelm, cameraToPose, EYE, SELF, qToMat4, cameraEye, cameraFromPose, CameraTrack, PoseTrack, qFromAxisAngle, NDC, mat4ToTransform, meshBounds, meshFlatten, meshNormals, meshGroups, SCREEN, cameraCopy, createCamera, coastAlive, coastStep, cameraOrbit, cameraPan, cameraDolly } from '@nakednous/tree';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file The view bag — the matrices every pick, solve, label and orbit reads.
|
|
@@ -186,10 +186,11 @@ function observeCanvas(canvas, onChange) {
|
|
|
186
186
|
*
|
|
187
187
|
* - pointerdown · pointermove · pointerup · pointercancel on the canvas,
|
|
188
188
|
* keydown (Esc) on the window.
|
|
189
|
-
* - Coordinates in
|
|
190
|
-
*
|
|
191
|
-
* size read off the view bag's vp
|
|
192
|
-
* expect
|
|
189
|
+
* - Coordinates in screen space — the surface's logical pixels, top-left, y
|
|
190
|
+
* down — through the element's bounding rectangle: (clientX − rect.left) ·
|
|
191
|
+
* (width / rect.width), the logical size read off the view bag's vp. These
|
|
192
|
+
* are the numbers mapLocation(SCREEN) and vp expect (a viewport of negative
|
|
193
|
+
* height), so a CSS-scaled canvas maps correctly.
|
|
193
194
|
* - Per pointer: an entry { id, x, y, seq, down, up, cancel, owner }, kept
|
|
194
195
|
* from its press until the frame after its release; `seq` counts moves,
|
|
195
196
|
* so a consumer remembers the last seq it consumed and reads only new
|
|
@@ -787,7 +788,7 @@ class Handle {
|
|
|
787
788
|
}
|
|
788
789
|
|
|
789
790
|
// The proxy's world position and its grab radius in world units — a
|
|
790
|
-
// constant grabPx
|
|
791
|
+
// constant grabPx canvas size at the proxy's depth.
|
|
791
792
|
_proxyPrep() {
|
|
792
793
|
const c = this._constraint, v = this._view, p = this._proxyPos;
|
|
793
794
|
if (this._from && !this._grabbed) this._resolveFrame();
|
|
@@ -810,14 +811,14 @@ class Handle {
|
|
|
810
811
|
return rayHitSphere(o[0], o[1], o[2], d[0], d[1], d[2], p[0], p[1], p[2], this._proxyRad);
|
|
811
812
|
}
|
|
812
813
|
|
|
813
|
-
/** Unproject a
|
|
814
|
+
/** Unproject a screen-space pixel into the ray scratch; false on a stale view. */
|
|
814
815
|
_ray(x, y) {
|
|
815
816
|
const v = this._view;
|
|
816
817
|
if (v.stale) return false;
|
|
817
818
|
return unproject(_rayO, _rayD, x, y, v, v.vp, v.ndcZMin) !== null;
|
|
818
819
|
}
|
|
819
820
|
|
|
820
|
-
/** Hit-test the proxy at a
|
|
821
|
+
/** Hit-test the proxy at a screen-space pixel. */
|
|
821
822
|
_pickAt(x, y) {
|
|
822
823
|
return this._ray(x, y) && this._proxyT(_rayO, _rayD) < Infinity;
|
|
823
824
|
}
|
|
@@ -1207,7 +1208,7 @@ function cameraHelm(host, cam, opts) {
|
|
|
1207
1208
|
const helm = new PoseHelm();
|
|
1208
1209
|
_applyOpts(helm, opts);
|
|
1209
1210
|
if (opts && opts.from != null) {
|
|
1210
|
-
console.error('[host] cameraHelm: a camera helm is always body-fly and has no `from`. Ignoring it; bind the camera to a poseHelm for
|
|
1211
|
+
console.error('[host] cameraHelm: a camera helm is always body-fly and has no `from`. Ignoring it; bind the camera to a poseHelm for view- or world-relative motion.');
|
|
1211
1212
|
}
|
|
1212
1213
|
if (_isCameraState(cam)) helm.home(cameraToPose(_pose, cam));
|
|
1213
1214
|
const player = {
|
|
@@ -1294,17 +1295,81 @@ function poseHelm(host, opts) {
|
|
|
1294
1295
|
* poseTrack(host, opts) is a core PoseTrack whose activation hooks add and
|
|
1295
1296
|
* remove its player. cameraTrack(host, cam, opts) is a core CameraTrack whose
|
|
1296
1297
|
* player evaluates straight into the camera state `cam` — the state is the
|
|
1297
|
-
* keyframe shape, so track.eval(cam) is the write —
|
|
1298
|
-
* deactivate so the camera rests on the path
|
|
1298
|
+
* keyframe shape, so track.eval(cam) is the write — while the track plays, and
|
|
1299
|
+
* once more on deactivate so the camera rests on the path. That bound is the
|
|
1300
|
+
* point: a stopped track's camera belongs to whoever else drives it — the
|
|
1301
|
+
* reader's orbit, the figure's own camera code — so a consumer that renders
|
|
1302
|
+
* from the state derives it instead, one evaluation per frame after the tick
|
|
1303
|
+
* that advances the transport, which carries a seek, a dragged keyframe and a
|
|
1304
|
+
* capture into the state as well and repeats the player's write harmlessly
|
|
1305
|
+
* while the track plays. add() with no argument
|
|
1299
1306
|
* captures `cam`, add({ camera }) any camera-state object. { handles } on
|
|
1300
1307
|
* either decorates the track with TrackHandles: one VIEW handle per
|
|
1301
1308
|
* draggable keyframe field (pos / eye / center), an optional rot DIAL per
|
|
1302
1309
|
* keyframe about a declared axis, all on one router.
|
|
1310
|
+
*
|
|
1311
|
+
* add(depth) — the transport panel's + button, which passes its depth slider
|
|
1312
|
+
* (ui's contract) — authors a keyframe where the camera is looking: on a pose
|
|
1313
|
+
* track the pose lands at the centre of the installed camera's frustum at
|
|
1314
|
+
* that depth, aimed along the camera's own eye frame; on a camera track the
|
|
1315
|
+
* camera state is captured, the depth having nothing to place. A panel whose
|
|
1316
|
+
* depth row is hidden passes no depth, which reads the panel's default, 0.5.
|
|
1317
|
+
* The frame is the host's view bag — the matrices the last setCamera
|
|
1318
|
+
* installed — so the placement is the same on every bridge and needs no
|
|
1319
|
+
* renderer here. remove() is the panel's −: no argument retracts the last
|
|
1320
|
+
* keyframe, one per call, an index staying the core track's own remove.
|
|
1303
1321
|
*/
|
|
1304
1322
|
|
|
1305
1323
|
|
|
1324
|
+
// ── Placement — the + button's depth ───────────────────────────────────────
|
|
1325
|
+
//
|
|
1326
|
+
// The transport panel hands add() a depth in [0, 1] and expects a keyframe in
|
|
1327
|
+
// front of the camera. Writer-free placement: unproject the frustum's centre
|
|
1328
|
+
// through the view bag's P · V inverse and aim the pose along the bag's eye
|
|
1329
|
+
// frame (E = V⁻¹, its columns right / up / back). The depth is NDC-linear, as
|
|
1330
|
+
// the p5 transport's was — 0 the near plane's centre, 1 the far plane's — so
|
|
1331
|
+
// every bridge's own ndcZMin maps it without the caller knowing the API.
|
|
1332
|
+
|
|
1333
|
+
const _at = [0, 0, 0]; // the unprojected frustum centre
|
|
1334
|
+
const _dir = [0, 0, 0]; // the camera's forward
|
|
1335
|
+
const _up = [0, 0, 0]; // the camera's up
|
|
1336
|
+
|
|
1337
|
+
/**
|
|
1338
|
+
* Append a pose at the centre of the installed camera's frustum, `depth` of
|
|
1339
|
+
* the way from the near plane to the far one.
|
|
1340
|
+
*
|
|
1341
|
+
* @param {object} host
|
|
1342
|
+
* @param {function} add The track's core add, already bound.
|
|
1343
|
+
* @param {number} [depth] NDC-linear depth in [0, 1]; clamped, absent or NaN
|
|
1344
|
+
* reads the panel's own default, 0.5.
|
|
1345
|
+
* @returns {boolean} false when no camera is installed (a stale view bag).
|
|
1346
|
+
*/
|
|
1347
|
+
function _addInFront(host, add, depth) {
|
|
1348
|
+
const view = host.view;
|
|
1349
|
+
if (!view || view.stale) return false;
|
|
1350
|
+
const d = Number.isFinite(depth) ? (depth < 0 ? 0 : depth > 1 ? 1 : depth) : 0.5;
|
|
1351
|
+
const ndcZ = view.ndcZMin + d * (1 - view.ndcZMin);
|
|
1352
|
+
mapLocation(_at, 0, 0, ndcZ, NDC, WORLD, view, view.vp, view.ndcZMin);
|
|
1353
|
+
const e = view.mat4Eye; // columns: right · up · back · eye
|
|
1354
|
+
_dir[0] = -e[8]; _dir[1] = -e[9]; _dir[2] = -e[10];
|
|
1355
|
+
_up[0] = e[4]; _up[1] = e[5]; _up[2] = e[6];
|
|
1356
|
+
add({ pos: [_at[0], _at[1], _at[2]], rot: { dir: _dir, up: _up } }, { deduplicate: false });
|
|
1357
|
+
return true;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1306
1360
|
// ── Players ────────────────────────────────────────────────────────────────
|
|
1307
1361
|
|
|
1362
|
+
// remove(): the panel's −, retracting the last authored keyframe — one per
|
|
1363
|
+
// click, either kind of track. An index stays the core track's own remove, so
|
|
1364
|
+
// a caller editing by index is unaffected; an empty track removes nothing.
|
|
1365
|
+
function _wireRemove(track) {
|
|
1366
|
+
const coreRemove = track.remove.bind(track);
|
|
1367
|
+
track.remove = function (index) {
|
|
1368
|
+
return coreRemove(index == null ? track.keyframes.length - 1 : index);
|
|
1369
|
+
};
|
|
1370
|
+
return track;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1308
1373
|
function _wirePoseTrack(host, track) {
|
|
1309
1374
|
let player = null;
|
|
1310
1375
|
track._onActivate = () => {
|
|
@@ -1324,12 +1389,36 @@ function poseTrack(host, opts) {
|
|
|
1324
1389
|
const o = opts || {};
|
|
1325
1390
|
const track = new PoseTrack();
|
|
1326
1391
|
_wirePoseTrack(host, track);
|
|
1392
|
+
|
|
1393
|
+
// add(depth): the transport's + (see the module header). A spec, or an array
|
|
1394
|
+
// of specs, is the core track's own; no argument — the panel with its depth
|
|
1395
|
+
// row hidden — places at the panel's default depth, 0.5.
|
|
1396
|
+
const coreAdd = track.add.bind(track);
|
|
1397
|
+
let warned = false;
|
|
1398
|
+
track.add = function (spec, addOpts) {
|
|
1399
|
+
if (spec != null && typeof spec !== 'number') return coreAdd(spec, addOpts);
|
|
1400
|
+
if (!_addInFront(host, coreAdd, spec) && !warned) {
|
|
1401
|
+
warned = true;
|
|
1402
|
+
console.warn('[host] poseTrack: add(depth) places a pose against the installed camera, and the view bag is stale — no keyframe added. Call the bridge\'s setCamera first.');
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1327
1406
|
if (o.handles) track.handles = new TrackHandles(host, track, o.handles, false);
|
|
1328
|
-
return track;
|
|
1407
|
+
return _wireRemove(track);
|
|
1329
1408
|
}
|
|
1330
1409
|
|
|
1331
1410
|
/**
|
|
1332
|
-
* A core CameraTrack evaluating into the camera state `cam` while it plays
|
|
1411
|
+
* A core CameraTrack evaluating into the camera state `cam` while it plays, and
|
|
1412
|
+
* once when it stops, so the camera rests on the path.
|
|
1413
|
+
*
|
|
1414
|
+
* The write is bounded by playback on purpose: while a track is stopped the
|
|
1415
|
+
* camera it names belongs to whoever else drives it — the reader's orbit, the
|
|
1416
|
+
* figure's own camera code — and a factory writing every frame would fight
|
|
1417
|
+
* them. A consumer that renders *from* the state therefore derives it: evaluate
|
|
1418
|
+
* the track once per frame, after the tick that advances the transport. A seek,
|
|
1419
|
+
* a dragged keyframe and a capture reach the state that way too, and the
|
|
1420
|
+
* evaluation repeats the player's own write harmlessly while the track plays.
|
|
1421
|
+
*
|
|
1333
1422
|
* @param {object} host
|
|
1334
1423
|
* @param {object} cam A camera state ({ eye, center, up, fov, halfHeight, near, far }).
|
|
1335
1424
|
* @param {{ handles?:boolean|object }} [opts]
|
|
@@ -1344,7 +1433,9 @@ function cameraTrack(host, cam, opts) {
|
|
|
1344
1433
|
|
|
1345
1434
|
const coreAdd = track.add.bind(track);
|
|
1346
1435
|
track.add = function (spec, addOpts) {
|
|
1347
|
-
|
|
1436
|
+
// No spec, or the transport panel's depth: a camera keyframe *is* the
|
|
1437
|
+
// camera, so there is nothing a placement depth could move — capture.
|
|
1438
|
+
if (spec == null || typeof spec === 'number') {
|
|
1348
1439
|
if (!state) return;
|
|
1349
1440
|
spec = state;
|
|
1350
1441
|
} else if (Array.isArray(spec)) {
|
|
@@ -1373,7 +1464,7 @@ function cameraTrack(host, cam, opts) {
|
|
|
1373
1464
|
track._onDeactivate = () => { host.players.remove(player); apply(); };
|
|
1374
1465
|
|
|
1375
1466
|
if (o.handles) track.handles = new TrackHandles(host, track, o.handles, true);
|
|
1376
|
-
return track;
|
|
1467
|
+
return _wireRemove(track);
|
|
1377
1468
|
}
|
|
1378
1469
|
|
|
1379
1470
|
// ── TrackHandles ───────────────────────────────────────────────────────────
|
|
@@ -2417,7 +2508,7 @@ function raster(draw, w, h, opts) {
|
|
|
2417
2508
|
* class — a CSS class beside the layer's own 'host-label', frame — a
|
|
2418
2509
|
* transient label that lives for the frame it was set in: a gizmo re-sets
|
|
2419
2510
|
* it every draw and tick() removes it once a frame passes without. A label
|
|
2420
|
-
* whose
|
|
2511
|
+
* whose window depth leaves [0, 1] or whose anchor leaves the canvas is
|
|
2421
2512
|
* hidden, not clamped. Elements are reused by id; tick() writes transforms
|
|
2422
2513
|
* only, text when the string changed.
|
|
2423
2514
|
*/
|
|
@@ -2610,7 +2701,7 @@ function createLabels(host, opts) {
|
|
|
2610
2701
|
* what is still draining, so do home() and enabled = false. The default is
|
|
2611
2702
|
* 0.2 s; 0 makes the orbit exact.
|
|
2612
2703
|
*
|
|
2613
|
-
*
|
|
2704
|
+
* Canvas y runs down; which world direction that is depends on the
|
|
2614
2705
|
* projection installed in the view bag: a y-up projection (GL's, tree's
|
|
2615
2706
|
* default) maps a downward drag to the eye's −up, a y-flipped one (p5's,
|
|
2616
2707
|
* mat4Proj[5] < 0) to +up. The orbit reads that sign off the bag each
|
|
@@ -2725,7 +2816,7 @@ function createOrbit(host, cam, opts) {
|
|
|
2725
2816
|
let moved = false;
|
|
2726
2817
|
let az = 0, el = 0, px = 0, py = 0, ld = 0; // this frame's step
|
|
2727
2818
|
const view = host.view;
|
|
2728
|
-
const ys = view.mat4Proj[5] < 0 ? -1 : 1; //
|
|
2819
|
+
const ys = view.mat4Proj[5] < 0 ? -1 : 1; // canvas-down is the eye's −up under a y-up projection, +up under p5's flip
|
|
2729
2820
|
if (a !== null && b === null) {
|
|
2730
2821
|
const t = tracked.get(a.id);
|
|
2731
2822
|
if (t) {
|