@nakednous/tree 0.0.24 → 0.0.25
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 +5 -5
- package/dist/index.js +66 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -376,19 +376,19 @@ Three-state result: `VISIBLE` (fully inside), `SEMIVISIBLE` (intersecting), `INV
|
|
|
376
376
|
`handle.js` is the renderer-agnostic core of an interactive manipulator: ray-primitive intersections, az/el utilities, and a `Constraint` state machine. The `p5.tree` bridge wraps these into a draggable handle; this package supplies the math and the **contract** that makes the handle extensible.
|
|
377
377
|
|
|
378
378
|
```js
|
|
379
|
-
import { createConstraint, SPHERE, PLANE, AXIS, POINT, DIRECTION,
|
|
379
|
+
import { createConstraint, SPHERE, PLANE, AXIS, DIAL, POINT, DIRECTION,
|
|
380
380
|
raySphere, rayPlane, rayClosestPointOnAxis,
|
|
381
381
|
dirFromAzEl, azElFromDir } from '@nakednous/tree'
|
|
382
382
|
|
|
383
|
-
const c = createConstraint(SPHERE, { radius: 1 }) // or PLANE / AXIS
|
|
383
|
+
const c = createConstraint(SPHERE, { radius: 1 }) // or PLANE / AXIS / DIAL
|
|
384
384
|
const out = [0, 0, 0]
|
|
385
385
|
c.solve(ox,oy,oz, dx,dy,dz) // ray (working space) → canonical state; chainable
|
|
386
386
|
c.value(out, DIRECTION) // write the reported value into out(3)
|
|
387
387
|
```
|
|
388
388
|
|
|
389
|
-
`SPHERE` stores a unit direction (gimbal-free); `PLANE` / `AXIS` store a constrained point. `value` reports a `DIRECTION` (unit) or a `POINT` per kind. Ray primitives are out-first and assume a unit ray direction; `rayPlane` returns `Infinity` when the ray is parallel.
|
|
389
|
+
`SPHERE` stores a unit direction (gimbal-free); `PLANE` / `AXIS` store a constrained point; `DIAL` stores an accumulated angle θ (multi-turn winding preserved). `value` reports a `DIRECTION` (unit) or a `POINT` per kind. `aim(ax,ay,az[, zx,zy,zz])` re-aims the constraint basis in the working space — `PLANE` takes a new normal (point re-projected), `AXIS` a new direction (`t` preserved), `DIAL` a new plane normal plus optional θ=0 reference (θ preserved) — the seam the `p5.tree` bridge's deferred `from` frame drives. Ray primitives are out-first and assume a unit ray direction; `rayPlane` returns `Infinity` when the ray is parallel.
|
|
390
390
|
|
|
391
|
-
**Constraint contract (extension seam).** A constraint is any object exposing `kind`, `solve(ox,oy,oz, dx,dy,dz)`, `value(out, report)`, `seed(x,y,z)`, and optionally `scalar()` / `azEl(out2)`. The handle controller drives any conforming constraint, so a new kind — rotation, 6-DOF, or app-specific — implements this contract (portable, draw-free) plus a bridge-side locus draw, rather than forking the controller. The built-in `Constraint` is the reference implementation. Full design: [`handle-design.md`](./handle-design.md).
|
|
391
|
+
**Constraint contract (extension seam).** A constraint is any object exposing `kind`, `solve(ox,oy,oz, dx,dy,dz)`, `value(out, report)`, `seed(x,y,z)`, and optionally `scalar()` / `azEl(out2)` / `aim(ax,ay,az[, zx,zy,zz])`. The handle controller drives any conforming constraint, so a new kind — rotation, 6-DOF, or app-specific — implements this contract (portable, draw-free) plus a bridge-side locus draw, rather than forking the controller. The built-in `Constraint` is the reference implementation. Full design: [`handle-design.md`](./handle-design.md).
|
|
392
392
|
|
|
393
393
|
---
|
|
394
394
|
|
|
@@ -463,7 +463,7 @@ WEBGPU // 0 (z ∈ [0, 1])
|
|
|
463
463
|
INVISIBLE, VISIBLE, SEMIVISIBLE
|
|
464
464
|
|
|
465
465
|
// Manipulator constraint kinds & report modes
|
|
466
|
-
SPHERE, PLANE, AXIS
|
|
466
|
+
SPHERE, PLANE, AXIS, DIAL
|
|
467
467
|
POINT, DIRECTION
|
|
468
468
|
|
|
469
469
|
// Basis vectors (frozen)
|
package/dist/index.js
CHANGED
|
@@ -2506,7 +2506,9 @@ class CameraTrack extends Track {
|
|
|
2506
2506
|
* ── Extension contract ─────────────────────────────────────────────────────
|
|
2507
2507
|
* A constraint is any object exposing: `kind` (integer discriminant),
|
|
2508
2508
|
* `solve(ox,oy,oz, dx,dy,dz)`, `value(out, report)`, `seed(x,y,z)`, and
|
|
2509
|
-
* optionally `scalar()` / `azEl(out2)
|
|
2509
|
+
* optionally `scalar()` / `azEl(out2)` / `aim(ax,ay,az[, zx,zy,zz])` — the
|
|
2510
|
+
* basis re-aim seam the bridge's deferred `from` frame drives (§4.13).
|
|
2511
|
+
* The p5.tree handle controller drives
|
|
2510
2512
|
* any conforming constraint (lifecycle, frame conversion, bind, hooks, pick);
|
|
2511
2513
|
* a new kind — 6-DOF, or app-specific — implements this contract here
|
|
2512
2514
|
* (portable, draw-free) plus a bridge-side locus/pick draw (`drawLocus` /
|
|
@@ -2735,22 +2737,7 @@ class Constraint {
|
|
|
2735
2737
|
this.r1 = [0, 0, 1];
|
|
2736
2738
|
if (kind === DIAL) {
|
|
2737
2739
|
const z = _vec3(opts.zero);
|
|
2738
|
-
|
|
2739
|
-
// Project the supplied reference onto the dial plane.
|
|
2740
|
-
const d = z[0]*this.u[0] + z[1]*this.u[1] + z[2]*this.u[2];
|
|
2741
|
-
this.r0[0] = z[0] - d*this.u[0];
|
|
2742
|
-
this.r0[1] = z[1] - d*this.u[1];
|
|
2743
|
-
this.r0[2] = z[2] - d*this.u[2];
|
|
2744
|
-
const l = Math.sqrt(this.r0[0]**2 + this.r0[1]**2 + this.r0[2]**2);
|
|
2745
|
-
if (l < EPS) _basis(this.u, this.r0, this.r1);
|
|
2746
|
-
else { this.r0[0]/=l; this.r0[1]/=l; this.r0[2]/=l; }
|
|
2747
|
-
} else {
|
|
2748
|
-
_basis(this.u, this.r0, this.r1);
|
|
2749
|
-
}
|
|
2750
|
-
// r1 = u × r0 (recomputed even when _basis ran — same result, one rule).
|
|
2751
|
-
this.r1[0] = this.u[1]*this.r0[2] - this.u[2]*this.r0[1];
|
|
2752
|
-
this.r1[1] = this.u[2]*this.r0[0] - this.u[0]*this.r0[2];
|
|
2753
|
-
this.r1[2] = this.u[0]*this.r0[1] - this.u[1]*this.r0[0];
|
|
2740
|
+
this._dialBasis(z ? z[0] : NaN, z ? z[1] : NaN, z ? z[2] : NaN);
|
|
2754
2741
|
}
|
|
2755
2742
|
|
|
2756
2743
|
// Extent: AXIS clamps t (default [-1, 1]); DIAL clamps θ in radians
|
|
@@ -2789,6 +2776,28 @@ class Constraint {
|
|
|
2789
2776
|
this.pt[2] = this.anchor[2] + c*this.r0[2] + sn*this.r1[2];
|
|
2790
2777
|
}
|
|
2791
2778
|
|
|
2779
|
+
// Build the DIAL in-plane basis (r0, r1) from the current axis u and an
|
|
2780
|
+
// optional θ=0 reference (zx,zy,zz): the reference is projected onto the
|
|
2781
|
+
// dial plane and normalised; absent (NaN) or degenerate, r0 derives from u
|
|
2782
|
+
// via the least-aligned-axis seed. r1 = u × r0, right-handed about u.
|
|
2783
|
+
_dialBasis(zx, zy, zz) {
|
|
2784
|
+
if (_isNum(zx) && _isNum(zy) && _isNum(zz)) {
|
|
2785
|
+
const d = zx*this.u[0] + zy*this.u[1] + zz*this.u[2];
|
|
2786
|
+
this.r0[0] = zx - d*this.u[0];
|
|
2787
|
+
this.r0[1] = zy - d*this.u[1];
|
|
2788
|
+
this.r0[2] = zz - d*this.u[2];
|
|
2789
|
+
const l = Math.sqrt(this.r0[0]**2 + this.r0[1]**2 + this.r0[2]**2);
|
|
2790
|
+
if (l < EPS) _basis(this.u, this.r0, this.r1);
|
|
2791
|
+
else { this.r0[0]/=l; this.r0[1]/=l; this.r0[2]/=l; }
|
|
2792
|
+
} else {
|
|
2793
|
+
_basis(this.u, this.r0, this.r1);
|
|
2794
|
+
}
|
|
2795
|
+
// r1 = u × r0 (recomputed even when _basis ran — same result, one rule).
|
|
2796
|
+
this.r1[0] = this.u[1]*this.r0[2] - this.u[2]*this.r0[1];
|
|
2797
|
+
this.r1[1] = this.u[2]*this.r0[0] - this.u[0]*this.r0[2];
|
|
2798
|
+
this.r1[2] = this.u[0]*this.r0[1] - this.u[1]*this.r0[0];
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2792
2801
|
/**
|
|
2793
2802
|
* Update the canonical state from a ray in the working space. The ray
|
|
2794
2803
|
* direction is assumed unit. Chainable.
|
|
@@ -2959,6 +2968,46 @@ class Constraint {
|
|
|
2959
2968
|
}
|
|
2960
2969
|
return this;
|
|
2961
2970
|
}
|
|
2971
|
+
|
|
2972
|
+
/**
|
|
2973
|
+
* Re-aim the constraint basis in the working space — the deferred-frame
|
|
2974
|
+
* seam (the p5.tree bridge's `from` opt resolves its symbolic basis through
|
|
2975
|
+
* mapDirection and calls this). Per kind:
|
|
2976
|
+
* PLANE — new normal; the point is re-projected onto the new plane.
|
|
2977
|
+
* AXIS — new direction; the scalar t is preserved, the point recomputed.
|
|
2978
|
+
* DIAL — new plane normal + optional θ=0 reference; θ is preserved, the
|
|
2979
|
+
* in-plane basis rebuilt (reference re-derived when omitted),
|
|
2980
|
+
* the point recomputed.
|
|
2981
|
+
* SPHERE — no basis; no-op.
|
|
2982
|
+
* Inputs are normalised; a zero-length axis keeps the previous one.
|
|
2983
|
+
* Chainable.
|
|
2984
|
+
*
|
|
2985
|
+
* @param {number} ax,ay,az New normal (PLANE) / direction (AXIS) / dial-plane normal (DIAL).
|
|
2986
|
+
* @param {number} [zx,zy,zz] DIAL only — θ=0 reference (re-derived when omitted).
|
|
2987
|
+
* @returns {Constraint} this
|
|
2988
|
+
*/
|
|
2989
|
+
aim(ax, ay, az, zx, zy, zz) {
|
|
2990
|
+
if (this.kind === PLANE) {
|
|
2991
|
+
const px = this.n[0], py = this.n[1], pz = this.n[2];
|
|
2992
|
+
this.n[0] = ax; this.n[1] = ay; this.n[2] = az;
|
|
2993
|
+
_unit(this.n, px, py, pz);
|
|
2994
|
+
this.seed(this.pt[0], this.pt[1], this.pt[2]);
|
|
2995
|
+
} else if (this.kind === AXIS) {
|
|
2996
|
+
const px = this.u[0], py = this.u[1], pz = this.u[2];
|
|
2997
|
+
this.u[0] = ax; this.u[1] = ay; this.u[2] = az;
|
|
2998
|
+
_unit(this.u, px, py, pz);
|
|
2999
|
+
this.pt[0] = this.anchor[0] + this.s*this.u[0];
|
|
3000
|
+
this.pt[1] = this.anchor[1] + this.s*this.u[1];
|
|
3001
|
+
this.pt[2] = this.anchor[2] + this.s*this.u[2];
|
|
3002
|
+
} else if (this.kind === DIAL) {
|
|
3003
|
+
const px = this.u[0], py = this.u[1], pz = this.u[2];
|
|
3004
|
+
this.u[0] = ax; this.u[1] = ay; this.u[2] = az;
|
|
3005
|
+
_unit(this.u, px, py, pz);
|
|
3006
|
+
this._dialBasis(zx, zy, zz);
|
|
3007
|
+
this._dialPoint();
|
|
3008
|
+
}
|
|
3009
|
+
return this;
|
|
3010
|
+
}
|
|
2962
3011
|
}
|
|
2963
3012
|
|
|
2964
3013
|
/**
|