bireactive 0.2.3 → 0.3.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/dist/animation/anim.js +4 -0
- package/dist/coll.d.ts +7 -7
- package/dist/coll.js +3 -1
- package/dist/core/cell.d.ts +89 -66
- package/dist/core/cell.js +642 -401
- package/dist/core/index.d.ts +4 -14
- package/dist/core/index.js +4 -14
- package/dist/core/lenses/aggregates.d.ts +1 -1
- package/dist/core/lenses/aggregates.js +4 -3
- package/dist/core/lenses/closed-form-policies.js +6 -6
- package/dist/core/lenses/decompositions.js +3 -3
- package/dist/core/lenses/domain-aggregates.js +5 -5
- package/dist/core/lenses/geometry.d.ts +1 -1
- package/dist/core/lenses/geometry.js +6 -7
- package/dist/core/lenses/memory.d.ts +2 -2
- package/dist/core/lenses/memory.js +3 -3
- package/dist/core/lenses/typed-factor.js +4 -3
- package/dist/core/traits.d.ts +1 -0
- package/dist/core/values/box.js +7 -7
- package/dist/core/values/color.js +5 -5
- package/dist/core/values/field.d.ts +70 -0
- package/dist/core/values/field.js +230 -0
- package/dist/core/values/gpu.d.ts +4 -2
- package/dist/core/values/gpu.js +11 -4
- package/dist/core/values/matrix.js +7 -7
- package/dist/core/values/num.d.ts +1 -1
- package/dist/core/values/num.js +1 -1
- package/dist/core/values/pose.js +4 -4
- package/dist/core/values/range.js +6 -6
- package/dist/core/values/template.d.ts +1 -1
- package/dist/core/values/template.js +2 -1
- package/dist/core/values/transform.js +7 -7
- package/dist/core/values/tri.js +3 -3
- package/dist/core/values/vec.js +8 -12
- package/dist/ext/timeline.js +2 -2
- package/dist/formats/cst.d.ts +127 -0
- package/dist/formats/cst.js +280 -0
- package/dist/formats/edn.d.ts +2 -0
- package/dist/formats/edn.js +301 -0
- package/dist/formats/index.d.ts +6 -0
- package/dist/formats/index.js +8 -0
- package/dist/formats/json.d.ts +2 -0
- package/dist/formats/json.js +332 -0
- package/dist/formats/lens.d.ts +8 -0
- package/dist/formats/lens.js +54 -0
- package/dist/formats/toml.d.ts +2 -0
- package/dist/formats/toml.js +526 -0
- package/dist/formats/yaml.d.ts +2 -0
- package/dist/formats/yaml.js +661 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/learn/data.d.ts +49 -0
- package/dist/learn/data.js +181 -0
- package/dist/learn/index.d.ts +3 -0
- package/dist/learn/index.js +6 -0
- package/dist/learn/lens-net.d.ts +63 -0
- package/dist/learn/lens-net.js +219 -0
- package/dist/learn/mlp.d.ts +77 -0
- package/dist/learn/mlp.js +292 -0
- package/dist/propagators/csp.d.ts +13 -0
- package/dist/propagators/csp.js +52 -0
- package/dist/propagators/flex.d.ts +31 -0
- package/dist/propagators/flex.js +189 -0
- package/dist/propagators/graph.d.ts +73 -0
- package/dist/propagators/graph.js +543 -0
- package/dist/propagators/index.d.ts +8 -6
- package/dist/propagators/index.js +15 -6
- package/dist/propagators/lattice.d.ts +45 -0
- package/dist/propagators/lattice.js +113 -0
- package/dist/propagators/layout.d.ts +1 -27
- package/dist/propagators/layout.js +6 -175
- package/dist/propagators/numeric.d.ts +17 -0
- package/dist/propagators/numeric.js +93 -0
- package/dist/propagators/solver.d.ts +51 -0
- package/dist/propagators/solver.js +175 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/lens.d.ts +121 -0
- package/dist/schema/lens.js +429 -0
- package/dist/shapes/annular-sector.js +4 -4
- package/dist/shapes/button.js +1 -1
- package/dist/shapes/circle.js +1 -1
- package/dist/shapes/handle.js +2 -2
- package/dist/shapes/label.js +1 -1
- package/dist/shapes/layout.js +2 -2
- package/dist/shapes/rect.js +7 -7
- package/dist/shapes/shape.js +8 -8
- package/dist/tex/tex.js +9 -2
- package/dist/web/diagram.js +2 -2
- package/package.json +9 -19
- package/dist/propagators/network.d.ts +0 -52
- package/dist/propagators/network.js +0 -185
- package/dist/propagators/propagator.d.ts +0 -12
- package/dist/propagators/propagator.js +0 -16
- package/dist/propagators/range.d.ts +0 -45
- package/dist/propagators/range.js +0 -147
- package/dist/propagators/relations.d.ts +0 -60
- package/dist/propagators/relations.js +0 -343
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// field.ts — dense, GPU-resident reactive field: a grid of `T` as ONE cell.
|
|
2
|
+
//
|
|
3
|
+
// A Field is a single reactive node, not one-per-texel: the value is a header
|
|
4
|
+
// {tex, w, h, epoch} (the Canvas handle trick, see gpu.ts), the graph compares
|
|
5
|
+
// `epoch`, and no pixel crosses the bus. So a 1000×1000 field costs the engine
|
|
6
|
+
// exactly what a 4×4 does — graph size is the lens-chain length, independent of
|
|
7
|
+
// resolution. Per-frame work is allocation-free: `evolve`/`splat` ping-pong
|
|
8
|
+
// through reused scratch textures and mint only the small header.
|
|
9
|
+
//
|
|
10
|
+
// Generic over the texel encoding via `Kind<T>` (channel `dim` + the `Pack`
|
|
11
|
+
// codec + the boundary cell class). The dense interior stays a flat RGBA32F
|
|
12
|
+
// texture forever; `T` (Num/Vec/Color) is instantiated ONLY at the boundary —
|
|
13
|
+
// `mean`, `regionMean` — never per-texel. `dim` (1/2/4) selects the channel
|
|
14
|
+
// mask: scalar (heatmap / SDF / density), vector (flow / gradient), colour
|
|
15
|
+
// (i.e. a Canvas).
|
|
16
|
+
//
|
|
17
|
+
// The point of interest is the GPU↔reactivity bridge. `evolve` runs a sim on
|
|
18
|
+
// its own clock (a raf/Anim loop), committing one new value per frame; the
|
|
19
|
+
// reductions (`mean`, `regionMean`) are ordinary read-only derived cells, so
|
|
20
|
+
// they recompute when the field's epoch changes and only *propagate* when their
|
|
21
|
+
// scalar actually moves. Reactive logic downstream (`density > 0.5` → effect)
|
|
22
|
+
// is thereby loosely coupled to time: it observes a continuously-running GPU
|
|
23
|
+
// simulation without knowing a frame exists.
|
|
24
|
+
import { Cell, reader } from "../cell.js";
|
|
25
|
+
import { Canvas, stamp as canvasStamp } from "./canvas.js";
|
|
26
|
+
import { Color } from "./color.js";
|
|
27
|
+
import { newTex, pass, reduceMean, scratch, scratch2 } from "./gpu.js";
|
|
28
|
+
import { Num } from "./num.js";
|
|
29
|
+
import { Vec } from "./vec.js";
|
|
30
|
+
let EPOCH = 0;
|
|
31
|
+
/** Stamp a texture with a fresh epoch — the only way to mint a value. */
|
|
32
|
+
const stamp = (tex, w, h) => ({
|
|
33
|
+
tex,
|
|
34
|
+
w,
|
|
35
|
+
h,
|
|
36
|
+
epoch: ++EPOCH,
|
|
37
|
+
});
|
|
38
|
+
const equals = (a, b) => a.epoch === b.epoch;
|
|
39
|
+
/** Scalar field (1 channel) — heatmaps, SDFs, density, height. */
|
|
40
|
+
export const Scalar = { dim: 1, pack: Num.traits.pack, cls: Num };
|
|
41
|
+
/** Vector field (2 channels) — flow, gradient, displacement, RD chemicals. */
|
|
42
|
+
export const Vector = { dim: 2, pack: Vec.traits.pack, cls: Vec };
|
|
43
|
+
/** Colour field (4 channels) — i.e. a Canvas. */
|
|
44
|
+
export const Colour = {
|
|
45
|
+
dim: 4,
|
|
46
|
+
pack: Color.traits.pack,
|
|
47
|
+
cls: Color,
|
|
48
|
+
};
|
|
49
|
+
const HEAD = `#version 300 es
|
|
50
|
+
precision highp float;
|
|
51
|
+
in vec2 v_uv;
|
|
52
|
+
out vec4 o;`;
|
|
53
|
+
const MASK = `${HEAD}
|
|
54
|
+
uniform sampler2D u_src; uniform vec2 u_res; uniform vec2 u_o; uniform vec2 u_wh;
|
|
55
|
+
void main() {
|
|
56
|
+
vec2 px = v_uv * u_res;
|
|
57
|
+
vec4 s = texture(u_src, v_uv);
|
|
58
|
+
bool inside = px.x >= u_o.x && px.y >= u_o.y && px.x < u_o.x + u_wh.x && px.y < u_o.y + u_wh.y;
|
|
59
|
+
o = inside ? vec4(s.rgb, 1.0) : vec4(0.0);
|
|
60
|
+
}`;
|
|
61
|
+
const SPLAT = `${HEAD}
|
|
62
|
+
uniform sampler2D u_src; uniform vec2 u_res; uniform vec2 u_c; uniform float u_r;
|
|
63
|
+
uniform vec3 u_v; uniform float u_mix;
|
|
64
|
+
void main() {
|
|
65
|
+
vec4 s = texture(u_src, v_uv);
|
|
66
|
+
float d = distance(v_uv * u_res, u_c);
|
|
67
|
+
float f = clamp(exp(-(d * d) / (u_r * u_r)) * u_mix, 0.0, 1.0);
|
|
68
|
+
o = vec4(mix(s.rgb, u_v, f), s.a);
|
|
69
|
+
}`;
|
|
70
|
+
/** GLSL float literal (always carries a decimal point). */
|
|
71
|
+
const glf = (n) => (Number.isInteger(n) ? `${n}.0` : String(n));
|
|
72
|
+
const glv3 = (c) => `${glf(c[0])}, ${glf(c[1])}, ${glf(c[2])}`;
|
|
73
|
+
/** Build a piecewise-linear ramp shader over `channel` (baked per stop set,
|
|
74
|
+
* cached by source string in gpu.ts's program cache). */
|
|
75
|
+
function rampFrag(channel, stops) {
|
|
76
|
+
const ch = "rgba"[channel] ?? "r";
|
|
77
|
+
let body = ` vec3 c = vec3(${glv3(stops[0][1])});\n`;
|
|
78
|
+
for (let i = 1; i < stops.length; i++) {
|
|
79
|
+
const p0 = stops[i - 1][0];
|
|
80
|
+
const [p1, col] = stops[i];
|
|
81
|
+
body += ` c = mix(c, vec3(${glv3(col)}), smoothstep(${glf(p0)}, ${glf(p1)}, x));\n`;
|
|
82
|
+
}
|
|
83
|
+
return `${HEAD}
|
|
84
|
+
uniform sampler2D u_src;
|
|
85
|
+
void main() {
|
|
86
|
+
float x = texture(u_src, v_uv).${ch};
|
|
87
|
+
${body} o = vec4(c, 1.0);
|
|
88
|
+
}`;
|
|
89
|
+
}
|
|
90
|
+
/** Read-only typed view via the kind's boundary class (`Num`/`Vec`/`Color`).
|
|
91
|
+
* Cast through the concrete `derive` signature — the static's polymorphic
|
|
92
|
+
* `this` rejects the abstract `Ctor<T>`, but at runtime `k.cls` is concrete. */
|
|
93
|
+
function deriveT(k, parent, fn) {
|
|
94
|
+
const d = k.cls;
|
|
95
|
+
return d.derive(parent, fn);
|
|
96
|
+
}
|
|
97
|
+
const TMP = new Float64Array(4);
|
|
98
|
+
/** Pack a reduction's `[r,g,b,a]` mean into a boundary value of kind `k`. */
|
|
99
|
+
function packT(k, m) {
|
|
100
|
+
TMP[0] = m[0];
|
|
101
|
+
TMP[1] = m[1];
|
|
102
|
+
TMP[2] = m[2];
|
|
103
|
+
TMP[3] = m[3];
|
|
104
|
+
return k.pack.write(TMP, 0);
|
|
105
|
+
}
|
|
106
|
+
function bindUniform(s, name, val) {
|
|
107
|
+
if (typeof val === "number")
|
|
108
|
+
s.f(name, val);
|
|
109
|
+
else if (val.length === 2)
|
|
110
|
+
s.v2(name, val[0], val[1]);
|
|
111
|
+
else if (val.length === 3)
|
|
112
|
+
s.v3(name, val[0], val[1], val[2]);
|
|
113
|
+
else
|
|
114
|
+
throw new Error(`field.evolve: uniform ${name} must be number | vec2 | vec3`);
|
|
115
|
+
}
|
|
116
|
+
const EMPTY = { tex: null, w: 0, h: 0, epoch: 0 };
|
|
117
|
+
export class Field extends Cell {
|
|
118
|
+
kind;
|
|
119
|
+
static traits = { equals };
|
|
120
|
+
/** Feedback-safe ping-pong pair for `evolve`/`splat`, lazily allocated. */
|
|
121
|
+
io = null;
|
|
122
|
+
constructor(kind, v = EMPTY) {
|
|
123
|
+
super(v, { equals });
|
|
124
|
+
this.kind = kind;
|
|
125
|
+
}
|
|
126
|
+
pingTex() {
|
|
127
|
+
return (this.io ??= scratch2());
|
|
128
|
+
}
|
|
129
|
+
/** Step the field by `steps` GPU passes of `frag` (which samples the current
|
|
130
|
+
* field as `u_src`; `u_texel` = 1/size is provided). One value is committed
|
|
131
|
+
* after the substeps, so the reactive graph sees a single new epoch/frame. */
|
|
132
|
+
evolve(frag, uniforms, steps = 1) {
|
|
133
|
+
const ping = this.pingTex();
|
|
134
|
+
const v = this.peek();
|
|
135
|
+
const w = v.w;
|
|
136
|
+
const h = v.h;
|
|
137
|
+
let src = v.tex;
|
|
138
|
+
let last = null;
|
|
139
|
+
const entries = uniforms ? Object.entries(uniforms) : null;
|
|
140
|
+
for (let i = 0; i < steps; i++) {
|
|
141
|
+
const dst = ping(w, h, src);
|
|
142
|
+
pass(frag, dst, s => {
|
|
143
|
+
s.tex("u_src", 0, src);
|
|
144
|
+
s.v2("u_texel", 1 / w, 1 / h);
|
|
145
|
+
if (entries)
|
|
146
|
+
for (const [name, val] of entries)
|
|
147
|
+
bindUniform(s, name, val);
|
|
148
|
+
});
|
|
149
|
+
src = dst.tex;
|
|
150
|
+
last = dst;
|
|
151
|
+
}
|
|
152
|
+
if (last)
|
|
153
|
+
this.value = stamp(last.tex, w, h);
|
|
154
|
+
}
|
|
155
|
+
/** Stamp a Gaussian disc of `value` at data pixel `(x, y)`, radius `r`. The
|
|
156
|
+
* raster interaction primitive — seed a sim, inject, paint density. */
|
|
157
|
+
splat(x, y, r, value, strength = 1) {
|
|
158
|
+
const ping = this.pingTex();
|
|
159
|
+
const v = this.peek();
|
|
160
|
+
this.kind.pack.read(value, TMP, 0);
|
|
161
|
+
const dst = ping(v.w, v.h, v.tex);
|
|
162
|
+
pass(SPLAT, dst, s => {
|
|
163
|
+
s.tex("u_src", 0, v.tex);
|
|
164
|
+
s.v2("u_res", v.w, v.h);
|
|
165
|
+
s.v2("u_c", x, y);
|
|
166
|
+
s.f("u_r", r);
|
|
167
|
+
s.v3("u_v", TMP[0], TMP[1], TMP[2]);
|
|
168
|
+
s.f("u_mix", strength);
|
|
169
|
+
});
|
|
170
|
+
this.value = stamp(dst.tex, v.w, v.h);
|
|
171
|
+
}
|
|
172
|
+
/** Whole-field mean as a read-only `T` cell. Reactive: recomputes on every
|
|
173
|
+
* new epoch, propagates only when the value moves. One 1×1 GPU readback. */
|
|
174
|
+
mean() {
|
|
175
|
+
const k = this.kind;
|
|
176
|
+
return deriveT(k, this, v => packT(k, reduceMean({ tex: v.tex, w: v.w, h: v.h })));
|
|
177
|
+
}
|
|
178
|
+
/** Mean over a sub-rectangle (data pixels, reactive `box`) as a read-only
|
|
179
|
+
* `T` cell — the field's "density of this region" observation. */
|
|
180
|
+
regionMean(box) {
|
|
181
|
+
const k = this.kind;
|
|
182
|
+
const bf = reader(box);
|
|
183
|
+
const sc = scratch();
|
|
184
|
+
return deriveT(k, this, v => {
|
|
185
|
+
const b = bf();
|
|
186
|
+
const dst = sc(v.w, v.h);
|
|
187
|
+
pass(MASK, dst, s => {
|
|
188
|
+
s.tex("u_src", 0, v.tex);
|
|
189
|
+
s.v2("u_res", v.w, v.h);
|
|
190
|
+
s.v2("u_o", b.x, b.y);
|
|
191
|
+
s.v2("u_wh", b.w, b.h);
|
|
192
|
+
});
|
|
193
|
+
const m = reduceMean({ tex: dst.tex, w: v.w, h: v.h });
|
|
194
|
+
const cov = m[3];
|
|
195
|
+
if (cov < 1e-6)
|
|
196
|
+
return packT(k, [0, 0, 0, 0]);
|
|
197
|
+
return packT(k, [m[0] / cov, m[1] / cov, m[2] / cov, 1]);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/** Render `channel` through a colormap to a read-only `Canvas`. Reactive:
|
|
201
|
+
* re-renders on every new epoch, so rendering is itself a reactive edge. */
|
|
202
|
+
colormap(channel, stops) {
|
|
203
|
+
const frag = rampFrag(channel, stops);
|
|
204
|
+
const sc = scratch();
|
|
205
|
+
return Canvas.derive(this, (v) => {
|
|
206
|
+
const out = sc(v.w, v.h);
|
|
207
|
+
pass(frag, out, s => s.tex("u_src", 0, v.tex));
|
|
208
|
+
return canvasStamp(out.tex, v.w, v.h);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** Writable `Field<T>` of size `w×h`. `painter` fills each texel (returns a
|
|
213
|
+
* `T`); omit for an all-zero field. Linear-filtered for continuous sampling. */
|
|
214
|
+
export function field(kind, w, h, painter) {
|
|
215
|
+
const buf = new Float32Array(w * h * 4);
|
|
216
|
+
if (painter !== undefined) {
|
|
217
|
+
const t = new Float64Array(4);
|
|
218
|
+
let o = 0;
|
|
219
|
+
for (let y = 0; y < h; y++) {
|
|
220
|
+
for (let x = 0; x < w; x++, o += 4) {
|
|
221
|
+
kind.pack.read(painter(x, y), t, 0);
|
|
222
|
+
for (let c = 0; c < kind.dim; c++)
|
|
223
|
+
buf[o + c] = t[c];
|
|
224
|
+
if (kind.dim < 4)
|
|
225
|
+
buf[o + 3] = 1;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return new Field(kind, stamp(newTex(w, h, buf, true), w, h));
|
|
230
|
+
}
|
|
@@ -6,8 +6,10 @@ export interface Tex {
|
|
|
6
6
|
w: number;
|
|
7
7
|
h: number;
|
|
8
8
|
}
|
|
9
|
-
/** Allocate an RGBA32F texture (`data` in 0–1 floats, or null).
|
|
10
|
-
|
|
9
|
+
/** Allocate an RGBA32F texture (`data` in 0–1 floats, or null). `linear`
|
|
10
|
+
* requests bilinear filtering (continuous Field sampling), silently
|
|
11
|
+
* downgraded to NEAREST where `OES_texture_float_linear` is unavailable. */
|
|
12
|
+
export declare function newTex(w: number, h: number, data?: Float32Array | null, linear?: boolean): WebGLTexture;
|
|
11
13
|
export declare function disposeTex(t: WebGLTexture): void;
|
|
12
14
|
/** A reusable owned texture, reallocated on size change — the GPU analog of
|
|
13
15
|
* the CPU scratch buffer. */
|
package/dist/core/values/gpu.js
CHANGED
|
@@ -21,9 +21,13 @@ export function gl() {
|
|
|
21
21
|
throw new Error("WebGL2 unavailable");
|
|
22
22
|
if (!ctx.getExtension("EXT_color_buffer_float"))
|
|
23
23
|
throw new Error("EXT_color_buffer_float unavailable");
|
|
24
|
+
// Optional: linear filtering of RGBA32F (continuous Field sampling/render).
|
|
25
|
+
// Absent on some mobile GPUs — `newTex(..., linear)` falls back to NEAREST.
|
|
26
|
+
_floatLinear = ctx.getExtension("OES_texture_float_linear") !== null;
|
|
24
27
|
_gl = ctx;
|
|
25
28
|
return ctx;
|
|
26
29
|
}
|
|
30
|
+
let _floatLinear = false;
|
|
27
31
|
const QUAD = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
|
|
28
32
|
const VERT = `#version 300 es
|
|
29
33
|
in vec2 a_pos;
|
|
@@ -81,14 +85,17 @@ function fbo() {
|
|
|
81
85
|
_fbo = gl().createFramebuffer();
|
|
82
86
|
return _fbo;
|
|
83
87
|
}
|
|
84
|
-
/** Allocate an RGBA32F texture (`data` in 0–1 floats, or null).
|
|
85
|
-
|
|
88
|
+
/** Allocate an RGBA32F texture (`data` in 0–1 floats, or null). `linear`
|
|
89
|
+
* requests bilinear filtering (continuous Field sampling), silently
|
|
90
|
+
* downgraded to NEAREST where `OES_texture_float_linear` is unavailable. */
|
|
91
|
+
export function newTex(w, h, data = null, linear = false) {
|
|
86
92
|
const g = gl();
|
|
87
93
|
const t = g.createTexture();
|
|
88
94
|
g.bindTexture(g.TEXTURE_2D, t);
|
|
89
95
|
g.texImage2D(g.TEXTURE_2D, 0, g.RGBA32F, w, h, 0, g.RGBA, g.FLOAT, data);
|
|
90
|
-
|
|
91
|
-
g.texParameteri(g.TEXTURE_2D, g.
|
|
96
|
+
const filter = linear && _floatLinear ? g.LINEAR : g.NEAREST;
|
|
97
|
+
g.texParameteri(g.TEXTURE_2D, g.TEXTURE_MIN_FILTER, filter);
|
|
98
|
+
g.texParameteri(g.TEXTURE_2D, g.TEXTURE_MAG_FILTER, filter);
|
|
92
99
|
g.texParameteri(g.TEXTURE_2D, g.TEXTURE_WRAP_S, g.CLAMP_TO_EDGE);
|
|
93
100
|
g.texParameteri(g.TEXTURE_2D, g.TEXTURE_WRAP_T, g.CLAMP_TO_EDGE);
|
|
94
101
|
return t;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// compile time. Two invertibles, both `: this` via `Cell#lens`:
|
|
6
6
|
// - `multiply(b)` — inverse multiplies by `invert(b)`
|
|
7
7
|
// - `invert()` — its own inverse
|
|
8
|
-
import { Cell, cachedDerive,
|
|
8
|
+
import { Cell, cachedDerive, fieldLens, reader, } from "../cell.js";
|
|
9
9
|
import { Num, num } from "./num.js";
|
|
10
10
|
export const identity = () => ({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 });
|
|
11
11
|
export const fromTranslate = (x, y) => ({ a: 1, b: 0, c: 0, d: 1, e: x, f: y });
|
|
@@ -93,22 +93,22 @@ export class Matrix extends Cell {
|
|
|
93
93
|
return this.lens(invert, invert);
|
|
94
94
|
}
|
|
95
95
|
get a() {
|
|
96
|
-
return
|
|
96
|
+
return fieldLens(this, "a", Num);
|
|
97
97
|
}
|
|
98
98
|
get b() {
|
|
99
|
-
return
|
|
99
|
+
return fieldLens(this, "b", Num);
|
|
100
100
|
}
|
|
101
101
|
get c() {
|
|
102
|
-
return
|
|
102
|
+
return fieldLens(this, "c", Num);
|
|
103
103
|
}
|
|
104
104
|
get d() {
|
|
105
|
-
return
|
|
105
|
+
return fieldLens(this, "d", Num);
|
|
106
106
|
}
|
|
107
107
|
get e() {
|
|
108
|
-
return
|
|
108
|
+
return fieldLens(this, "e", Num);
|
|
109
109
|
}
|
|
110
110
|
get f() {
|
|
111
|
-
return
|
|
111
|
+
return fieldLens(this, "f", Num);
|
|
112
112
|
}
|
|
113
113
|
get determinant() {
|
|
114
114
|
return cachedDerive(this, "determinant", Num, determinant);
|
|
@@ -61,7 +61,7 @@ export declare class Num extends Cell<V> {
|
|
|
61
61
|
}
|
|
62
62
|
/** Writable `Num`. Literal seeds a fresh cell; existing `Writable<Num>`
|
|
63
63
|
* passes through by identity. RO sources are rejected at the type level —
|
|
64
|
-
* use `Num.derive(...)` for reactive RO tracking, or `Num.
|
|
64
|
+
* use `Num.derive(...)` for reactive RO tracking, or `Num.coerce(...)` for
|
|
65
65
|
* the permissive lift over any `Val<number>`. */
|
|
66
66
|
export declare function num(v?: Init<Num>): Writable<Num>;
|
|
67
67
|
export {};
|
package/dist/core/values/num.js
CHANGED
|
@@ -177,7 +177,7 @@ export class Num extends Cell {
|
|
|
177
177
|
}
|
|
178
178
|
/** Writable `Num`. Literal seeds a fresh cell; existing `Writable<Num>`
|
|
179
179
|
* passes through by identity. RO sources are rejected at the type level —
|
|
180
|
-
* use `Num.derive(...)` for reactive RO tracking, or `Num.
|
|
180
|
+
* use `Num.derive(...)` for reactive RO tracking, or `Num.coerce(...)` for
|
|
181
181
|
* the permissive lift over any `Val<number>`. */
|
|
182
182
|
export function num(v = 0) {
|
|
183
183
|
if (v instanceof Num)
|
package/dist/core/values/pose.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// a 3-DOF block and writes back through it, so renderers, drag handlers,
|
|
5
5
|
// IK, and physics all observe the same value. Vec / Num lenses compose
|
|
6
6
|
// for consumers that care only about translation or rotation.
|
|
7
|
-
import { Cell,
|
|
7
|
+
import { Cell, fieldLens } from "../cell.js";
|
|
8
8
|
import { Num } from "./num.js";
|
|
9
9
|
export const add = (a, b) => ({
|
|
10
10
|
x: a.x + b.x,
|
|
@@ -71,13 +71,13 @@ export class Pose extends Cell {
|
|
|
71
71
|
super(v, { equals });
|
|
72
72
|
}
|
|
73
73
|
get x() {
|
|
74
|
-
return
|
|
74
|
+
return fieldLens(this, "x", Num);
|
|
75
75
|
}
|
|
76
76
|
get y() {
|
|
77
|
-
return
|
|
77
|
+
return fieldLens(this, "y", Num);
|
|
78
78
|
}
|
|
79
79
|
get theta() {
|
|
80
|
-
return
|
|
80
|
+
return fieldLens(this, "theta", Num);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
/** Writable `Pose`. Literal seeds a fresh cell; existing `Pose` passes
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// respectively); `.slider(t)` is the bidirectional `t ↔ lo + t·(hi - lo)`
|
|
7
7
|
// iso. Traits: `linear`, `lerp`, `equals`, `pack` (scalar-only).
|
|
8
8
|
import { tween } from "../../animation/index.js";
|
|
9
|
-
import { Cell, cachedDerive,
|
|
9
|
+
import { Cell, cachedDerive, fieldLens, isReadonly, reader, readNow, SKIP, } from "../cell.js";
|
|
10
10
|
import { Bool } from "./bool.js";
|
|
11
11
|
import { Num, num } from "./num.js";
|
|
12
12
|
export const add = (a, b) => ({ lo: a.lo + b.lo, hi: a.hi + b.hi });
|
|
@@ -62,11 +62,11 @@ export class Range extends Cell {
|
|
|
62
62
|
}
|
|
63
63
|
/** Start endpoint. Writes preserve `hi` (start-knob semantics). */
|
|
64
64
|
get lo() {
|
|
65
|
-
return
|
|
65
|
+
return fieldLens(this, "lo", Num);
|
|
66
66
|
}
|
|
67
67
|
/** End endpoint. Writes preserve `lo` (end-knob semantics). */
|
|
68
68
|
get hi() {
|
|
69
|
-
return
|
|
69
|
+
return fieldLens(this, "hi", Num);
|
|
70
70
|
}
|
|
71
71
|
get width() {
|
|
72
72
|
return cachedDerive(this, "width", Num, width);
|
|
@@ -112,7 +112,7 @@ export class Range extends Cell {
|
|
|
112
112
|
// defeats the mapped-tuple inference on `[this, t]`).
|
|
113
113
|
return Num.lens([this, t], ([r, tv]) => sample(r, tv), (v, [r]) => {
|
|
114
114
|
const w = r.hi - r.lo;
|
|
115
|
-
return [
|
|
115
|
+
return [SKIP, w === 0 ? 0 : (v - r.lo) / w];
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
/** Membership predicate. Conditional return type: a writable `Num`
|
|
@@ -127,8 +127,8 @@ export class Range extends Cell {
|
|
|
127
127
|
return Bool.lens([this, v], (vals) => contains(vals[0], vals[1]), (target, vals) => {
|
|
128
128
|
const [r, n] = vals;
|
|
129
129
|
if (contains(r, n) === target)
|
|
130
|
-
return [
|
|
131
|
-
return [
|
|
130
|
+
return [SKIP, SKIP];
|
|
131
|
+
return [SKIP, target ? clamp(r, n) : eject(r, n)];
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
134
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// stop prunes the no-op, and that slot stays put. A missing delimiter
|
|
21
21
|
// rejects the whole edit. Adjacent slots with no literal between them are
|
|
22
22
|
// ambiguous to split — avoid empty inter-slot literals.
|
|
23
|
+
import { SKIP } from "../cell.js";
|
|
23
24
|
import { num } from "./num.js";
|
|
24
25
|
import { Str, str } from "./str.js";
|
|
25
26
|
/** Identity codec for string slots. */
|
|
@@ -66,7 +67,7 @@ const render = (literals, slots, vals) => {
|
|
|
66
67
|
};
|
|
67
68
|
const parse = (literals, slots, edited) => {
|
|
68
69
|
const k = slots.length;
|
|
69
|
-
const reject = () => new Array(k).fill(
|
|
70
|
+
const reject = () => new Array(k).fill(SKIP);
|
|
70
71
|
const updates = reject();
|
|
71
72
|
const lit0 = literals[0] ?? "";
|
|
72
73
|
if (!edited.startsWith(lit0))
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Invertibles (`add`, `sub`) return `: this` and ride on
|
|
4
4
|
// `Cell#lens(fwd, bwd)`; chained calls compose into a lens chain.
|
|
5
|
-
// Field-lens getters use `
|
|
5
|
+
// Field-lens getters use `fieldLens()`, so writability propagates through
|
|
6
6
|
// nested chains
|
|
7
7
|
// (`Transform.translate.x.value = 5` works on writable receivers).
|
|
8
8
|
import { tween } from "../../animation/index.js";
|
|
9
|
-
import { Cell,
|
|
9
|
+
import { Cell, fieldLens, reader, readNow } from "../cell.js";
|
|
10
10
|
import { Num } from "./num.js";
|
|
11
11
|
import { Vec, add as vAdd, equals as vEquals, lerp as vLerp, metric as vMetric, scale as vScale, sub as vSub, } from "./vec.js";
|
|
12
12
|
export const DEFAULT = {
|
|
@@ -75,19 +75,19 @@ export class Transform extends Cell {
|
|
|
75
75
|
return Transform.derive(() => lerp(this.value, readNow(b), readNow(t)));
|
|
76
76
|
}
|
|
77
77
|
get translate() {
|
|
78
|
-
return
|
|
78
|
+
return fieldLens(this, "translate", Vec);
|
|
79
79
|
}
|
|
80
80
|
get scale() {
|
|
81
|
-
return
|
|
81
|
+
return fieldLens(this, "scale", Vec);
|
|
82
82
|
}
|
|
83
83
|
get origin() {
|
|
84
|
-
return
|
|
84
|
+
return fieldLens(this, "origin", Vec);
|
|
85
85
|
}
|
|
86
86
|
get rotate() {
|
|
87
|
-
return
|
|
87
|
+
return fieldLens(this, "rotate", Num);
|
|
88
88
|
}
|
|
89
89
|
get opacity() {
|
|
90
|
-
return
|
|
90
|
+
return fieldLens(this, "opacity", Num);
|
|
91
91
|
}
|
|
92
92
|
/** Tween-builder, implied by the lerp trait. */
|
|
93
93
|
to(target, dur, ease) {
|
package/dist/core/values/tri.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// `Tri.value ∈ { true, false, "mixed" }` — Bool plus an unknown state
|
|
4
4
|
// fixed under negation. Strong-Kleene AND/OR follow the partial-info
|
|
5
5
|
// reading (`mixed AND false` → `false`, `mixed AND true` → `mixed`).
|
|
6
|
-
import { Cell } from "../cell.js";
|
|
6
|
+
import { Cell, SKIP } from "../cell.js";
|
|
7
7
|
const equals = (a, b) => a === b;
|
|
8
8
|
/** Kleene negation: `true` / `false` swap, `"mixed"` is fixed. */
|
|
9
9
|
export const not = (a) => (a === "mixed" ? "mixed" : !a);
|
|
@@ -55,7 +55,7 @@ export class Tri extends Cell {
|
|
|
55
55
|
return anyT;
|
|
56
56
|
}, (target, _vs) => {
|
|
57
57
|
if (target === "mixed")
|
|
58
|
-
return parents.map(() =>
|
|
58
|
+
return parents.map(() => SKIP);
|
|
59
59
|
return parents.map(() => target);
|
|
60
60
|
});
|
|
61
61
|
}
|
|
@@ -81,7 +81,7 @@ export class Tri extends Cell {
|
|
|
81
81
|
return "mixed";
|
|
82
82
|
}, (target, _vs) => {
|
|
83
83
|
if (target === "mixed")
|
|
84
|
-
return parents.map(() =>
|
|
84
|
+
return parents.map(() => SKIP);
|
|
85
85
|
return parents.map(() => target);
|
|
86
86
|
});
|
|
87
87
|
}
|
package/dist/core/values/vec.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Invertibles return `: this` and ride on `Cell#lens(fwd, bwd)`;
|
|
4
4
|
// chained calls compose into a lens chain. Field-lens getters use
|
|
5
|
-
// `
|
|
5
|
+
// `fieldLens()` (propagates writability); `cachedDerive()` wraps RO views.
|
|
6
6
|
import { tween } from "../../animation/index.js";
|
|
7
|
-
import { Cell, cachedDerive,
|
|
7
|
+
import { Cell, cachedDerive, fieldLens, reader, readNow, SKIP, } from "../cell.js";
|
|
8
8
|
import { Num, num } from "./num.js";
|
|
9
9
|
export const add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
10
10
|
export const sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
@@ -149,10 +149,10 @@ export class Vec extends Cell {
|
|
|
149
149
|
return Num.derive(this, v => metric(v, readNow(other)));
|
|
150
150
|
}
|
|
151
151
|
get x() {
|
|
152
|
-
return
|
|
152
|
+
return fieldLens(this, "x", Num);
|
|
153
153
|
}
|
|
154
154
|
get y() {
|
|
155
|
-
return
|
|
155
|
+
return fieldLens(this, "y", Num);
|
|
156
156
|
}
|
|
157
157
|
get magnitude() {
|
|
158
158
|
return cachedDerive(this, "magnitude", Num, v => Math.hypot(v.x, v.y));
|
|
@@ -200,28 +200,24 @@ export function polar(center, r, a, policy = "rotate") {
|
|
|
200
200
|
bwd = (p, [cv, , av]) => {
|
|
201
201
|
const dx = p.x - cv.x;
|
|
202
202
|
const dy = p.y - cv.y;
|
|
203
|
-
return [
|
|
203
|
+
return [SKIP, Math.hypot(dx, dy), nearestAngle(Math.atan2(dy, dx), av)];
|
|
204
204
|
};
|
|
205
205
|
break;
|
|
206
206
|
case "translate":
|
|
207
207
|
bwd = (p, [cv, rv, av]) => {
|
|
208
208
|
const f = project(cv, rv, av);
|
|
209
|
-
return [{ x: cv.x + (p.x - f.x), y: cv.y + (p.y - f.y) },
|
|
209
|
+
return [{ x: cv.x + (p.x - f.x), y: cv.y + (p.y - f.y) }, SKIP, SKIP];
|
|
210
210
|
};
|
|
211
211
|
break;
|
|
212
212
|
case "radial":
|
|
213
213
|
bwd = (p, [cv, , av]) => {
|
|
214
214
|
const dx = p.x - cv.x;
|
|
215
215
|
const dy = p.y - cv.y;
|
|
216
|
-
return [
|
|
216
|
+
return [SKIP, dx * Math.cos(av) + dy * Math.sin(av), SKIP];
|
|
217
217
|
};
|
|
218
218
|
break;
|
|
219
219
|
case "circular":
|
|
220
|
-
bwd = (p, [cv, , av]) => [
|
|
221
|
-
undefined,
|
|
222
|
-
undefined,
|
|
223
|
-
nearestAngle(Math.atan2(p.y - cv.y, p.x - cv.x), av),
|
|
224
|
-
];
|
|
220
|
+
bwd = (p, [cv, , av]) => [SKIP, SKIP, nearestAngle(Math.atan2(p.y - cv.y, p.x - cv.x), av)];
|
|
225
221
|
break;
|
|
226
222
|
}
|
|
227
223
|
return Vec.lens([cSig, rSig, aSig], ([c, rv, av]) => project(c, rv, av), bwd);
|
package/dist/ext/timeline.js
CHANGED
|
@@ -32,8 +32,8 @@ class TimelineImpl {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
function makeClip(spec, clock) {
|
|
35
|
-
const at = Num.
|
|
36
|
-
const dur = Num.
|
|
35
|
+
const at = Num.coerce(spec.at);
|
|
36
|
+
const dur = Num.coerce(spec.dur);
|
|
37
37
|
const end = Num.derive(() => at.value + dur.value);
|
|
38
38
|
// Bidirectional span when both inputs are writable; RO derive when
|
|
39
39
|
// either is read-only. Mirrors `ResolvedSpan`.
|