bireactive 0.2.0
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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/animation/anim.d.ts +57 -0
- package/dist/animation/anim.js +318 -0
- package/dist/animation/combinators.d.ts +39 -0
- package/dist/animation/combinators.js +113 -0
- package/dist/animation/easings.d.ts +5 -0
- package/dist/animation/easings.js +5 -0
- package/dist/animation/index.d.ts +3 -0
- package/dist/animation/index.js +3 -0
- package/dist/assert/algebra.d.ts +20 -0
- package/dist/assert/algebra.js +79 -0
- package/dist/assert/claim.d.ts +40 -0
- package/dist/assert/claim.js +129 -0
- package/dist/assert/index.d.ts +7 -0
- package/dist/assert/index.js +19 -0
- package/dist/assert/predicates.d.ts +18 -0
- package/dist/assert/predicates.js +43 -0
- package/dist/assert/record.d.ts +20 -0
- package/dist/assert/record.js +78 -0
- package/dist/assert/scope.d.ts +42 -0
- package/dist/assert/scope.js +233 -0
- package/dist/assert/span.d.ts +37 -0
- package/dist/assert/span.js +68 -0
- package/dist/assert/tree.d.ts +22 -0
- package/dist/assert/tree.js +65 -0
- package/dist/code/code.d.ts +70 -0
- package/dist/code/code.js +361 -0
- package/dist/code/index.d.ts +2 -0
- package/dist/code/index.js +9 -0
- package/dist/code/morph.d.ts +5 -0
- package/dist/code/morph.js +194 -0
- package/dist/code/tokenize.d.ts +8 -0
- package/dist/code/tokenize.js +51 -0
- package/dist/constraints/cluster.d.ts +83 -0
- package/dist/constraints/cluster.js +213 -0
- package/dist/constraints/drivers.d.ts +15 -0
- package/dist/constraints/drivers.js +40 -0
- package/dist/constraints/factories.d.ts +73 -0
- package/dist/constraints/factories.js +248 -0
- package/dist/constraints/index.d.ts +11 -0
- package/dist/constraints/index.js +39 -0
- package/dist/constraints/interaction.d.ts +21 -0
- package/dist/constraints/interaction.js +148 -0
- package/dist/constraints/linalg.d.ts +18 -0
- package/dist/constraints/linalg.js +141 -0
- package/dist/constraints/phases.d.ts +21 -0
- package/dist/constraints/phases.js +60 -0
- package/dist/constraints/physics.d.ts +34 -0
- package/dist/constraints/physics.js +128 -0
- package/dist/constraints/rigid.d.ts +210 -0
- package/dist/constraints/rigid.js +835 -0
- package/dist/constraints/solver.d.ts +107 -0
- package/dist/constraints/solver.js +510 -0
- package/dist/constraints/term.d.ts +50 -0
- package/dist/constraints/term.js +80 -0
- package/dist/constraints/terms.d.ts +80 -0
- package/dist/constraints/terms.js +302 -0
- package/dist/constraints/world.d.ts +31 -0
- package/dist/constraints/world.js +245 -0
- package/dist/core/aggregates.d.ts +64 -0
- package/dist/core/aggregates.js +198 -0
- package/dist/core/anim.d.ts +84 -0
- package/dist/core/anim.js +301 -0
- package/dist/core/index.d.ts +38 -0
- package/dist/core/index.js +38 -0
- package/dist/core/introspect.d.ts +5 -0
- package/dist/core/introspect.js +31 -0
- package/dist/core/lenses/closed-form-policies.d.ts +64 -0
- package/dist/core/lenses/closed-form-policies.js +452 -0
- package/dist/core/lenses/domain-aggregates.d.ts +54 -0
- package/dist/core/lenses/domain-aggregates.js +259 -0
- package/dist/core/lenses/factor-lens.d.ts +42 -0
- package/dist/core/lenses/factor-lens.js +419 -0
- package/dist/core/lenses/index.d.ts +5 -0
- package/dist/core/lenses/index.js +16 -0
- package/dist/core/lenses/memory.d.ts +47 -0
- package/dist/core/lenses/memory.js +102 -0
- package/dist/core/lenses/typed-factor.d.ts +45 -0
- package/dist/core/lenses/typed-factor.js +376 -0
- package/dist/core/network-utils.d.ts +14 -0
- package/dist/core/network-utils.js +62 -0
- package/dist/core/new-primitives.d.ts +33 -0
- package/dist/core/new-primitives.js +113 -0
- package/dist/core/signal.d.ts +254 -0
- package/dist/core/signal.js +1349 -0
- package/dist/core/traits.d.ts +61 -0
- package/dist/core/traits.js +56 -0
- package/dist/core/tree.d.ts +23 -0
- package/dist/core/tree.js +62 -0
- package/dist/core/values/anchor.d.ts +23 -0
- package/dist/core/values/anchor.js +23 -0
- package/dist/core/values/audio.d.ts +33 -0
- package/dist/core/values/audio.js +107 -0
- package/dist/core/values/bool.d.ts +37 -0
- package/dist/core/values/bool.js +75 -0
- package/dist/core/values/box.d.ts +77 -0
- package/dist/core/values/box.js +211 -0
- package/dist/core/values/canvas.d.ts +71 -0
- package/dist/core/values/canvas.js +495 -0
- package/dist/core/values/color.d.ts +49 -0
- package/dist/core/values/color.js +106 -0
- package/dist/core/values/flags.d.ts +18 -0
- package/dist/core/values/flags.js +50 -0
- package/dist/core/values/gpu.d.ts +74 -0
- package/dist/core/values/gpu.js +426 -0
- package/dist/core/values/matrix.d.ts +53 -0
- package/dist/core/values/matrix.js +140 -0
- package/dist/core/values/num.d.ts +62 -0
- package/dist/core/values/num.js +166 -0
- package/dist/core/values/pose.d.ts +31 -0
- package/dist/core/values/pose.js +83 -0
- package/dist/core/values/range.d.ts +83 -0
- package/dist/core/values/range.js +167 -0
- package/dist/core/values/str.d.ts +76 -0
- package/dist/core/values/str.js +346 -0
- package/dist/core/values/template.d.ts +49 -0
- package/dist/core/values/template.js +148 -0
- package/dist/core/values/transform.d.ts +49 -0
- package/dist/core/values/transform.js +115 -0
- package/dist/core/values/tri.d.ts +31 -0
- package/dist/core/values/tri.js +95 -0
- package/dist/core/values/vec.d.ts +72 -0
- package/dist/core/values/vec.js +219 -0
- package/dist/core/writable.d.ts +15 -0
- package/dist/core/writable.js +29 -0
- package/dist/ext/events.d.ts +10 -0
- package/dist/ext/events.js +31 -0
- package/dist/ext/index.d.ts +4 -0
- package/dist/ext/index.js +4 -0
- package/dist/ext/snapshot.d.ts +8 -0
- package/dist/ext/snapshot.js +29 -0
- package/dist/ext/timeline.d.ts +56 -0
- package/dist/ext/timeline.js +94 -0
- package/dist/ext/waapi.d.ts +25 -0
- package/dist/ext/waapi.js +198 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +10 -0
- package/dist/propagators/index.d.ts +6 -0
- package/dist/propagators/index.js +6 -0
- package/dist/propagators/layout.d.ts +68 -0
- package/dist/propagators/layout.js +336 -0
- package/dist/propagators/network.d.ts +52 -0
- package/dist/propagators/network.js +185 -0
- package/dist/propagators/propagator.d.ts +12 -0
- package/dist/propagators/propagator.js +16 -0
- package/dist/propagators/range.d.ts +45 -0
- package/dist/propagators/range.js +147 -0
- package/dist/propagators/relations.d.ts +60 -0
- package/dist/propagators/relations.js +343 -0
- package/dist/shapes/annular-sector.d.ts +15 -0
- package/dist/shapes/annular-sector.js +64 -0
- package/dist/shapes/button.d.ts +14 -0
- package/dist/shapes/button.js +31 -0
- package/dist/shapes/choreographers.d.ts +22 -0
- package/dist/shapes/choreographers.js +69 -0
- package/dist/shapes/circle.d.ts +17 -0
- package/dist/shapes/circle.js +57 -0
- package/dist/shapes/clip.d.ts +5 -0
- package/dist/shapes/clip.js +31 -0
- package/dist/shapes/connect.d.ts +16 -0
- package/dist/shapes/connect.js +70 -0
- package/dist/shapes/curve.d.ts +60 -0
- package/dist/shapes/curve.js +285 -0
- package/dist/shapes/dashed.d.ts +16 -0
- package/dist/shapes/dashed.js +142 -0
- package/dist/shapes/debug.d.ts +43 -0
- package/dist/shapes/debug.js +97 -0
- package/dist/shapes/group.d.ts +5 -0
- package/dist/shapes/group.js +10 -0
- package/dist/shapes/handle.d.ts +32 -0
- package/dist/shapes/handle.js +88 -0
- package/dist/shapes/index.d.ts +23 -0
- package/dist/shapes/index.js +23 -0
- package/dist/shapes/interaction.d.ts +32 -0
- package/dist/shapes/interaction.js +187 -0
- package/dist/shapes/label.d.ts +20 -0
- package/dist/shapes/label.js +42 -0
- package/dist/shapes/layout.d.ts +29 -0
- package/dist/shapes/layout.js +74 -0
- package/dist/shapes/line.d.ts +21 -0
- package/dist/shapes/line.js +79 -0
- package/dist/shapes/list.d.ts +18 -0
- package/dist/shapes/list.js +51 -0
- package/dist/shapes/mount.d.ts +7 -0
- package/dist/shapes/mount.js +10 -0
- package/dist/shapes/path.d.ts +77 -0
- package/dist/shapes/path.js +227 -0
- package/dist/shapes/rect.d.ts +30 -0
- package/dist/shapes/rect.js +131 -0
- package/dist/shapes/shape.d.ts +132 -0
- package/dist/shapes/shape.js +306 -0
- package/dist/shapes/text.d.ts +24 -0
- package/dist/shapes/text.js +53 -0
- package/dist/shapes/tokens.d.ts +28 -0
- package/dist/shapes/tokens.js +27 -0
- package/dist/shapes/transitions.d.ts +23 -0
- package/dist/shapes/transitions.js +62 -0
- package/dist/tex/decorations.d.ts +26 -0
- package/dist/tex/decorations.js +116 -0
- package/dist/tex/index.d.ts +5 -0
- package/dist/tex/index.js +5 -0
- package/dist/tex/marker.d.ts +17 -0
- package/dist/tex/marker.js +63 -0
- package/dist/tex/motion.d.ts +43 -0
- package/dist/tex/motion.js +290 -0
- package/dist/tex/parts.d.ts +65 -0
- package/dist/tex/parts.js +149 -0
- package/dist/tex/tex.d.ts +45 -0
- package/dist/tex/tex.js +244 -0
- package/dist/web/attr.d.ts +16 -0
- package/dist/web/attr.js +98 -0
- package/dist/web/diagram.d.ts +49 -0
- package/dist/web/diagram.js +260 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +6 -0
- package/dist/web/md-marker.d.ts +6 -0
- package/dist/web/md-marker.js +39 -0
- package/dist/web/md-tex.d.ts +6 -0
- package/dist/web/md-tex.js +61 -0
- package/dist/web/raf.d.ts +6 -0
- package/dist/web/raf.js +24 -0
- package/dist/web/viewport.d.ts +7 -0
- package/dist/web/viewport.js +13 -0
- package/package.json +87 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// matrix.ts — reactive 2D affine matrix (SVG/Canvas convention).
|
|
2
|
+
//
|
|
3
|
+
// Sparse-trait: only `equals`. Element-wise linear combine / lerp don't
|
|
4
|
+
// decompose for matrices, so `spring`/`tween`/`mean` reject Matrix at
|
|
5
|
+
// compile time. Two invertibles, both `: this` via `Cell#lens`:
|
|
6
|
+
// - `multiply(b)` — inverse multiplies by `invert(b)`
|
|
7
|
+
// - `invert()` — its own inverse
|
|
8
|
+
import { Cell, reader } from "../signal.js";
|
|
9
|
+
import { derived, field } from "../writable.js";
|
|
10
|
+
import { Num, num } from "./num.js";
|
|
11
|
+
export const identity = () => ({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 });
|
|
12
|
+
export const fromTranslate = (x, y) => ({ a: 1, b: 0, c: 0, d: 1, e: x, f: y });
|
|
13
|
+
export const fromScale = (x, y) => ({ a: x, b: 0, c: 0, d: y, e: 0, f: 0 });
|
|
14
|
+
export const fromRotate = (angle) => {
|
|
15
|
+
const s = Math.sin(angle);
|
|
16
|
+
const c = Math.cos(angle);
|
|
17
|
+
return { a: c, b: s, c: -s, d: c, e: 0, f: 0 };
|
|
18
|
+
};
|
|
19
|
+
export const isIdentity = (m) => m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0;
|
|
20
|
+
export const equals = (m, n) => m === n ||
|
|
21
|
+
(m.a === n.a && m.b === n.b && m.c === n.c && m.d === n.d && m.e === n.e && m.f === n.f);
|
|
22
|
+
export function multiply(a, b) {
|
|
23
|
+
return {
|
|
24
|
+
a: a.a * b.a + a.c * b.b,
|
|
25
|
+
b: a.b * b.a + a.d * b.b,
|
|
26
|
+
c: a.a * b.c + a.c * b.d,
|
|
27
|
+
d: a.b * b.c + a.d * b.d,
|
|
28
|
+
e: a.a * b.e + a.c * b.f + a.e,
|
|
29
|
+
f: a.b * b.e + a.d * b.f + a.f,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function invert(m) {
|
|
33
|
+
const det = m.a * m.d - m.b * m.c;
|
|
34
|
+
if (det === 0)
|
|
35
|
+
throw new Error("Matrix not invertible");
|
|
36
|
+
const inv = 1 / det;
|
|
37
|
+
return {
|
|
38
|
+
a: m.d * inv,
|
|
39
|
+
b: -m.b * inv,
|
|
40
|
+
c: -m.c * inv,
|
|
41
|
+
d: m.a * inv,
|
|
42
|
+
e: (m.c * m.f - m.d * m.e) * inv,
|
|
43
|
+
f: (m.b * m.e - m.a * m.f) * inv,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export const determinant = (m) => m.a * m.d - m.b * m.c;
|
|
47
|
+
export const transformPoint = (m, p) => ({
|
|
48
|
+
x: m.a * p.x + m.c * p.y + m.e,
|
|
49
|
+
y: m.b * p.x + m.d * p.y + m.f,
|
|
50
|
+
});
|
|
51
|
+
export function transformBox(m, b) {
|
|
52
|
+
if (isIdentity(m))
|
|
53
|
+
return b;
|
|
54
|
+
const x0 = b.x, y0 = b.y, x1 = b.x + b.w, y1 = b.y + b.h;
|
|
55
|
+
const ax = m.a * x0 + m.c * y0 + m.e;
|
|
56
|
+
const ay = m.b * x0 + m.d * y0 + m.f;
|
|
57
|
+
const bx = m.a * x1 + m.c * y0 + m.e;
|
|
58
|
+
const by = m.b * x1 + m.d * y0 + m.f;
|
|
59
|
+
const cx = m.a * x1 + m.c * y1 + m.e;
|
|
60
|
+
const cy = m.b * x1 + m.d * y1 + m.f;
|
|
61
|
+
const dx = m.a * x0 + m.c * y1 + m.e;
|
|
62
|
+
const dy = m.b * x0 + m.d * y1 + m.f;
|
|
63
|
+
return {
|
|
64
|
+
x: Math.min(ax, bx, cx, dx),
|
|
65
|
+
y: Math.min(ay, by, cy, dy),
|
|
66
|
+
w: Math.max(ax, bx, cx, dx) - Math.min(ax, bx, cx, dx),
|
|
67
|
+
h: Math.max(ay, by, cy, dy) - Math.min(ay, by, cy, dy),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const SCALE_EPS = 1e-7;
|
|
71
|
+
export function compose(t, r, s, pivot) {
|
|
72
|
+
const sx = Math.abs(s.x) < SCALE_EPS ? (s.x < 0 ? -SCALE_EPS : SCALE_EPS) : s.x;
|
|
73
|
+
const sy = Math.abs(s.y) < SCALE_EPS ? (s.y < 0 ? -SCALE_EPS : SCALE_EPS) : s.y;
|
|
74
|
+
let m = fromTranslate(t.x, t.y);
|
|
75
|
+
m = multiply(m, fromTranslate(pivot.x, pivot.y));
|
|
76
|
+
if (r !== 0)
|
|
77
|
+
m = multiply(m, fromRotate(r));
|
|
78
|
+
if (sx !== 1 || sy !== 1)
|
|
79
|
+
m = multiply(m, fromScale(sx, sy));
|
|
80
|
+
m = multiply(m, fromTranslate(-pivot.x, -pivot.y));
|
|
81
|
+
return m;
|
|
82
|
+
}
|
|
83
|
+
export const toMatrixString = (m) => `matrix(${m.a},${m.b},${m.c},${m.d},${m.e},${m.f})`;
|
|
84
|
+
export class Matrix extends Cell {
|
|
85
|
+
static traits = { equals };
|
|
86
|
+
constructor(v = identity()) {
|
|
87
|
+
super(v, { equals });
|
|
88
|
+
}
|
|
89
|
+
multiply(b) {
|
|
90
|
+
const bf = reader(b);
|
|
91
|
+
return this.lens(v => multiply(v, bf()), n => multiply(n, invert(bf())));
|
|
92
|
+
}
|
|
93
|
+
invert() {
|
|
94
|
+
return this.lens(invert, invert);
|
|
95
|
+
}
|
|
96
|
+
get a() {
|
|
97
|
+
return field(this, "a", Num);
|
|
98
|
+
}
|
|
99
|
+
get b() {
|
|
100
|
+
return field(this, "b", Num);
|
|
101
|
+
}
|
|
102
|
+
get c() {
|
|
103
|
+
return field(this, "c", Num);
|
|
104
|
+
}
|
|
105
|
+
get d() {
|
|
106
|
+
return field(this, "d", Num);
|
|
107
|
+
}
|
|
108
|
+
get e() {
|
|
109
|
+
return field(this, "e", Num);
|
|
110
|
+
}
|
|
111
|
+
get f() {
|
|
112
|
+
return field(this, "f", Num);
|
|
113
|
+
}
|
|
114
|
+
get determinant() {
|
|
115
|
+
return derived(this, "determinant", Num, determinant);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Writable `Matrix` with entries `(a, b, c, d, e, f)` (SVG/Canvas order).
|
|
119
|
+
* Each entry is a literal `number` (lifted to a fresh seed) or an existing
|
|
120
|
+
* `Writable<Num>` (identity passthrough). RO sources are rejected at the
|
|
121
|
+
* type level — use `Matrix.derive(...)` for reactive RO tracking, or
|
|
122
|
+
* `cell.value` to snapshot. Lock an entry with `Num.pin(c)`. */
|
|
123
|
+
export function matrix(a = 1, b = 0, c = 0, d = 1, e = 0, f = 0) {
|
|
124
|
+
if (typeof a === "number" &&
|
|
125
|
+
typeof b === "number" &&
|
|
126
|
+
typeof c === "number" &&
|
|
127
|
+
typeof d === "number" &&
|
|
128
|
+
typeof e === "number" &&
|
|
129
|
+
typeof f === "number") {
|
|
130
|
+
return new Matrix({ a, b, c, d, e, f });
|
|
131
|
+
}
|
|
132
|
+
const aN = num(a);
|
|
133
|
+
const bN = num(b);
|
|
134
|
+
const cN = num(c);
|
|
135
|
+
const dN = num(d);
|
|
136
|
+
const eN = num(e);
|
|
137
|
+
const fN = num(f);
|
|
138
|
+
// The view fully reconstructs all 6 cells (1-arg bwd ⇒ no source read).
|
|
139
|
+
return Matrix.lens([aN, bN, cN, dN, eN, fN], ([a, b, c, d, e, f]) => ({ a, b, c, d, e, f }), v => [v.a, v.b, v.c, v.d, v.e, v.f]);
|
|
140
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Easing } from "../../animation/index.js";
|
|
2
|
+
import { type Tween } from "../anim.js";
|
|
3
|
+
import { Cell, type Init, type Val, type Writable, type WritableBrand } from "../signal.js";
|
|
4
|
+
import type { Linear, Pack } from "../traits.js";
|
|
5
|
+
import { Bool } from "./bool.js";
|
|
6
|
+
type V = number;
|
|
7
|
+
export declare const add: (a: V, b: V) => number;
|
|
8
|
+
export declare const sub: (a: V, b: V) => number;
|
|
9
|
+
export declare const scale: (a: V, k: number) => number;
|
|
10
|
+
export declare const lerp: (a: V, b: V, t: number) => number;
|
|
11
|
+
export declare const metric: (a: V, b: V) => number;
|
|
12
|
+
export declare const equals: (a: V, b: V) => boolean;
|
|
13
|
+
export declare class Num extends Cell<V> {
|
|
14
|
+
static traits: {
|
|
15
|
+
linear: Linear<number>;
|
|
16
|
+
lerp: (a: V, b: V, t: number) => number;
|
|
17
|
+
metric: (a: V, b: V) => number;
|
|
18
|
+
equals: (a: V, b: V) => boolean;
|
|
19
|
+
pack: Pack<number>;
|
|
20
|
+
};
|
|
21
|
+
readonly _t: typeof Num.traits;
|
|
22
|
+
constructor(v?: V);
|
|
23
|
+
add(b: Val<V>): this;
|
|
24
|
+
sub(b: Val<V>): this;
|
|
25
|
+
scale(k: Val<number>): this;
|
|
26
|
+
/** Affine `v ↦ k·v + off`. Invertible iff k ≠ 0; readability alias
|
|
27
|
+
* for `.scale(k).add(off)`. */
|
|
28
|
+
affine(k: Val<number>, off: Val<number>): this;
|
|
29
|
+
/** Lossy clamping lens to `[lo, hi]`. PutGet only (a write outside
|
|
30
|
+
* the range reads back clamped, not as written). */
|
|
31
|
+
clamp(lo: Val<V>, hi: Val<V>): this;
|
|
32
|
+
/** Lossy lens snapping reads/writes to the nearest multiple of `step`. */
|
|
33
|
+
quantize(step: Val<number>): this;
|
|
34
|
+
/** Cyclic-coordinate lens. Reads pass through; writes pick the
|
|
35
|
+
* representative closest to current modulo `period`, so dragging an
|
|
36
|
+
* angle never jumps a full revolution. The 2-arg bwd is arity-detected
|
|
37
|
+
* as stateful, threading the accumulated value through `s`. */
|
|
38
|
+
cyclic(period: Val<number>): this;
|
|
39
|
+
/** `this > t` as a Bool. Flipping the view bumps the source across
|
|
40
|
+
* the threshold by `eps`. */
|
|
41
|
+
greaterThan<T extends Num>(this: T, t: Val<V>, eps?: Val<V>): T extends WritableBrand ? Writable<Bool> : Bool;
|
|
42
|
+
/** `this < t`. Dual of `greaterThan`. */
|
|
43
|
+
lessThan<T extends Num>(this: T, t: Val<V>, eps?: Val<V>): T extends WritableBrand ? Writable<Bool> : Bool;
|
|
44
|
+
/** `round(this) ≡ 0 (mod d)` as a Bool; pair with `quantize(1)` for
|
|
45
|
+
* integer sliders. Bwd: to make divisible, snap to the nearer
|
|
46
|
+
* multiple of `d`; to make non-divisible, bump by `+1`; no-op when
|
|
47
|
+
* the class already matches. */
|
|
48
|
+
divisibleBy<T extends Num>(this: T, d: Val<V>): T extends WritableBrand ? Writable<Bool> : Bool;
|
|
49
|
+
/** `divisibleBy(2)` — lazy getter for the common case. */
|
|
50
|
+
get isEven(): this extends WritableBrand ? Writable<Bool> : Bool;
|
|
51
|
+
/** `not(divisibleBy(2))` — lazy getter. */
|
|
52
|
+
get isOdd(): this extends WritableBrand ? Writable<Bool> : Bool;
|
|
53
|
+
/** Tween-builder; `this: Writable<Num>` gates the call to writable
|
|
54
|
+
* receivers. */
|
|
55
|
+
to(this: Writable<Num>, target: V, dur: Val<number>, ease?: Easing): Tween<V>;
|
|
56
|
+
}
|
|
57
|
+
/** Writable `Num`. Literal seeds a fresh cell; existing `Writable<Num>`
|
|
58
|
+
* passes through by identity. RO sources are rejected at the type level —
|
|
59
|
+
* use `Num.derive(...)` for reactive RO tracking, or `Num.from(...)` for
|
|
60
|
+
* the permissive lift over any `Val<number>`. */
|
|
61
|
+
export declare function num(v?: Init<Num>): Writable<Num>;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// num.ts — reactive scalar.
|
|
2
|
+
//
|
|
3
|
+
// Invertibles return `: this` and ride on `Cell#lens(fwd, bwd)`;
|
|
4
|
+
// chained calls compose into a lens chain.
|
|
5
|
+
import { tween } from "../anim.js";
|
|
6
|
+
import { Cell, lazy, reader, } from "../signal.js";
|
|
7
|
+
import { Bool } from "./bool.js";
|
|
8
|
+
export const add = (a, b) => a + b;
|
|
9
|
+
export const sub = (a, b) => a - b;
|
|
10
|
+
export const scale = (a, k) => a * k;
|
|
11
|
+
export const lerp = (a, b, t) => a + (b - a) * t;
|
|
12
|
+
export const metric = (a, b) => Math.abs(a - b);
|
|
13
|
+
export const equals = (a, b) => a === b;
|
|
14
|
+
const linearImpl = { add, sub, scale };
|
|
15
|
+
const packImpl = {
|
|
16
|
+
dim: 1,
|
|
17
|
+
read: (v, a, o) => {
|
|
18
|
+
a[o] = v;
|
|
19
|
+
},
|
|
20
|
+
write: (a, o) => a[o],
|
|
21
|
+
};
|
|
22
|
+
export class Num extends Cell {
|
|
23
|
+
static traits = {
|
|
24
|
+
linear: linearImpl,
|
|
25
|
+
lerp,
|
|
26
|
+
metric,
|
|
27
|
+
equals,
|
|
28
|
+
pack: packImpl,
|
|
29
|
+
};
|
|
30
|
+
constructor(v = 0) {
|
|
31
|
+
super(v, { equals });
|
|
32
|
+
}
|
|
33
|
+
add(b) {
|
|
34
|
+
const bf = reader(b);
|
|
35
|
+
return this.lens(v => v + bf(), n => n - bf());
|
|
36
|
+
}
|
|
37
|
+
sub(b) {
|
|
38
|
+
const bf = reader(b);
|
|
39
|
+
return this.lens(v => v - bf(), n => n + bf());
|
|
40
|
+
}
|
|
41
|
+
scale(k) {
|
|
42
|
+
const kf = reader(k);
|
|
43
|
+
return this.lens(v => v * kf(), n => n / kf());
|
|
44
|
+
}
|
|
45
|
+
/** Affine `v ↦ k·v + off`. Invertible iff k ≠ 0; readability alias
|
|
46
|
+
* for `.scale(k).add(off)`. */
|
|
47
|
+
affine(k, off) {
|
|
48
|
+
const kf = reader(k);
|
|
49
|
+
const of = reader(off);
|
|
50
|
+
return this.lens(v => v * kf() + of(), n => (n - of()) / kf());
|
|
51
|
+
}
|
|
52
|
+
/** Lossy clamping lens to `[lo, hi]`. PutGet only (a write outside
|
|
53
|
+
* the range reads back clamped, not as written). */
|
|
54
|
+
clamp(lo, hi) {
|
|
55
|
+
const lf = reader(lo);
|
|
56
|
+
const hf = reader(hi);
|
|
57
|
+
const c = (v) => {
|
|
58
|
+
const l = lf(), h = hf();
|
|
59
|
+
return v < l ? l : v > h ? h : v;
|
|
60
|
+
};
|
|
61
|
+
// A write whose clamped projection matches the current view leaves
|
|
62
|
+
// the source untouched (off-range source preserved).
|
|
63
|
+
return this.lens(c, (v, s) => {
|
|
64
|
+
const cv = c(v);
|
|
65
|
+
return cv === c(s) ? s : cv;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Lossy lens snapping reads/writes to the nearest multiple of `step`. */
|
|
69
|
+
quantize(step) {
|
|
70
|
+
const sf = reader(step);
|
|
71
|
+
const q = (v) => {
|
|
72
|
+
const s = sf();
|
|
73
|
+
return Math.round(v / s) * s;
|
|
74
|
+
};
|
|
75
|
+
// A write that snaps to the current bucket leaves the source
|
|
76
|
+
// untouched (off-grid remainder preserved).
|
|
77
|
+
return this.lens(q, (v, src) => {
|
|
78
|
+
const qv = q(v);
|
|
79
|
+
return qv === q(src) ? src : qv;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Cyclic-coordinate lens. Reads pass through; writes pick the
|
|
83
|
+
* representative closest to current modulo `period`, so dragging an
|
|
84
|
+
* angle never jumps a full revolution. The 2-arg bwd is arity-detected
|
|
85
|
+
* as stateful, threading the accumulated value through `s`. */
|
|
86
|
+
cyclic(period) {
|
|
87
|
+
const pf = reader(period);
|
|
88
|
+
return this.lens(v => v, (v, s) => {
|
|
89
|
+
const p = pf();
|
|
90
|
+
const delta = v - s;
|
|
91
|
+
return s + delta - p * Math.round(delta / p);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// Predicate bridges to Bool.
|
|
95
|
+
//
|
|
96
|
+
// Cross-type quotient lenses projecting Num through a boolean
|
|
97
|
+
// predicate. Conditional return type: writable receiver yields
|
|
98
|
+
// `Writable<Bool>`, RO receiver yields RO `Bool`.
|
|
99
|
+
/** `this > t` as a Bool. Flipping the view bumps the source across
|
|
100
|
+
* the threshold by `eps`. */
|
|
101
|
+
greaterThan(t, eps = 1e-6) {
|
|
102
|
+
const tf = reader(t);
|
|
103
|
+
const ef = reader(eps);
|
|
104
|
+
return Bool.lens(this, v => v > tf(), (target, current) => {
|
|
105
|
+
const th = tf();
|
|
106
|
+
if (target === current > th)
|
|
107
|
+
return current;
|
|
108
|
+
return target ? th + ef() : th - ef();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/** `this < t`. Dual of `greaterThan`. */
|
|
112
|
+
lessThan(t, eps = 1e-6) {
|
|
113
|
+
const tf = reader(t);
|
|
114
|
+
const ef = reader(eps);
|
|
115
|
+
return Bool.lens(this, v => v < tf(), (target, current) => {
|
|
116
|
+
const th = tf();
|
|
117
|
+
if (target === current < th)
|
|
118
|
+
return current;
|
|
119
|
+
return target ? th - ef() : th + ef();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/** `round(this) ≡ 0 (mod d)` as a Bool; pair with `quantize(1)` for
|
|
123
|
+
* integer sliders. Bwd: to make divisible, snap to the nearer
|
|
124
|
+
* multiple of `d`; to make non-divisible, bump by `+1`; no-op when
|
|
125
|
+
* the class already matches. */
|
|
126
|
+
divisibleBy(d) {
|
|
127
|
+
const df = reader(d);
|
|
128
|
+
return Bool.lens(this, v => Math.round(v) % df() === 0, (target, current) => {
|
|
129
|
+
const dv = df();
|
|
130
|
+
const r = Math.round(current);
|
|
131
|
+
// ((a % b) + b) % b handles negative `r` cleanly.
|
|
132
|
+
const mod = ((r % dv) + dv) % dv;
|
|
133
|
+
const isDiv = mod === 0;
|
|
134
|
+
if (target === isDiv)
|
|
135
|
+
return current;
|
|
136
|
+
if (target) {
|
|
137
|
+
const down = r - mod;
|
|
138
|
+
const up = r + (dv - mod);
|
|
139
|
+
return Math.abs(current - down) <= Math.abs(current - up) ? down : up;
|
|
140
|
+
}
|
|
141
|
+
return r + 1;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/** `divisibleBy(2)` — lazy getter for the common case. */
|
|
145
|
+
get isEven() {
|
|
146
|
+
return lazy(this, "isEven", () => this.divisibleBy(2));
|
|
147
|
+
}
|
|
148
|
+
/** `not(divisibleBy(2))` — lazy getter. */
|
|
149
|
+
get isOdd() {
|
|
150
|
+
return lazy(this, "isOdd", () => this.divisibleBy(2).not());
|
|
151
|
+
}
|
|
152
|
+
/** Tween-builder; `this: Writable<Num>` gates the call to writable
|
|
153
|
+
* receivers. */
|
|
154
|
+
to(target, dur, ease) {
|
|
155
|
+
return tween(this, target, dur, ease);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Writable `Num`. Literal seeds a fresh cell; existing `Writable<Num>`
|
|
159
|
+
* passes through by identity. RO sources are rejected at the type level —
|
|
160
|
+
* use `Num.derive(...)` for reactive RO tracking, or `Num.from(...)` for
|
|
161
|
+
* the permissive lift over any `Val<number>`. */
|
|
162
|
+
export function num(v = 0) {
|
|
163
|
+
if (v instanceof Num)
|
|
164
|
+
return v;
|
|
165
|
+
return new Num(v);
|
|
166
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Cell, type Init, type Writable } from "../signal.js";
|
|
2
|
+
import type { Linear, Pack, Pivotal } from "../traits.js";
|
|
3
|
+
type V = {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
theta: number;
|
|
7
|
+
};
|
|
8
|
+
export declare const add: (a: V, b: V) => V;
|
|
9
|
+
export declare const sub: (a: V, b: V) => V;
|
|
10
|
+
export declare const scale: (a: V, k: number) => V;
|
|
11
|
+
export declare const lerp: (a: V, b: V, t: number) => V;
|
|
12
|
+
export declare const metric: (a: V, b: V) => number;
|
|
13
|
+
export declare const equals: (a: V, b: V) => boolean;
|
|
14
|
+
export declare class Pose extends Cell<V> {
|
|
15
|
+
static traits: {
|
|
16
|
+
linear: Linear<V>;
|
|
17
|
+
lerp: (a: V, b: V, t: number) => V;
|
|
18
|
+
metric: (a: V, b: V) => number;
|
|
19
|
+
equals: (a: V, b: V) => boolean;
|
|
20
|
+
pack: Pack<V>;
|
|
21
|
+
pivotal: Pivotal<V>;
|
|
22
|
+
};
|
|
23
|
+
readonly _t: typeof Pose.traits;
|
|
24
|
+
constructor(v?: V);
|
|
25
|
+
}
|
|
26
|
+
/** Writable `Pose`. Literal seeds a fresh cell; existing `Pose` passes
|
|
27
|
+
* through by identity. RO sources are rejected at the type level — use
|
|
28
|
+
* `Pose.derive(...)` for reactive RO tracking, or `cell.value` to
|
|
29
|
+
* snapshot. */
|
|
30
|
+
export declare function pose(v?: Init<Pose>): Writable<Pose>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// pose.ts — reactive 2D rigid-body pose: { x, y, theta }.
|
|
2
|
+
//
|
|
3
|
+
// Single source of truth for a rigid body. The solver binds the cell as
|
|
4
|
+
// a 3-DOF block and writes back through it, so renderers, drag handlers,
|
|
5
|
+
// IK, and physics all observe the same value. Vec / Num lenses compose
|
|
6
|
+
// for consumers that care only about translation or rotation.
|
|
7
|
+
import { Cell } from "../signal.js";
|
|
8
|
+
export const add = (a, b) => ({
|
|
9
|
+
x: a.x + b.x,
|
|
10
|
+
y: a.y + b.y,
|
|
11
|
+
theta: a.theta + b.theta,
|
|
12
|
+
});
|
|
13
|
+
export const sub = (a, b) => ({
|
|
14
|
+
x: a.x - b.x,
|
|
15
|
+
y: a.y - b.y,
|
|
16
|
+
theta: a.theta - b.theta,
|
|
17
|
+
});
|
|
18
|
+
export const scale = (a, k) => ({
|
|
19
|
+
x: a.x * k,
|
|
20
|
+
y: a.y * k,
|
|
21
|
+
theta: a.theta * k,
|
|
22
|
+
});
|
|
23
|
+
export const lerp = (a, b, t) => ({
|
|
24
|
+
x: a.x + (b.x - a.x) * t,
|
|
25
|
+
y: a.y + (b.y - a.y) * t,
|
|
26
|
+
theta: a.theta + (b.theta - a.theta) * t,
|
|
27
|
+
});
|
|
28
|
+
export const metric = (a, b) => Math.hypot(a.x - b.x, a.y - b.y, a.theta - b.theta);
|
|
29
|
+
export const equals = (a, b) => a === b || (a.x === b.x && a.y === b.y && a.theta === b.theta);
|
|
30
|
+
const linearImpl = { add, sub, scale };
|
|
31
|
+
const packImpl = {
|
|
32
|
+
dim: 3,
|
|
33
|
+
read: (v, a, o) => {
|
|
34
|
+
a[o] = v.x;
|
|
35
|
+
a[o + 1] = v.y;
|
|
36
|
+
a[o + 2] = v.theta;
|
|
37
|
+
},
|
|
38
|
+
write: (a, o) => ({ x: a[o], y: a[o + 1], theta: a[o + 2] }),
|
|
39
|
+
};
|
|
40
|
+
/** Rotate-about-pivot moves the position and adds dθ to orientation;
|
|
41
|
+
* scale-about-pivot scales position, orientation untouched. */
|
|
42
|
+
const pivotalImpl = {
|
|
43
|
+
rotateAbout: (v, p, dθ) => {
|
|
44
|
+
const cos = Math.cos(dθ);
|
|
45
|
+
const sin = Math.sin(dθ);
|
|
46
|
+
const dx = v.x - p.x;
|
|
47
|
+
const dy = v.y - p.y;
|
|
48
|
+
return {
|
|
49
|
+
x: p.x + cos * dx - sin * dy,
|
|
50
|
+
y: p.y + sin * dx + cos * dy,
|
|
51
|
+
theta: v.theta + dθ,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
scaleAbout: (v, p, k) => ({
|
|
55
|
+
x: p.x + k * (v.x - p.x),
|
|
56
|
+
y: p.y + k * (v.y - p.y),
|
|
57
|
+
theta: v.theta,
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
export class Pose extends Cell {
|
|
61
|
+
static traits = {
|
|
62
|
+
linear: linearImpl,
|
|
63
|
+
lerp,
|
|
64
|
+
metric,
|
|
65
|
+
equals,
|
|
66
|
+
pack: packImpl,
|
|
67
|
+
pivotal: pivotalImpl,
|
|
68
|
+
};
|
|
69
|
+
constructor(v = { x: 0, y: 0, theta: 0 }) {
|
|
70
|
+
super(v, { equals });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Writable `Pose`. Literal seeds a fresh cell; existing `Pose` passes
|
|
74
|
+
* through by identity. RO sources are rejected at the type level — use
|
|
75
|
+
* `Pose.derive(...)` for reactive RO tracking, or `cell.value` to
|
|
76
|
+
* snapshot. */
|
|
77
|
+
export function pose(v = { x: 0, y: 0, theta: 0 }) {
|
|
78
|
+
if (v instanceof Pose)
|
|
79
|
+
return v;
|
|
80
|
+
const p = new Pose();
|
|
81
|
+
p.value = v;
|
|
82
|
+
return p;
|
|
83
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Easing } from "../../animation/index.js";
|
|
2
|
+
import { type Tween } from "../anim.js";
|
|
3
|
+
import { Cell, type Init, type Val, type Writable, type WritableBrand } from "../signal.js";
|
|
4
|
+
import type { Linear, Pack } from "../traits.js";
|
|
5
|
+
import { Bool } from "./bool.js";
|
|
6
|
+
import { Num } from "./num.js";
|
|
7
|
+
type V = {
|
|
8
|
+
lo: number;
|
|
9
|
+
hi: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const add: (a: V, b: V) => V;
|
|
12
|
+
export declare const sub: (a: V, b: V) => V;
|
|
13
|
+
export declare const scale: (a: V, k: number) => V;
|
|
14
|
+
export declare const lerp: (a: V, b: V, t: number) => V;
|
|
15
|
+
export declare const equals: (a: V, b: V) => boolean;
|
|
16
|
+
/** L2 distance over (lo, hi). Treats a range as a point in 2-space. */
|
|
17
|
+
export declare const metric: (a: V, b: V) => number;
|
|
18
|
+
export declare const width: (r: V) => number;
|
|
19
|
+
export declare const center: (r: V) => number;
|
|
20
|
+
export declare const contains: (r: V, v: number) => boolean;
|
|
21
|
+
export declare const clamp: (r: V, v: number) => number;
|
|
22
|
+
/** Closest value STRICTLY outside `[lo, hi]`, displaced past the
|
|
23
|
+
* nearest endpoint by `eps`. Used by `Range#contains` as the bwd's
|
|
24
|
+
* false-side policy. */
|
|
25
|
+
export declare const eject: (r: V, v: number, eps?: number) => number;
|
|
26
|
+
/** Sample at parameter `t`: `lo + t·(hi - lo)`. `t ∈ [0, 1]` stays
|
|
27
|
+
* inside the range; values outside extrapolate linearly. */
|
|
28
|
+
export declare const sample: (r: V, t: number) => number;
|
|
29
|
+
/** Inverse of `sample`: given a value, recover the `t` that would
|
|
30
|
+
* produce it. Degenerate (zero-width) ranges return 0. */
|
|
31
|
+
export declare const paramOf: (r: V, v: number) => number;
|
|
32
|
+
export declare class Range extends Cell<V> {
|
|
33
|
+
static traits: {
|
|
34
|
+
linear: Linear<V>;
|
|
35
|
+
lerp: (a: V, b: V, t: number) => V;
|
|
36
|
+
metric: (a: V, b: V) => number;
|
|
37
|
+
equals: (a: V, b: V) => boolean;
|
|
38
|
+
pack: Pack<V>;
|
|
39
|
+
};
|
|
40
|
+
readonly _t: typeof Range.traits;
|
|
41
|
+
constructor(v?: V);
|
|
42
|
+
/** Start endpoint. Writes preserve `hi` (start-knob semantics). */
|
|
43
|
+
get lo(): this extends WritableBrand ? Writable<Num> : Num;
|
|
44
|
+
/** End endpoint. Writes preserve `lo` (end-knob semantics). */
|
|
45
|
+
get hi(): this extends WritableBrand ? Writable<Num> : Num;
|
|
46
|
+
get width(): Num;
|
|
47
|
+
get center(): Num;
|
|
48
|
+
/** Translate by `by`. Reads shift the interval; writes shift back. */
|
|
49
|
+
shift(by: Val<number>): this;
|
|
50
|
+
/** Scale uniformly about the origin. Iso for `k ≠ 0`. */
|
|
51
|
+
scale(k: Val<number>): this;
|
|
52
|
+
/** Body-drag handle: read returns `lo`; write shifts the range so `lo`
|
|
53
|
+
* matches (width preserved). For start-knob editing use `.lo`. */
|
|
54
|
+
get start(): Writable<Num>;
|
|
55
|
+
/** RO sample at `t`. `t ∈ [0, 1]` stays inside; outside extrapolates. */
|
|
56
|
+
sample(t: Val<number>): Num;
|
|
57
|
+
/** Bidirectional `t ↔ value` slider. Read `lo + t·(hi - lo)`; write
|
|
58
|
+
* solves for `t` and updates `t` only, leaving `lo` / `hi` put. */
|
|
59
|
+
slider(t: Writable<Num>): Writable<Num>;
|
|
60
|
+
/** Membership predicate. Conditional return type: a writable `Num`
|
|
61
|
+
* yields `Writable<Bool>` and flipping the view bumps the source
|
|
62
|
+
* (`true` clamps into `[lo, hi]`, `false` ejects past the nearest
|
|
63
|
+
* endpoint by `eps`). Literal / RO inputs yield a bare RO `Bool`. */
|
|
64
|
+
contains<P extends Val<number>>(v: P): P extends WritableBrand ? Writable<Bool> : Bool;
|
|
65
|
+
/** RO clamp: read `v` into `[lo, hi]`. For a writable clamping lens
|
|
66
|
+
* on a single Num, see `Num#clamp(lo, hi)`. */
|
|
67
|
+
clampedRead(v: Val<number>): Num;
|
|
68
|
+
/** Inverse of `sample`: derive the `t` that would produce `v`. */
|
|
69
|
+
paramOf(v: Val<number>): Num;
|
|
70
|
+
/** Tween-builder; animates `{lo, hi}` jointly. */
|
|
71
|
+
to(this: Writable<Range>, target: V, dur: Val<number>, ease?: Easing): Tween<V>;
|
|
72
|
+
}
|
|
73
|
+
/** Range over `[at, at + dur]`, parameterised by start + duration. The
|
|
74
|
+
* timeline-clip shape: `.lo` slides the start, `.hi` the end, `.start`
|
|
75
|
+
* body-drags (preserving width). Backed by the live `at` / `dur` Nums. */
|
|
76
|
+
export declare function span(at: Writable<Num>, dur: Writable<Num>): Writable<Range>;
|
|
77
|
+
/** Writable `Range` over `[lo, hi]`. Each endpoint is a literal `number`
|
|
78
|
+
* (lifted to a fresh seed) or an existing `Writable<Num>` (identity
|
|
79
|
+
* passthrough). RO sources are rejected at the type level — use
|
|
80
|
+
* `Range.derive(...)` for reactive RO tracking, or `cell.value` to
|
|
81
|
+
* snapshot. Lock an endpoint with `Num.pin(c)`. */
|
|
82
|
+
export declare function range(lo?: Init<Num>, hi?: Init<Num>): Writable<Range>;
|
|
83
|
+
export {};
|