@nakednous/tree 0.0.2 → 0.0.4

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,302 @@ 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
+ ```
57
+
58
+ Interpolation modes:
59
+
60
+ ```js
61
+ track.posInterp = 'catmullrom' // default — centripetal Catmull-Rom
62
+ track.posInterp = 'linear'
63
+
64
+ track.rotInterp = 'slerp' // default — constant angular velocity
65
+ track.rotInterp = 'nlerp' // normalised lerp; cheaper, slightly non-constant
66
+ ```
67
+
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.
69
+
70
+ `add()` accepts flexible specs. Top-level forms:
71
+
72
+ ```js
73
+ track.add({ pos, rot, scl }) // explicit TRS — rot accepts any form below
74
+ track.add({ mMatrix: mat4 }) // decompose a column-major model matrix into TRS
75
+ track.add([ spec, spec, ... ]) // bulk
76
+ ```
77
+
78
+ `rot` sub-forms — all normalised internally:
79
+
80
+ ```js
81
+ rot: [x,y,z,w] // raw quaternion
82
+ rot: { axis:[x,y,z], angle } // axis-angle
83
+ rot: { dir:[x,y,z], up?:[x,y,z] } // look direction (−Z forward)
84
+ rot: { euler:[rx,ry,rz], order?:'YXZ' } // intrinsic Euler angles (radians)
85
+ // orders: YXZ (default), XYZ, ZYX,
86
+ // ZXY, XZY, YZX
87
+ // extrinsic ABC = intrinsic CBA
88
+ rot: { from:[x,y,z], to:[x,y,z] } // shortest-arc between directions
89
+ rot: { mat3: Float32Array|Array } // column-major 3×3 rotation matrix
90
+ rot: { eMatrix: mat4 } // rotation block of an eye matrix
91
+ ```
92
+
93
+ ---
94
+
95
+ ### CameraTrack — lookat keyframe animation
96
+
97
+ 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.
98
+
99
+ ```js
100
+ import { CameraTrack } from '@nakednous/tree'
101
+
102
+ const track = new CameraTrack()
103
+ track.add({ eye:[0,0,500], center:[0,0,0] })
104
+ track.add({ eye:[300,-150,0], center:[0,0,0] })
105
+ track.play({ loop: true, duration: 90 })
106
+
107
+ // per-frame — zero allocation
108
+ const out = { eye:[0,0,0], center:[0,0,0], up:[0,1,0] }
109
+ track.tick()
110
+ track.eval(out)
111
+ // apply: cam.camera(out.eye[0],out.eye[1],out.eye[2],
112
+ // out.center[0],out.center[1],out.center[2],
113
+ // out.up[0],out.up[1],out.up[2])
114
+ ```
115
+
116
+ Interpolation modes:
117
+
118
+ ```js
119
+ track.eyeInterp = 'catmullrom' // default
120
+ track.eyeInterp = 'linear'
121
+
122
+ track.centerInterp = 'linear' // default — suits fixed lookat targets
123
+ track.centerInterp = 'catmullrom' // smoother when center is also moving freely
124
+ ```
125
+
126
+ `add()` accepts:
42
127
 
43
- ### Animation tracks
128
+ ```js
129
+ track.add({ eye, center, up? }) // explicit lookat; up defaults to [0,1,0]
130
+ track.add({ vMatrix: mat4 }) // view matrix (world→eye); eye reconstructed
131
+ track.add({ eMatrix: mat4 }) // eye matrix (eye→world); eye read from col3
132
+ track.add([ spec, spec, ... ]) // bulk
133
+ ```
134
+
135
+ Note: both matrix forms default `up` to `[0,1,0]`. The matrix col1 (up_ortho) is intentionally not used — it differs from the hint for upright cameras and would shift orbitControl's orbit reference. Use `capturePose()` (p5.tree bridge) when the real up hint is needed.
44
136
 
45
- Keyframe animation with playback control.
137
+ ---
46
138
 
47
- Tracks support:
139
+ ### Shared Track transport
140
+
141
+ Both `PoseTrack` and `CameraTrack` extend `Track`, which holds all transport machinery:
142
+
143
+ ```js
144
+ track.play({ duration, loop, pingPong, rate, onPlay, onEnd, onStop })
145
+ track.stop([rewind]) // rewind=true seeks to origin on stop
146
+ track.reset() // clear all keyframes and stop
147
+ track.seek(t) // normalised position [0, 1]
148
+ track.time() // → number ∈ [0, 1]
149
+ track.info() // → { keyframes, segments, seg, f, playing, loop, ... }
150
+ track.tick() // advance cursor by rate — returns playing state
151
+ track.add(spec) // append keyframe(s)
152
+ track.set(i, spec) // replace keyframe at index
153
+ track.remove(i) // remove keyframe at index
154
+
155
+ track.playing // boolean
156
+ track.loop // boolean
157
+ track.pingPong // boolean
158
+ track.rate // get/set — never starts/stops playback
159
+ track.duration // frames per segment
160
+ track.keyframes // raw array
161
+ ```
48
162
 
49
- * interpolation between poses
50
- * rate control
51
- * seeking
52
- * looping and ping-pong modes
163
+ Hook firing order:
164
+ ```
165
+ play() → onPlay → _onActivate
166
+ tick() → onEnd → _onDeactivate (once mode, at boundary)
167
+ stop() → onStop → _onDeactivate
168
+ reset() → onStop → _onDeactivate
169
+ ```
53
170
 
54
- Tracks are renderer-agnostic and can drive cameras, objects, or any transform-like structure.
171
+ One-keyframe behaviour: `play()` with exactly one keyframe snaps `eval()` to that keyframe without setting `playing = true` and without firing hooks.
55
172
 
56
173
  ---
57
174
 
58
- ### Scene-space queries
175
+ ### Coordinate-space mapping
176
+
177
+ `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.
178
+
179
+ **Spaces:** `WORLD`, `EYE`, `SCREEN`, `NDC`, `MODEL`, `MATRIX` (custom frame).
59
180
 
60
- Utilities for converting between coordinate spaces.
181
+ **NDC convention:** `WEBGL = -1` (z ∈ [−1,1]), `WEBGPU = 0` (z ∈ [0,1]).
61
182
 
62
- Common queries include:
183
+ ```js
184
+ import { mapLocation, mapDirection, WORLD, SCREEN, WEBGL } from '@nakednous/tree'
63
185
 
64
- * mapping locations between spaces
65
- * mapping directions between spaces
66
- * deriving projection parameters
186
+ const out = new Float32Array(3)
187
+ const m = {
188
+ pMatrix: /* Float32Array(16) projection */,
189
+ vMatrix: /* Float32Array(16) — view (world→eye) */,
190
+ pvMatrix: /* pMatrix × vMatrix — optional, computed if absent */,
191
+ ipvMatrix: /* inv(pvMatrix) — optional, computed if absent */,
192
+ }
193
+ const vp = [0, height, width, -height]
194
+
195
+ mapLocation(out, worldX, worldY, worldZ, WORLD, SCREEN, m, vp, WEBGL)
196
+ ```
197
+
198
+ 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.
67
199
 
68
200
  ---
69
201
 
70
- ### Visibility tests
202
+ ### Visibility testing
203
+
204
+ [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.
71
205
 
72
- Frustum-based visibility checks for:
206
+ ```js
207
+ import { frustumPlanes, pointVisibility, sphereVisibility, boxVisibility,
208
+ VISIBLE, SEMIVISIBLE, INVISIBLE } from '@nakednous/tree'
73
209
 
74
- * points
75
- * spheres
76
- * axis-aligned boxes
210
+ const planes = new Float64Array(24)
211
+ frustumPlanes(planes, posX, posY, posZ, vdX, vdY, vdZ,
212
+ upX, upY, upZ, rtX, rtY, rtZ,
213
+ ortho, near, far, left, right, top, bottom)
77
214
 
78
- These functions operate on numeric camera descriptions and do not depend on renderer classes.
215
+ sphereVisibility(planes, cx, cy, cz, radius) // VISIBLE | SEMIVISIBLE | INVISIBLE
216
+ boxVisibility(planes, x0,y0,z0, x1,y1,z1)
217
+ pointVisibility(planes, px, py, pz)
218
+ ```
219
+
220
+ Three-state result: `VISIBLE` (fully inside), `SEMIVISIBLE` (intersecting), `INVISIBLE` (fully outside).
79
221
 
80
222
  ---
81
223
 
82
- ### Shader-space helpers
224
+ ### Quaternion and matrix math
83
225
 
84
- Projection queries and coordinate transforms useful when developing GPU shaders and rendering pipelines.
226
+ Exported individually for use in hot paths.
227
+
228
+ **Quaternions** — `[x,y,z,w]` w-last:
229
+
230
+ ```
231
+ qSet qCopy qDot qNormalize qNegate qMul
232
+ qSlerp qNlerp
233
+ qFromAxisAngle qFromLookDir qFromRotMat3x3 qFromMat4 qToMat4
234
+ quatToAxisAngle
235
+ ```
236
+
237
+ **Spline / vector:** `catmullRomVec3`, `lerpVec3`
238
+
239
+ **Mat4:**
240
+ ```
241
+ mat4Mul mat4Invert mat4Transpose mat4MulPoint
242
+ mat3NormalFromMat4 mat4Location mat3Direction
243
+ ```
244
+
245
+ **TRS ↔ mat4:** `transformToMat4`, `mat4ToTransform`
246
+
247
+ **Projection queries** (read from a projection mat4 — no renderer needed):
248
+ ```
249
+ projIsOrtho projNear projFar projFov projHfov
250
+ projLeft projRight projTop projBottom
251
+ ```
252
+
253
+ **Pixel ratio:** `pixelRatio(proj, vpH, eyeZ, ndcZMin)` — world-units-per-pixel at a given depth, handles both perspective and orthographic.
85
254
 
86
255
  ---
87
256
 
88
- ## Philosophy
257
+ ### Constants
89
258
 
90
- The backend follows a **query-oriented design**.
259
+ ```js
260
+ // Coordinate spaces
261
+ WORLD, EYE, NDC, SCREEN, MODEL, MATRIX
91
262
 
92
- Instead of managing scene graphs or renderer state, it focuses on answering spatial and animation questions:
263
+ // NDC Z convention
264
+ WEBGL // −1 (z ∈ [−1, 1])
265
+ WEBGPU // 0 (z ∈ [0, 1])
93
266
 
94
- * mapping coordinates between spaces
95
- * interpolating transforms over time
96
- * determining whether objects are visible
267
+ // Visibility results
268
+ INVISIBLE, VISIBLE, SEMIVISIBLE
97
269
 
98
- This keeps the core portable and easy to integrate with different engines.
270
+ // Basis vectors (frozen)
271
+ ORIGIN, i, j, k, _i, _j, _k
272
+ ```
99
273
 
100
274
  ---
101
275
 
102
- ## Use cases
276
+ ## Performance contract
103
277
 
104
- Typical uses include:
278
+ All hot-path functions follow an **out-first, zero-allocation** contract:
105
279
 
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
280
+ - `out` is the first parameter the caller owns the buffer
281
+ - the function writes into `out` and returns it
282
+ - `null` is returned on degeneracy (singular matrix, etc.)
283
+ - no heap allocations per call
284
+
285
+ ```js
286
+ // allocate once
287
+ const out = new Float32Array(3)
288
+ const pvMatrix = new Float32Array(16)
289
+ const ipvMatrix= new Float32Array(16)
290
+
291
+ // per frame — zero allocation
292
+ mat4Mul(pvMatrix, proj, view)
293
+ mat4Invert(ipvMatrix, pvMatrix)
294
+ mapLocation(out, px, py, pz, WORLD, SCREEN,
295
+ { pMatrix: proj, vMatrix: view, pvMatrix, ipvMatrix }, vp, WEBGL)
296
+ ```
111
297
 
112
298
  ---
113
299
 
114
300
  ## Relationship to `p5.tree`
115
301
 
116
- `p5.tree` provides a p5.js integration layer built on top of this backend.
302
+ [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
303
 
118
- It handles renderer-specific tasks such as retrieving camera matrices and applying transforms to p5 objects, while `@nakednous/tree` provides the underlying algorithms.
304
+ `@nakednous/tree` provides the algorithms. The bridge provides the wiring.
119
305
 
120
306
  ---
121
307
 
122
308
  ## License
123
309
 
124
- GPL-3.0-only
310
+ GPL-3.0-only
125
311
  © JP Charalambos