@nakednous/host 0.0.1 → 0.0.3

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
@@ -1,13 +1,13 @@
1
1
  # `@nakednous/host`
2
2
 
3
3
  DOM transport for `@nakednous/tree` — pointer input with per-pointer capture, the interactive
4
- handle controller and pointer router, players and a frame loop, 6-DOF device streams (WebHID,
5
- Gamepad), image and video sources, a DOM label overlay — **zero renderer**, pure vanilla DOM.
4
+ handle controller and pointer router, players and a frame loop, helm and track factories, 6-DOF
5
+ device streams (WebHID, Gamepad), image and video sources, a DOM label overlay and the orbit —
6
+ **zero renderer**, pure vanilla DOM.
6
7
 
7
- > **Status: 0.0.x.** Shipped: the context (`createHost`), the view bag, the pointer source,
8
- > players and the frame loop, the canvas observer, `Handle` and `PointerRouter`, the helm and
9
- > track factories with `TrackHandles`. Still to land: device streams, media, the label overlay
10
- > and the orbit.
8
+ > **Status: 0.0.x.** The whole surface below is shipped. What a bridge draws (a handle's locus,
9
+ > a track's path, a helm's rig) lives in `twgl.tree` / `p5.tree`; the host only computes, through
10
+ > `@nakednous/tree`, and listens.
11
11
 
12
12
  ---
13
13
 
@@ -19,11 +19,19 @@ npm install @nakednous/host
19
19
 
20
20
  ```js
21
21
  import { createHost } from '@nakednous/host'
22
+ import { createCamera, PLANE } from '@nakednous/tree'
22
23
 
23
- const host = createHost(canvas) // one per canvas
24
- host.view.setCamera(cam) // a @nakednous/tree camera state
24
+ const cam = createCamera({ eye: [0, 0, 10] })
25
+ const host = createHost(canvas, {
26
+ onFrame(dt) {
27
+ host.view.setCamera(cam) // the matrices every pick and label reads
28
+ if (!h.update()) orbit.update() // the handle gate falls through to the orbit
29
+ draw()
30
+ host.labels.tick()
31
+ },
32
+ })
25
33
  const h = host.handle({ constraint: PLANE, normal: [0, 0, 1] })
26
- // each frame: if (!h.update()) orbit(); …; host.tick(dt)
34
+ const orbit = host.orbit(cam)
27
35
  ```
28
36
 
29
37
  ---
@@ -40,7 +48,7 @@ WebGL or WebGPU — it owns a canvas element's input and the frame clock, and ha
40
48
  ▼
41
49
  twgl.tree · webgpu.tree ← bridge: the GPU, thinly
42
50
  │
43
- ├── @nakednous/host ← this package: pointer, handles, tracks, helms, players, streams, media
51
+ ├── @nakednous/host ← this package: pointer, handles, tracks, helms, players, streams, media, labels, orbit
44
52
  │ │
45
53
  │ └── @nakednous/tree
46
54
  │
@@ -54,6 +62,97 @@ and nothing flows back into tree.
54
62
 
55
63
  ---
56
64
 
65
+ ## The surface
66
+
67
+ One context per canvas. Every construct is a method on it, takes its one positional
68
+ dependency first and an options object last, and registers with the context so
69
+ `host.dispose()` releases everything.
70
+
71
+ | member | what it is |
72
+ |---|---|
73
+ | `createHost(canvas, opts)` | the context: `ndcZMin` (WEBGL default, or WEBGPU), `onFrame(dt, host)` for the host's own loop, `onSize(w, h, dpr)` for the bridge's buffer, `raf` / `caf` overrides |
74
+ | `host.view` | the view bag — `mat4Proj` · `mat4View` · `mat4PV` · `mat4PVInv` · `mat4Eye`, `vp`, `ndcZMin`, `stale`; `set(P, V)`, `setCamera(cam)`, `resize(w, h)` |
75
+ | `host.pointer` | the pointer source — presses queued per frame, per-pointer entries with a move counter, claims that capture on the canvas, Esc cancel, `flush()` |
76
+ | `host.players` | the registry ticked each frame — `{ tick(dt) → boolean }`, removed on `false` |
77
+ | `host.width` · `host.height` · `host.dpr` | the canvas observer's logical size and pixel ratio |
78
+ | `host.dt` · `host.clock()` | last frame period and seconds since creation |
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
+ | `host.router(handles, opts)` | `PointerRouter` — one shared pick over overlapping handles, nearest proxy wins; shared hover |
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 |
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
+ | `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
+ | `host.labels` · `host.hasLabels` | the label layer, created on first access — `set(id, text, x, y, z, opts)` at a world anchor, `setScreen(id, text, sx, sy, opts)` in canvas px, `{ frame: true }` for a label that lives one frame, `tick()`; `hasLabels` probes without creating |
86
+ | `host.orbit(cam, opts)` | the fall-through gesture — one pointer orbits, two pan and dolly, the wheel dollies; `update()` reports whether the camera moved; the vertical sense follows the bag's projection (y-up GL, y-flipped p5) |
87
+ | `host.tick(dt)` | external-loop mode: tick the players; the caller flushes the pointer once its consumers ran |
88
+ | `host.register(c)` · `host.unregister(c)` · `host.dispose()` | the lifecycle |
89
+
90
+ The module-level functions behind the methods are exported too (`createView`, `createPointer`,
91
+ `createPlayers`, `createLoop`, `observeCanvas`, `Handle`, `PointerRouter`, `cameraHelm`,
92
+ `poseHelm`, `helmBasis`, `poseTrack`, `cameraTrack`, `TrackHandles`, `createHid`,
93
+ `createGamepad`, `decodeSpaceNavigator`, `loadImage`, `createVideo`, `raster`, `createLabels`,
94
+ `createOrbit`), so an adapter can compose them on its own context.
95
+
96
+ ---
97
+
98
+ ## The frame
99
+
100
+ Two loop modes. With `{ onFrame }` the host runs `requestAnimationFrame`; without it the
101
+ application — or the p5 adapter from its `predraw` — calls `host.tick(dt)` and flushes the
102
+ pointer itself. Inside a frame the order is fixed, because each step reads what the previous
103
+ one wrote:
104
+
105
+ 1. **players tick** — a playing track writes its camera state or pose, a helm integrates the
106
+ stream it was fed, a stream polls its device.
107
+ 2. **install the camera** — `host.view.setCamera(cam)` (or `set(P, V)` from renderer state):
108
+ the matrices every pick, solve, label and orbit reads this frame.
109
+ 3. **handles and routers update** — presses are picked, drags solved, claims taken.
110
+ 4. **the orbit falls through** — `if (!h.update()) orbit.update()`: only unclaimed pointers
111
+ reach it.
112
+ 5. **draw** — the bridge reads the camera state, the handles' values, the tracks' paths.
113
+ 6. **labels tick** — world anchors projected through the current bag, transforms written.
114
+ 7. **pointer flush** — the press queue empties, released pointers leave.
115
+
116
+ `dt` is seconds, clamped to 50 ms so a stalled tab never integrates a leap.
117
+
118
+ ---
119
+
120
+ ## Seams
121
+
122
+ | seam | direction | contract |
123
+ |---|---|---|
124
+ | view bag | bridge → host | the bridge's `setCamera(cam)` also calls `host.view.setCamera(cam)`; without a host the bridge keeps its own matrices |
125
+ | external tick | adapter → host | `host.tick(dt)` from the adapter's predraw; `host.labels.tick()` after the adapter set `host.view` from renderer state |
126
+ | proxies · locus | tree → host → bridge | constraint objects carry `proxy` and `locus`; the host tests, the bridge draws |
127
+ | label anchors | tree → bridge → host | a generator writes `out.labels`; the bridge forwards each to `labels.set` |
128
+ | rate stream | host → tree | `helm.feed(lin, ang)` per tick, raw device units; the helm's profile scales |
129
+ | camera state | host ↔ tree ↔ bridge | plain data: the orbit, a track, a helm write it; `setCamera(cam)` reads it |
130
+ | bridge camera | host → bridge | a camera helm or track exposes `_onApply(cam)`, fired after each write, for an adapter that mirrors the state into its own camera |
131
+ | construction | host → bridge | `TrackHandles` builds members through `_makeHandle(opts)` / `_makeRouter(opts)`, so a subclass makes members it can draw |
132
+ | device interfaces | test → host | `hid`, `gamepads`, `document`, `media`, `raf` / `caf` options replace the globals |
133
+
134
+ ---
135
+
136
+ ## Conventions
137
+
138
+ - Options object last; the context's factories take their one positional dependency first.
139
+ - Out-first, zero allocation in every per-frame path: `value(out, …)`, the streams' triples,
140
+ the label positions, the view bag's matrices — caller- or host-owned buffers allocated once.
141
+ - Every DOM listener attached by the context is removed by `dispose()`.
142
+ - Errors are `console.error` with a `[host]` prefix and a fallback, never a throw.
143
+ - Library code: semicolons, JSDoc on every public member, `@module host/<file>`.
144
+
145
+ ---
146
+
147
+ ## Development
148
+
149
+ ```bash
150
+ npm test # node:test — DOM pieces against a minimal stub, math through tree
151
+ npm run build # rollup → dist/index.js
152
+ ```
153
+
154
+ ---
155
+
57
156
  ## License
58
157
 
59
158
  AGPL-3.0-only