@nakednous/tree 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,8 +1,6 @@
1
1
  # `@nakednous/tree`
2
2
 
3
- Backend for **animations, scene-space queries, visibility tests, and shader-space transforms**.
4
-
5
- The package is **renderer-agnostic**, has **no dependencies**, and can run in both browser and server environments.
3
+ Pure numeric core for animation, coordinate-space mapping, and visibility **zero dependencies**, runs anywhere.
6
4
 
7
5
  ---
8
6
 
@@ -12,114 +10,276 @@ The package is **renderer-agnostic**, has **no dependencies**, and can run in bo
12
10
  npm install @nakednous/tree
13
11
  ```
14
12
 
15
- ```javascript
13
+ ```js
16
14
  import * as tree from '@nakednous/tree'
17
15
  ```
18
16
 
19
17
  ---
20
18
 
21
- ## Role
19
+ ## Architecture
22
20
 
23
- `@nakednous/tree` is intended to sit behind a host library or engine.
21
+ `@nakednous/tree` is the bottom layer of a three-package stack. It knows nothing about renderers, the DOM, or p5 — it operates on plain arrays and `Float32Array` buffers throughout.
24
22
 
25
- ```text
26
- application / renderer
27
-
28
-
29
- integration layer
30
- (e.g. p5.tree)
31
-
32
-
33
- @nakednous/tree
23
+ ```
24
+ application
25
+
26
+
27
+ p5.tree.js ← bridge: wires tree + ui into p5.js v2
28
+
29
+ ├── @nakednous/ui ← DOM param panels, transport controls
30
+
31
+ └── @nakednous/tree ← this package: math, spaces, animation, visibility
34
32
  ```
35
33
 
36
- The host layer provides camera state, projection parameters, and scene data.
37
- `@nakednous/tree` performs the underlying computations: animation interpolation, spatial mapping, and visibility testing.
34
+ The dependency direction is strict: `@nakednous/tree` never imports from the bridge or the DOM layer. This is what lets the same `PoseTrack` that drives a camera path also animate any object — headless, server-side, or in a future renderer.
38
35
 
39
36
  ---
40
37
 
41
- ## Capabilities
38
+ ## What it does
39
+
40
+ ### PoseTrack — TRS keyframe animation
41
+
42
+ A renderer-agnostic state machine for `{ pos, rot, scl }` keyframe sequences. Rotation is stored as `[x,y,z,w]` quaternions (w-last, glTF layout).
43
+
44
+ ```js
45
+ import { PoseTrack } from '@nakednous/tree'
46
+
47
+ const track = new PoseTrack()
48
+ track.add({ pos: [0, 0, 0], rot: [0,0,0,1], scl: [1,1,1] })
49
+ track.add({ pos: [100, 50, 0], rot: [0,0,0,1], scl: [2,1,1] })
50
+ track.play({ duration: 60, loop: true })
51
+
52
+ // per-frame — zero allocation
53
+ const out = { pos: [0,0,0], rot: [0,0,0,1], scl: [1,1,1] }
54
+ track.tick()
55
+ track.eval(out) // writes interpolated TRS into out
56
+ ```
42
57
 
43
- ### Animation tracks
58
+ Interpolation modes:
44
59
 
45
- Keyframe animation with playback control.
60
+ ```js
61
+ track.posInterp = 'catmullrom' // default — centripetal Catmull-Rom
62
+ track.posInterp = 'linear'
46
63
 
47
- Tracks support:
64
+ track.rotInterp = 'slerp' // default — constant angular velocity
65
+ track.rotInterp = 'nlerp' // normalised lerp; cheaper, slightly non-constant
66
+ ```
48
67
 
49
- * interpolation between poses
50
- * rate control
51
- * seeking
52
- * looping and ping-pong modes
68
+ Playback features: signed `rate` (negative reverses), `loop`, `pingPong`, `seek(t)` scrubbing, and lifecycle hooks (`onPlay`, `onEnd`, `onStop`). `_onActivate` / `_onDeactivate` are lib-space hooks for the host layer's draw-loop registry — not for user code.
53
69
 
54
- Tracks are renderer-agnostic and can drive cameras, objects, or any transform-like structure.
70
+ Keyframe `rot` input is flexible the parser normalises all forms:
71
+ - raw `[x,y,z,w]` quaternion
72
+ - `{ axis: [x,y,z], angle }` axis-angle
73
+ - `{ dir: [x,y,z], up? }` look-direction
74
+ - `{ view: mat4 }` from view matrix rotation block
75
+ - `{ eye, center, up? }` lookat shorthand
55
76
 
56
77
  ---
57
78
 
58
- ### Scene-space queries
79
+ ### CameraTrack — lookat keyframe animation
80
+
81
+ A renderer-agnostic state machine for `{ eye, center, up }` lookat keyframes. Each field is independently interpolated — eye and center along their own paths, up nlerped on the unit sphere.
82
+
83
+ ```js
84
+ import { CameraTrack } from '@nakednous/tree'
85
+
86
+ const track = new CameraTrack()
87
+ track.add({ eye:[0,0,500], center:[0,0,0] })
88
+ track.add({ eye:[300,-150,0], center:[0,0,0] })
89
+ track.play({ loop: true, duration: 90 })
90
+
91
+ // per-frame — zero allocation
92
+ const out = { eye:[0,0,0], center:[0,0,0], up:[0,1,0] }
93
+ track.tick()
94
+ track.eval(out)
95
+ // apply: cam.camera(out.eye[0],out.eye[1],out.eye[2],
96
+ // out.center[0],out.center[1],out.center[2],
97
+ // out.up[0],out.up[1],out.up[2])
98
+ ```
99
+
100
+ Interpolation modes:
101
+
102
+ ```js
103
+ track.eyeInterp = 'catmullrom' // default
104
+ track.eyeInterp = 'linear'
105
+
106
+ track.centerInterp = 'linear' // default — suits fixed lookat targets
107
+ track.centerInterp = 'catmullrom' // smoother when center is also flying
108
+ ```
109
+
110
+ `add()` accepts:
111
+ - `{ eye, center, up? }` explicit lookat; `up` defaults to `[0,1,0]`
112
+ - `{ view: mat4 }` column-major view matrix; eye and forward extracted
113
+
114
+ ---
59
115
 
60
- Utilities for converting between coordinate spaces.
116
+ ### Shared Track transport
117
+
118
+ Both `PoseTrack` and `CameraTrack` extend `Track`, which holds all transport machinery:
119
+
120
+ ```js
121
+ track.play({ duration, loop, pingPong, rate, onPlay, onEnd, onStop })
122
+ track.stop([rewind]) // rewind=true seeks to origin on stop
123
+ track.reset() // clear all keyframes and stop
124
+ track.seek(t) // normalised position [0, 1]
125
+ track.time() // → number ∈ [0, 1]
126
+ track.info() // → { keyframes, segments, seg, f, playing, loop, ... }
127
+ track.tick() // advance cursor by rate — returns playing state
128
+ track.add(spec) // append keyframe(s)
129
+ track.set(i, spec) // replace keyframe at index
130
+ track.remove(i) // remove keyframe at index
131
+
132
+ track.playing // boolean
133
+ track.rate // get/set — never starts/stops playback
134
+ track.duration // frames per segment
135
+ track.keyframes // raw array
136
+ ```
61
137
 
62
- Common queries include:
138
+ Hook firing order:
139
+ ```
140
+ play() → onPlay → _onActivate
141
+ tick() → onEnd → _onDeactivate (once mode, at boundary)
142
+ stop() → onStop → _onDeactivate
143
+ reset() → onStop → _onDeactivate
144
+ ```
63
145
 
64
- * mapping locations between spaces
65
- * mapping directions between spaces
66
- * deriving projection parameters
146
+ One-keyframe behaviour: `play()` with exactly one keyframe snaps `eval()` to that keyframe without setting `playing = true` and without firing hooks.
67
147
 
68
148
  ---
69
149
 
70
- ### Visibility tests
150
+ ### Coordinate-space mapping
151
+
152
+ `mapLocation` and `mapDirection` convert points and vectors between any pair of named spaces. All work is done in flat scalar arithmetic — no objects created per call.
71
153
 
72
- Frustum-based visibility checks for:
154
+ **Spaces:** `WORLD`, `EYE`, `SCREEN`, `NDC`, `MODEL`, `MATRIX` (custom frame).
73
155
 
74
- * points
75
- * spheres
76
- * axis-aligned boxes
156
+ **NDC convention:** `WEBGL = -1` (z ∈ [−1,1]), `WEBGPU = 0` (z ∈ [0,1]).
77
157
 
78
- These functions operate on numeric camera descriptions and do not depend on renderer classes.
158
+ ```js
159
+ import { mapLocation, mapDirection, WORLD, SCREEN, WEBGL } from '@nakednous/tree'
160
+
161
+ const out = new Float32Array(3)
162
+ const m = {
163
+ proj: /* Float32Array(16) */,
164
+ view: /* Float32Array(16) */,
165
+ pv: /* proj × view */,
166
+ ipv: /* inv(pv) */,
167
+ }
168
+ const vp = [0, height, width, -height]
169
+
170
+ mapLocation(out, worldX, worldY, worldZ, WORLD, SCREEN, m, vp, WEBGL)
171
+ ```
172
+
173
+ The matrices bag `m` is assembled by the host (p5.tree reads live renderer state into it). All pairs are supported: WORLD↔EYE, WORLD↔SCREEN, WORLD↔NDC, EYE↔SCREEN, SCREEN↔NDC, WORLD↔MATRIX, and their reverses.
79
174
 
80
175
  ---
81
176
 
82
- ### Shader-space helpers
177
+ ### Visibility testing
178
+
179
+ [Frustum culling](https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling) against six planes. All functions take scalar inputs and a pre-filled `Float64Array(24)` planes buffer — zero allocations per test.
83
180
 
84
- Projection queries and coordinate transforms useful when developing GPU shaders and rendering pipelines.
181
+ ```js
182
+ import { frustumPlanes, pointVisibility, sphereVisibility, boxVisibility,
183
+ VISIBLE, SEMIVISIBLE, INVISIBLE } from '@nakednous/tree'
184
+
185
+ const planes = new Float64Array(24)
186
+ frustumPlanes(planes, posX, posY, posZ, vdX, vdY, vdZ,
187
+ upX, upY, upZ, rtX, rtY, rtZ,
188
+ ortho, near, far, left, right, top, bottom)
189
+
190
+ sphereVisibility(planes, cx, cy, cz, radius) // → VISIBLE | SEMIVISIBLE | INVISIBLE
191
+ boxVisibility(planes, x0,y0,z0, x1,y1,z1)
192
+ pointVisibility(planes, px, py, pz)
193
+ ```
194
+
195
+ Three-state result: `VISIBLE` (fully inside), `SEMIVISIBLE` (intersecting), `INVISIBLE` (fully outside).
85
196
 
86
197
  ---
87
198
 
88
- ## Philosophy
199
+ ### Quaternion and matrix math
200
+
201
+ Exported individually for use in hot paths.
89
202
 
90
- The backend follows a **query-oriented design**.
203
+ **Quaternions** `[x,y,z,w]` w-last:
91
204
 
92
- Instead of managing scene graphs or renderer state, it focuses on answering spatial and animation questions:
205
+ ```
206
+ qSet qCopy qDot qNormalize qNegate qMul
207
+ qSlerp qNlerp
208
+ qFromAxisAngle qFromLookDir qFromRotMat3x3 qFromMat4 qToMat4
209
+ quatToAxisAngle
210
+ ```
93
211
 
94
- * mapping coordinates between spaces
95
- * interpolating transforms over time
96
- * determining whether objects are visible
212
+ **Spline / vector:** `catmullRomVec3`, `lerpVec3`
97
213
 
98
- This keeps the core portable and easy to integrate with different engines.
214
+ **Mat4:**
215
+ ```
216
+ mat4Mul mat4Invert mat4Transpose mat4MulPoint
217
+ mat3NormalFromMat4 mat4Location mat3Direction
218
+ ```
219
+
220
+ **TRS ↔ mat4:** `transformToMat4`, `mat4ToTransform`
221
+
222
+ **Projection queries** (read from a projection mat4 — no renderer needed):
223
+ ```
224
+ projIsOrtho projNear projFar projFov projHfov
225
+ projLeft projRight projTop projBottom
226
+ ```
227
+
228
+ **Pixel ratio:** `pixelRatio(proj, vpH, eyeZ, ndcZMin)` — world-units-per-pixel at a given depth, handles both perspective and orthographic.
99
229
 
100
230
  ---
101
231
 
102
- ## Use cases
232
+ ### Constants
233
+
234
+ ```js
235
+ // Coordinate spaces
236
+ WORLD, EYE, NDC, SCREEN, MODEL, MATRIX
237
+
238
+ // NDC Z convention
239
+ WEBGL // −1 (z ∈ [−1, 1])
240
+ WEBGPU // 0 (z ∈ [0, 1])
103
241
 
104
- Typical uses include:
242
+ // Visibility results
243
+ INVISIBLE, VISIBLE, SEMIVISIBLE
105
244
 
106
- * animation backend for cameras or scene objects
107
- * coordinate-space mapping utilities
108
- * CPU-side frustum culling
109
- * shader development tooling
110
- * server-side scene analysis or preprocessing
245
+ // Basis vectors (frozen)
246
+ ORIGIN, i, j, k, _i, _j, _k
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Performance contract
252
+
253
+ All hot-path functions follow an **out-first, zero-allocation** contract:
254
+
255
+ - `out` is the first parameter — the caller owns the buffer
256
+ - the function writes into `out` and returns it
257
+ - `null` is returned on degeneracy (singular matrix, etc.)
258
+ - no heap allocations per call
259
+
260
+ ```js
261
+ // allocate once
262
+ const out = new Float32Array(3)
263
+ const pv = new Float32Array(16)
264
+ const ipv = new Float32Array(16)
265
+
266
+ // per frame — zero allocation
267
+ mat4Mul(pv, proj, view)
268
+ mat4Invert(ipv, pv)
269
+ mapLocation(out, px, py, pz, WORLD, SCREEN, { proj, view, pv, ipv }, vp, WEBGL)
270
+ ```
111
271
 
112
272
  ---
113
273
 
114
274
  ## Relationship to `p5.tree`
115
275
 
116
- `p5.tree` provides a p5.js integration layer built on top of this backend.
276
+ [p5.tree](https://github.com/VisualComputing/p5.tree) is the bridge layer. It reads live renderer state (camera matrices, viewport dimensions, NDC convention) and passes it to `@nakednous/tree` functions. It wires `PoseTrack` and `CameraTrack` to the p5 draw loop, exposes `createTrack` / `getCamera`, and provides `createPanel` for transport and parameter UIs.
117
277
 
118
- It handles renderer-specific tasks such as retrieving camera matrices and applying transforms to p5 objects, while `@nakednous/tree` provides the underlying algorithms.
278
+ `@nakednous/tree` provides the algorithms. The bridge provides the wiring.
119
279
 
120
280
  ---
121
281
 
122
282
  ## License
123
283
 
124
- GPL-3.0-only
284
+ GPL-3.0-only
125
285
  © JP Charalambos