@dopaminefx/core 0.1.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/engine/color.d.ts +71 -0
- package/dist/engine/color.d.ts.map +1 -0
- package/dist/engine/color.js +107 -0
- package/dist/engine/color.js.map +1 -0
- package/dist/engine/context.d.ts +54 -0
- package/dist/engine/context.d.ts.map +1 -0
- package/dist/engine/context.js +0 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/gl.d.ts +9 -0
- package/dist/engine/gl.d.ts.map +1 -0
- package/dist/engine/gl.js +39 -0
- package/dist/engine/gl.js.map +1 -0
- package/dist/engine/look/glsl.d.ts +95 -0
- package/dist/engine/look/glsl.d.ts.map +1 -0
- package/dist/engine/look/glsl.js +171 -0
- package/dist/engine/look/glsl.js.map +1 -0
- package/dist/engine/look/particles.glsl.d.ts +21 -0
- package/dist/engine/look/particles.glsl.d.ts.map +1 -0
- package/dist/engine/look/particles.glsl.js +44 -0
- package/dist/engine/look/particles.glsl.js.map +1 -0
- package/dist/engine/sdf.d.ts +77 -0
- package/dist/engine/sdf.d.ts.map +1 -0
- package/dist/engine/sdf.js +255 -0
- package/dist/engine/sdf.js.map +1 -0
- package/dist/engine/seed.d.ts +10 -0
- package/dist/engine/seed.d.ts.map +1 -0
- package/dist/engine/seed.js +20 -0
- package/dist/engine/seed.js.map +1 -0
- package/dist/engine/shadow.d.ts +41 -0
- package/dist/engine/shadow.d.ts.map +1 -0
- package/dist/engine/shadow.js +39 -0
- package/dist/engine/shadow.js.map +1 -0
- package/dist/engine/tempo.d.ts +33 -0
- package/dist/engine/tempo.d.ts.map +1 -0
- package/dist/engine/tempo.js +51 -0
- package/dist/engine/tempo.js.map +1 -0
- package/dist/framework/conductor.d.ts +100 -0
- package/dist/framework/conductor.d.ts.map +1 -0
- package/dist/framework/conductor.js +493 -0
- package/dist/framework/conductor.js.map +1 -0
- package/dist/framework/content.d.ts +67 -0
- package/dist/framework/content.d.ts.map +1 -0
- package/dist/framework/content.js +72 -0
- package/dist/framework/content.js.map +1 -0
- package/dist/framework/dope-pass.d.ts +131 -0
- package/dist/framework/dope-pass.d.ts.map +1 -0
- package/dist/framework/dope-pass.js +346 -0
- package/dist/framework/dope-pass.js.map +1 -0
- package/dist/framework/dope-zip.d.ts +22 -0
- package/dist/framework/dope-zip.d.ts.map +1 -0
- package/dist/framework/dope-zip.js +116 -0
- package/dist/framework/dope-zip.js.map +1 -0
- package/dist/framework/effect.d.ts +128 -0
- package/dist/framework/effect.d.ts.map +1 -0
- package/dist/framework/effect.js +19 -0
- package/dist/framework/effect.js.map +1 -0
- package/dist/framework/frame-expr.d.ts +124 -0
- package/dist/framework/frame-expr.d.ts.map +1 -0
- package/dist/framework/frame-expr.js +135 -0
- package/dist/framework/frame-expr.js.map +1 -0
- package/dist/framework/load-effect.d.ts +77 -0
- package/dist/framework/load-effect.d.ts.map +1 -0
- package/dist/framework/load-effect.js +135 -0
- package/dist/framework/load-effect.js.map +1 -0
- package/dist/framework/loader.d.ts +309 -0
- package/dist/framework/loader.d.ts.map +1 -0
- package/dist/framework/loader.js +266 -0
- package/dist/framework/loader.js.map +1 -0
- package/dist/framework/mood-registry.d.ts +58 -0
- package/dist/framework/mood-registry.d.ts.map +1 -0
- package/dist/framework/mood-registry.js +58 -0
- package/dist/framework/mood-registry.js.map +1 -0
- package/dist/framework/panel-runner.d.ts +96 -0
- package/dist/framework/panel-runner.d.ts.map +1 -0
- package/dist/framework/panel-runner.js +137 -0
- package/dist/framework/panel-runner.js.map +1 -0
- package/dist/framework/pass-common.d.ts +97 -0
- package/dist/framework/pass-common.d.ts.map +1 -0
- package/dist/framework/pass-common.js +178 -0
- package/dist/framework/pass-common.js.map +1 -0
- package/dist/framework/pass-runner.d.ts +183 -0
- package/dist/framework/pass-runner.d.ts.map +1 -0
- package/dist/framework/pass-runner.js +212 -0
- package/dist/framework/pass-runner.js.map +1 -0
- package/dist/framework/programs.d.ts +54 -0
- package/dist/framework/programs.d.ts.map +1 -0
- package/dist/framework/programs.js +33 -0
- package/dist/framework/programs.js.map +1 -0
- package/dist/framework/registry.d.ts +29 -0
- package/dist/framework/registry.d.ts.map +1 -0
- package/dist/framework/registry.js +38 -0
- package/dist/framework/registry.js.map +1 -0
- package/dist/framework/runtime.d.ts +19 -0
- package/dist/framework/runtime.d.ts.map +1 -0
- package/dist/framework/runtime.js +37 -0
- package/dist/framework/runtime.js.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/dist/overlay.d.ts +46 -0
- package/dist/overlay.d.ts.map +1 -0
- package/dist/overlay.js +79 -0
- package/dist/overlay.js.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +37 -0
- package/src/engine/color.ts +154 -0
- package/src/engine/context.ts +0 -0
- package/src/engine/gl.ts +46 -0
- package/src/engine/look/glsl.ts +183 -0
- package/src/engine/look/particles.glsl.ts +44 -0
- package/src/engine/sdf.ts +298 -0
- package/src/engine/seed.ts +23 -0
- package/src/engine/shadow.ts +66 -0
- package/src/engine/tempo.ts +54 -0
- package/src/framework/conductor.ts +604 -0
- package/src/framework/content.ts +113 -0
- package/src/framework/dope-pass.ts +432 -0
- package/src/framework/dope-zip.ts +125 -0
- package/src/framework/effect.ts +127 -0
- package/src/framework/frame-expr.ts +217 -0
- package/src/framework/load-effect.ts +204 -0
- package/src/framework/loader.ts +502 -0
- package/src/framework/mood-registry.ts +87 -0
- package/src/framework/panel-runner.ts +233 -0
- package/src/framework/pass-common.ts +222 -0
- package/src/framework/pass-runner.ts +391 -0
- package/src/framework/programs.ts +62 -0
- package/src/framework/registry.ts +44 -0
- package/src/framework/runtime.ts +38 -0
- package/src/index.ts +227 -0
- package/src/overlay.ts +109 -0
- package/src/types.ts +63 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.dope` effect loader.
|
|
3
|
+
*
|
|
4
|
+
* Parses a `.dope` JSON document (per docs/effect-format.md) and evaluates its
|
|
5
|
+
* `controls → render.params` mapping grammar, the OKLCH golden-angle palette,
|
|
6
|
+
* and the per-mood baseline table into the SAME flat render-param bag the engine
|
|
7
|
+
* consumes. Shader bodies stay referenced GLSL (the format references them); the
|
|
8
|
+
* loader is NOT a GLSL transpiler.
|
|
9
|
+
*
|
|
10
|
+
* The single load-bearing invariant (the correctness anchor): the PRNG is
|
|
11
|
+
* consumed in the SAME order as the legacy `resolve*Params` — `buildPalette`
|
|
12
|
+
* draws the base hue first (one `rng()` inside it), then the per-fire scatter
|
|
13
|
+
* (`rng() * 1000`). So a pinned seed reproduces the legacy output byte-for-byte;
|
|
14
|
+
* a vitest asserts this across a mood × intensity × whimsy × seed grid.
|
|
15
|
+
*
|
|
16
|
+
* The grammar is intentionally tiny + non-Turing-complete (no loops, no user
|
|
17
|
+
* functions) so it is safe to evaluate from an untrusted file and trivial to
|
|
18
|
+
* port to Swift for the Metal backend.
|
|
19
|
+
*/
|
|
20
|
+
import { buildPalette, oklchToLinearSrgb } from "../engine/color.js";
|
|
21
|
+
import { mulberry32 } from "../engine/seed.js";
|
|
22
|
+
import { NPR_TIME_STEP_MS } from "../engine/tempo.js";
|
|
23
|
+
const clamp01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x);
|
|
24
|
+
const lerp = (a, b, t) => a + (b - a) * clamp01(t);
|
|
25
|
+
/** Read a named outline from a doc's geometry, or undefined. */
|
|
26
|
+
export function getOutline(doc, name) {
|
|
27
|
+
return doc.geometry?.outlines?.[name];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The mood a doc degrades to when asked for one it doesn't declare.
|
|
31
|
+
*
|
|
32
|
+
* Each effect declares its OWN moods (the success trio declares
|
|
33
|
+
* serene/celebratory/electric; the fail effect declares try-again/error/denied),
|
|
34
|
+
* so there is no single hardcoded fallback mood that exists for every effect.
|
|
35
|
+
* An effect's own default is, in order of preference:
|
|
36
|
+
* 1. its `controls.mood.default`, if that mood actually has a baseline, else
|
|
37
|
+
* 2. the first mood key in its `baselines` table.
|
|
38
|
+
* This keeps EVERY declared mood resolvable and makes an unknown mood degrade to
|
|
39
|
+
* the effect's own sensible default instead of throwing on a missing baseline.
|
|
40
|
+
*/
|
|
41
|
+
export function defaultMoodKey(doc) {
|
|
42
|
+
const controls = doc.controls;
|
|
43
|
+
const declared = controls?.mood?.default;
|
|
44
|
+
if (typeof declared === "string" && doc.baselines[declared])
|
|
45
|
+
return declared;
|
|
46
|
+
const first = Object.keys(doc.baselines)[0];
|
|
47
|
+
if (!first)
|
|
48
|
+
throw new Error("dope: document has no baselines to resolve a mood against");
|
|
49
|
+
return first;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve a requested mood name against a doc to a key the doc actually declares:
|
|
53
|
+
* the requested mood if it has a baseline, otherwise the doc's own default mood
|
|
54
|
+
* ({@link defaultMoodKey}). Returned key is used for BOTH the baseline table and
|
|
55
|
+
* the palette `perMood` register, so they always agree.
|
|
56
|
+
*/
|
|
57
|
+
function resolveMoodKey(doc, mood) {
|
|
58
|
+
return doc.baselines[mood] ? mood : defaultMoodKey(doc);
|
|
59
|
+
}
|
|
60
|
+
/** Evaluate a grammar node to a number. Pure; matches mood.ts arithmetic. */
|
|
61
|
+
export function evalExpr(node, ctx) {
|
|
62
|
+
if (typeof node === "number")
|
|
63
|
+
return node;
|
|
64
|
+
if ("const" in node)
|
|
65
|
+
return node.const;
|
|
66
|
+
if ("control" in node)
|
|
67
|
+
return clamp01(ctx.controls[node.control] ?? 0);
|
|
68
|
+
if ("baseline" in node) {
|
|
69
|
+
const v = ctx.baseline[node.baseline];
|
|
70
|
+
if (v === undefined)
|
|
71
|
+
throw new Error(`dope: unknown baseline "${node.baseline}"`);
|
|
72
|
+
return v;
|
|
73
|
+
}
|
|
74
|
+
if ("lerp" in node) {
|
|
75
|
+
const [c, a, b] = node.lerp;
|
|
76
|
+
return lerp(a, b, ctx.controls[c] ?? 0);
|
|
77
|
+
}
|
|
78
|
+
if ("mul" in node)
|
|
79
|
+
return node.mul.reduce((p, n) => p * evalExpr(n, ctx), 1);
|
|
80
|
+
if ("add" in node)
|
|
81
|
+
return node.add.reduce((p, n) => p + evalExpr(n, ctx), 0);
|
|
82
|
+
if ("sub" in node) {
|
|
83
|
+
const parts = node.sub.map((n) => evalExpr(n, ctx));
|
|
84
|
+
return parts.slice(1).reduce((p, n) => p - n, parts[0] ?? 0);
|
|
85
|
+
}
|
|
86
|
+
if ("round" in node)
|
|
87
|
+
return Math.round(evalExpr(node.round, ctx));
|
|
88
|
+
if ("floor" in node)
|
|
89
|
+
return Math.floor(evalExpr(node.floor, ctx));
|
|
90
|
+
if ("mix" in node) {
|
|
91
|
+
const [a, b, c] = node.mix;
|
|
92
|
+
const va = evalExpr(a, ctx);
|
|
93
|
+
const vb = evalExpr(b, ctx);
|
|
94
|
+
return va + (vb - va) * clamp01(ctx.controls[c] ?? 0);
|
|
95
|
+
}
|
|
96
|
+
if ("max" in node)
|
|
97
|
+
return Math.max(...node.max.map((n) => evalExpr(n, ctx)));
|
|
98
|
+
if ("min" in node)
|
|
99
|
+
return Math.min(...node.min.map((n) => evalExpr(n, ctx)));
|
|
100
|
+
throw new Error(`dope: unknown expr node ${JSON.stringify(node)}`);
|
|
101
|
+
}
|
|
102
|
+
/** Apply a param spec's post-clamp flags. */
|
|
103
|
+
function applyFlags(v, spec, consts) {
|
|
104
|
+
if (spec.clamp01)
|
|
105
|
+
v = clamp01(v);
|
|
106
|
+
if (spec.clampMax)
|
|
107
|
+
v = Math.min(v, consts[spec.clampMax] ?? Infinity);
|
|
108
|
+
if (spec.clampMin)
|
|
109
|
+
v = Math.max(v, consts[spec.clampMin] ?? -Infinity);
|
|
110
|
+
return v;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve a `.dope` doc + a feeling into the flat render-param bag (palette,
|
|
114
|
+
* style, durationMs, seed, scatter seed, and every `render.params` entry). The
|
|
115
|
+
* `scatterKey` is the name the legacy code gave the per-fire scatter offset
|
|
116
|
+
* (`moteSeed` / `inkSeed` / `comicSeed`) so the output keys match exactly.
|
|
117
|
+
*
|
|
118
|
+
* RNG order (the parity anchor): baseHue via buildPalette FIRST, then the
|
|
119
|
+
* scatter `rng()*1000` — identical to `resolve*Params`.
|
|
120
|
+
*/
|
|
121
|
+
export function resolveDopeParams(doc, input, consts, scatterKey,
|
|
122
|
+
/**
|
|
123
|
+
* Host theme override: three explicit OKLCH stops that REPLACE the generated
|
|
124
|
+
* golden-angle palette (a pinned brand palette). The base-hue rng() is still
|
|
125
|
+
* consumed first, so the per-fire scatter offset stays identical to the
|
|
126
|
+
* generated path — pinning the palette never shifts the mote/spray layout.
|
|
127
|
+
*/
|
|
128
|
+
paletteOverride) {
|
|
129
|
+
const i = clamp01(input.intensity);
|
|
130
|
+
const w = clamp01(input.whimsy);
|
|
131
|
+
// Degrade an undeclared mood to THIS effect's own default mood (not a hardcoded
|
|
132
|
+
// "celebratory", which the fail effect — and any non-success effect — has no
|
|
133
|
+
// baseline for). Declared moods always resolve to themselves, so parity holds.
|
|
134
|
+
const moodKey = resolveMoodKey(doc, input.mood);
|
|
135
|
+
const baseline = doc.baselines[moodKey];
|
|
136
|
+
const rng = mulberry32(input.seed);
|
|
137
|
+
const ctx = {
|
|
138
|
+
controls: { intensity: i, whimsy: w },
|
|
139
|
+
baseline,
|
|
140
|
+
consts,
|
|
141
|
+
};
|
|
142
|
+
const out = {
|
|
143
|
+
seed: input.seed,
|
|
144
|
+
style: w,
|
|
145
|
+
};
|
|
146
|
+
// durationMs (tempo)
|
|
147
|
+
if (doc.tempo.durationMs) {
|
|
148
|
+
out.durationMs = applyFlags(evalExpr(doc.tempo.durationMs.from, ctx), doc.tempo.durationMs, consts);
|
|
149
|
+
}
|
|
150
|
+
// render.params
|
|
151
|
+
for (const [name, spec] of Object.entries(doc.render.params)) {
|
|
152
|
+
if (name === "style")
|
|
153
|
+
continue; // style is the raw whimsy control, set above
|
|
154
|
+
out[name] = applyFlags(evalExpr(spec.from, ctx), spec, consts);
|
|
155
|
+
}
|
|
156
|
+
// Palette FIRST (consumes one rng() for the base hue inside buildPalette),
|
|
157
|
+
// matching the engine's call order exactly.
|
|
158
|
+
// Same resolved key as the baseline lookup, so palette + baselines always agree
|
|
159
|
+
// (an undeclared mood uses the effect's own default for both).
|
|
160
|
+
const reg = doc.palette.perMood[moodKey] ?? doc.palette.perMood[defaultMoodKey(doc)];
|
|
161
|
+
const chroma = evalExpr(doc.palette.chroma.from, { ...ctx, baseline: reg });
|
|
162
|
+
const generated = buildPalette(rng, {
|
|
163
|
+
lightness: reg.lightness,
|
|
164
|
+
chroma,
|
|
165
|
+
hueCenter: reg.hueCenter,
|
|
166
|
+
hueRange: reg.hueRange,
|
|
167
|
+
hueSpread: doc.palette.hueSpread,
|
|
168
|
+
});
|
|
169
|
+
// A host palette override REPLACES the generated stops (the base-hue rng() above
|
|
170
|
+
// was still consumed, so scatter parity holds), pinning a brand palette.
|
|
171
|
+
out.palette = paletteOverride ? paletteOverride.map(oklchToLinearSrgb) : generated;
|
|
172
|
+
// THEN the per-fire scatter offset (same rng() * 1000 as the engine).
|
|
173
|
+
out[scatterKey] = rng() * 1000;
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
// A `.dope` must be SELF-CONTAINED — it may inline assets (e.g. `data:` URIs) or
|
|
177
|
+
// reference bundled programs/assets by key or by a path RELATIVE to the package
|
|
178
|
+
// (resolved inside a `.dope` zip), but it must never point at the network or an
|
|
179
|
+
// absolute filesystem path. This keeps every effect portable and offline.
|
|
180
|
+
const REMOTE_REF_RE = /^(?:[a-z][a-z0-9+.-]*:)?\/\//i; // http(s)://, ftp://, //host
|
|
181
|
+
const ABS_PATH_RE = /^(?:\/|[A-Za-z]:[\\/])/; // /etc/..., C:\...
|
|
182
|
+
function assertStandalone(node, path = "$") {
|
|
183
|
+
if (typeof node === "string") {
|
|
184
|
+
if (REMOTE_REF_RE.test(node) || ABS_PATH_RE.test(node)) {
|
|
185
|
+
throw new Error(`dope: external asset reference is not allowed — a .dope must be ` +
|
|
186
|
+
`self-contained (inline or bundle assets). Offending value at ${path}: "${node}"`);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (Array.isArray(node)) {
|
|
191
|
+
node.forEach((v, i) => assertStandalone(v, `${path}[${i}]`));
|
|
192
|
+
}
|
|
193
|
+
else if (node && typeof node === "object") {
|
|
194
|
+
for (const [k, v] of Object.entries(node))
|
|
195
|
+
assertStandalone(v, `${path}.${k}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Tolerance for the loop whole-multiple checks (the step 1000/12 is not exactly
|
|
199
|
+
// representable, so an exact `% === 0` would be float-fragile).
|
|
200
|
+
const LOOP_EPS = 1e-6;
|
|
201
|
+
const isWhole = (x) => Math.abs(x - Math.round(x)) < LOOP_EPS;
|
|
202
|
+
/**
|
|
203
|
+
* Validate a `tempo.loop` block against the doc's baselines: the period must be
|
|
204
|
+
* positive, must tile the "animate on twos" grid (unless `snapAligned` is
|
|
205
|
+
* false), and every per-mood baseline `durationMs` must be a whole number of
|
|
206
|
+
* periods — the seam guarantee, moved from per-effect convention into the
|
|
207
|
+
* parser (identical on every platform).
|
|
208
|
+
*/
|
|
209
|
+
function assertValidLoop(loop, baselines) {
|
|
210
|
+
const p = loop.periodMs;
|
|
211
|
+
if (typeof p !== "number" || !Number.isFinite(p) || p <= 0) {
|
|
212
|
+
throw new Error(`dope: tempo.loop.periodMs must be a positive number (got ${JSON.stringify(p)})`);
|
|
213
|
+
}
|
|
214
|
+
if (loop.snapAligned !== false && !isWhole(p / NPR_TIME_STEP_MS)) {
|
|
215
|
+
throw new Error(`dope: tempo.loop.periodMs (${p}) is not a whole number of animate-on-twos steps (1000/12 ms)`);
|
|
216
|
+
}
|
|
217
|
+
for (const [mood, row] of Object.entries(baselines)) {
|
|
218
|
+
const d = row.durationMs;
|
|
219
|
+
if (d !== undefined && !isWhole(d / p)) {
|
|
220
|
+
throw new Error(`dope: baselines.${mood}.durationMs (${d}) is not a whole number of tempo.loop periods (${p} ms)`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Parse + validate a `.dope` document from a JSON string or already-parsed
|
|
226
|
+
* object. Rejects a wrong/absent magic or major version, any external
|
|
227
|
+
* (remote / absolute-path) asset reference — a `.dope` must be self-contained —
|
|
228
|
+
* and a `tempo.loop` that breaks the seam invariants.
|
|
229
|
+
* (A fuller JSON-Schema validation lives in CI against effect-format.schema.json.)
|
|
230
|
+
*/
|
|
231
|
+
export function parseDope(src) {
|
|
232
|
+
const doc = (typeof src === "string" ? JSON.parse(src) : src);
|
|
233
|
+
if (doc.fmt !== "dopamine-effect") {
|
|
234
|
+
throw new Error(`dope: not a Dopamine effect document (fmt="${doc.fmt}")`);
|
|
235
|
+
}
|
|
236
|
+
const major = Number(doc.v?.split(".")[0]);
|
|
237
|
+
if (!Number.isFinite(major) || major > 1) {
|
|
238
|
+
throw new Error(`dope: unsupported format version "${doc.v}"`);
|
|
239
|
+
}
|
|
240
|
+
if (!doc.render?.params || !doc.palette?.perMood || !doc.baselines) {
|
|
241
|
+
throw new Error("dope: document missing render.params / palette.perMood / baselines");
|
|
242
|
+
}
|
|
243
|
+
if (doc.tempo?.loop)
|
|
244
|
+
assertValidLoop(doc.tempo.loop, doc.baselines);
|
|
245
|
+
if (doc.render?.panel)
|
|
246
|
+
assertValidPanel(doc.render.panel, doc.binding);
|
|
247
|
+
assertStandalone(doc);
|
|
248
|
+
return doc;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* `render.panel` invariants: the panel sampler must be one of the declared
|
|
252
|
+
* `binding.samplers` (the panel is a texture binding like any other — one
|
|
253
|
+
* source of truth for the uniform list), and the unit must be a non-negative
|
|
254
|
+
* integer.
|
|
255
|
+
*/
|
|
256
|
+
function assertValidPanel(panel, binding) {
|
|
257
|
+
const samplers = (binding?.samplers ?? []).map((s) => (typeof s === "string" ? s : s.web));
|
|
258
|
+
if (!panel.sampler || !samplers.includes(panel.sampler)) {
|
|
259
|
+
throw new Error(`dope: render.panel.sampler "${panel.sampler}" is not a declared binding.samplers entry`);
|
|
260
|
+
}
|
|
261
|
+
const unit = panel.texture ?? 0;
|
|
262
|
+
if (!Number.isInteger(unit) || unit < 0) {
|
|
263
|
+
throw new Error(`dope: render.panel.texture must be a non-negative integer (got ${unit})`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/framework/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mood registry — the shared, effect-agnostic "emotional register" layer.
|
|
3
|
+
*
|
|
4
|
+
* A mood describes a *feeling baseline* (how warm, how energetic, how bright) in
|
|
5
|
+
* effect-neutral terms. Every effect reads the same resolved mood, so adding a
|
|
6
|
+
* mood ("triumphant", "focused", a brand mood) lights up across *all* effects at
|
|
7
|
+
* once — no per-effect edits.
|
|
8
|
+
*
|
|
9
|
+
* Why a register, not a full param table: the three built-in effects share their
|
|
10
|
+
* *color identity* per mood (the same `hueCenter`/`hueRange` — a serene blue, a
|
|
11
|
+
* celebratory roam, a hot electric band) but each effect has its own spatial /
|
|
12
|
+
* motion / typographic baselines (a bloom radius means nothing to a comic word).
|
|
13
|
+
* So the registry owns the shared register + a normalized `energy`, and each
|
|
14
|
+
* effect's `mood.ts` baseline table keys off the mood name (built-ins keep their
|
|
15
|
+
* exact tuned values; an unfamiliar mood is derived from the register + energy
|
|
16
|
+
* so it still renders sensibly everywhere). This keeps built-in output
|
|
17
|
+
* byte-identical to the legacy `resolve*Params` while making moods extensible.
|
|
18
|
+
*/
|
|
19
|
+
/** Effect-neutral description of a mood's shared color register + energy. */
|
|
20
|
+
export interface MoodSpec {
|
|
21
|
+
/** Preferred hue center in degrees (arousal rises blue→green→red). */
|
|
22
|
+
hueCenter: number;
|
|
23
|
+
/** Width of the random hue band around the center, in degrees. */
|
|
24
|
+
hueRange: number;
|
|
25
|
+
/** Perceptual lightness reference for palettes, 0..1. */
|
|
26
|
+
lightness: number;
|
|
27
|
+
/** Base chroma (colorfulness) reference for palettes, ~0..0.4. */
|
|
28
|
+
chroma: number;
|
|
29
|
+
/**
|
|
30
|
+
* Normalized energy 0..1 (serene → electric). Effects use this to derive a
|
|
31
|
+
* baseline for a mood they have no tuned table entry for (faster, denser,
|
|
32
|
+
* harder slams toward 1). Built-in effects ignore it for their built-in moods.
|
|
33
|
+
*/
|
|
34
|
+
energy: number;
|
|
35
|
+
}
|
|
36
|
+
/** A mood resolved for use: its spec plus the name it was registered under. */
|
|
37
|
+
export interface ResolvedMood extends MoodSpec {
|
|
38
|
+
readonly name: string;
|
|
39
|
+
}
|
|
40
|
+
/** The mood used when none is given or an unknown one is requested. */
|
|
41
|
+
export declare const DEFAULT_MOOD = "celebratory";
|
|
42
|
+
/**
|
|
43
|
+
* Register (or override) a mood. Returns the name so it can be used inline.
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* registerMood("triumphant", { hueCenter: 280, hueRange: 160,
|
|
47
|
+
* lightness: 0.8, chroma: 0.22, energy: 0.9 });
|
|
48
|
+
* await celebrate({ mood: "triumphant" }); // now works for ALL effects
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function registerMood(name: string, spec: MoodSpec): string;
|
|
52
|
+
/** Look up a mood, falling back to the default. Always returns a usable mood. */
|
|
53
|
+
export declare function resolveMood(name: string | undefined): ResolvedMood;
|
|
54
|
+
/** Whether a mood name is currently registered. */
|
|
55
|
+
export declare function hasMood(name: string): boolean;
|
|
56
|
+
/** Names of all registered moods (built-in + custom). */
|
|
57
|
+
export declare function moodNames(): string[];
|
|
58
|
+
//# sourceMappingURL=mood-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mood-registry.d.ts","sourceRoot":"","sources":["../../src/framework/mood-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,QAAQ;IACvB,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAeD,uEAAuE;AACvE,eAAO,MAAM,YAAY,gBAAgB,CAAC;AAE1C;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAGjE;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,CAGlE;AAED,mDAAmD;AACnD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,yDAAyD;AACzD,wBAAgB,SAAS,IAAI,MAAM,EAAE,CAEpC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mood registry — the shared, effect-agnostic "emotional register" layer.
|
|
3
|
+
*
|
|
4
|
+
* A mood describes a *feeling baseline* (how warm, how energetic, how bright) in
|
|
5
|
+
* effect-neutral terms. Every effect reads the same resolved mood, so adding a
|
|
6
|
+
* mood ("triumphant", "focused", a brand mood) lights up across *all* effects at
|
|
7
|
+
* once — no per-effect edits.
|
|
8
|
+
*
|
|
9
|
+
* Why a register, not a full param table: the three built-in effects share their
|
|
10
|
+
* *color identity* per mood (the same `hueCenter`/`hueRange` — a serene blue, a
|
|
11
|
+
* celebratory roam, a hot electric band) but each effect has its own spatial /
|
|
12
|
+
* motion / typographic baselines (a bloom radius means nothing to a comic word).
|
|
13
|
+
* So the registry owns the shared register + a normalized `energy`, and each
|
|
14
|
+
* effect's `mood.ts` baseline table keys off the mood name (built-ins keep their
|
|
15
|
+
* exact tuned values; an unfamiliar mood is derived from the register + energy
|
|
16
|
+
* so it still renders sensibly everywhere). This keeps built-in output
|
|
17
|
+
* byte-identical to the legacy `resolve*Params` while making moods extensible.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The three built-in moods. The register values mirror the per-mood columns
|
|
21
|
+
* shared by all three effects' baseline tables in `mood.ts`; `energy` orders
|
|
22
|
+
* them serene(0) → celebratory(0.5) → electric(1).
|
|
23
|
+
*/
|
|
24
|
+
const BUILTIN_MOODS = {
|
|
25
|
+
serene: { hueCenter: 230, hueRange: 120, lightness: 0.83, chroma: 0.1, energy: 0.0 },
|
|
26
|
+
celebratory: { hueCenter: 50, hueRange: 320, lightness: 0.81, chroma: 0.17, energy: 0.5 },
|
|
27
|
+
electric: { hueCenter: 35, hueRange: 150, lightness: 0.79, chroma: 0.24, energy: 1.0 },
|
|
28
|
+
};
|
|
29
|
+
const moods = new Map(Object.entries(BUILTIN_MOODS));
|
|
30
|
+
/** The mood used when none is given or an unknown one is requested. */
|
|
31
|
+
export const DEFAULT_MOOD = "celebratory";
|
|
32
|
+
/**
|
|
33
|
+
* Register (or override) a mood. Returns the name so it can be used inline.
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* registerMood("triumphant", { hueCenter: 280, hueRange: 160,
|
|
37
|
+
* lightness: 0.8, chroma: 0.22, energy: 0.9 });
|
|
38
|
+
* await celebrate({ mood: "triumphant" }); // now works for ALL effects
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function registerMood(name, spec) {
|
|
42
|
+
moods.set(name, spec);
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
/** Look up a mood, falling back to the default. Always returns a usable mood. */
|
|
46
|
+
export function resolveMood(name) {
|
|
47
|
+
const key = name && moods.has(name) ? name : DEFAULT_MOOD;
|
|
48
|
+
return { name: key, ...moods.get(key) };
|
|
49
|
+
}
|
|
50
|
+
/** Whether a mood name is currently registered. */
|
|
51
|
+
export function hasMood(name) {
|
|
52
|
+
return moods.has(name);
|
|
53
|
+
}
|
|
54
|
+
/** Names of all registered moods (built-in + custom). */
|
|
55
|
+
export function moodNames() {
|
|
56
|
+
return [...moods.keys()];
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=mood-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mood-registry.js","sourceRoot":"","sources":["../../src/framework/mood-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAyBH;;;;GAIG;AACH,MAAM,aAAa,GAA6B;IAC9C,MAAM,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;IACpF,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;IACzF,QAAQ,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;CACvF,CAAC;AAEF,MAAM,KAAK,GAAG,IAAI,GAAG,CAAmB,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;AAEvE,uEAAuE;AACvE,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAc;IACvD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACtB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,WAAW,CAAC,IAAwB;IAClD,MAAM,GAAG,GAAG,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC;IAC1D,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAE,EAAE,CAAC;AAC3C,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,SAAS;IACvB,OAAO,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Canvas2D "panel" runner — the shared backbone for HYBRID effects whose
|
|
3
|
+
* per-frame content is drawn with Canvas2D (vector / text / shape) and then lit
|
|
4
|
+
* by a fragment shader (Comic Impact's hand-lettered word + jagged starburst +
|
|
5
|
+
* ink, lit into Ben-Day halftone + action lines + flash).
|
|
6
|
+
*
|
|
7
|
+
* It owns ALL the renderer/texture/upload/shadow plumbing that a Canvas2D-panel
|
|
8
|
+
* effect would otherwise hand-wire:
|
|
9
|
+
* • the single offscreen panel canvas + its 2D context,
|
|
10
|
+
* • resizing the panel to track the live GL canvas each frame,
|
|
11
|
+
* • the per-frame draw → `texImage2D` UPLOAD into BOTH the light + shadow
|
|
12
|
+
* contexts (the panel pixels change every frame, so — unlike a static SDF
|
|
13
|
+
* aux — they are re-uploaded each frame), with the FLIP_Y + non-premultiplied
|
|
14
|
+
* channel-encoding convention the panel shaders expect,
|
|
15
|
+
* • the standard shader uniforms (resolution, center, life, time, style,
|
|
16
|
+
* palette, the shadow-pass uniforms via shadowGeometry) + scalar render.params
|
|
17
|
+
* auto-bound by name convention,
|
|
18
|
+
* • the light + (optional) shadow pass via the program-cached contexts, and
|
|
19
|
+
* • disposing the per-fire panel textures.
|
|
20
|
+
*
|
|
21
|
+
* What stays per-effect (the honest boundary): the GLSL, a small `draw()` panel
|
|
22
|
+
* program (the Canvas2D draw — genuinely code-shaped vector/text logic stays JS),
|
|
23
|
+
* and a tiny config naming the shader's uniforms + the per-frame timing.
|
|
24
|
+
*/
|
|
25
|
+
import type { EffectContext, EffectInstance } from "./effect.js";
|
|
26
|
+
import type { PassParams } from "./pass-runner.js";
|
|
27
|
+
/** Per-frame timing context for a panel effect's draw + frame hooks. */
|
|
28
|
+
export interface PanelFrameInfo {
|
|
29
|
+
/** Raw elapsed time since start, ms (panels don't snap "on twos"). */
|
|
30
|
+
elapsedMs: number;
|
|
31
|
+
/** Normalized life 0..1 (elapsedMs / durationMs, clamped). */
|
|
32
|
+
life: number;
|
|
33
|
+
/** Device-pixel ratio the panel is rendered at. */
|
|
34
|
+
dpr: number;
|
|
35
|
+
/**
|
|
36
|
+
* Targeted element CENTRE in panel device px (canvas space, y-down). The
|
|
37
|
+
* centrepiece is drawn here instead of the canvas centre. Defaults to the
|
|
38
|
+
* canvas centre.
|
|
39
|
+
*/
|
|
40
|
+
centerPx: {
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Targeted element SIZE in device px. The centrepiece is sized to this box.
|
|
46
|
+
* Defaults to the full canvas.
|
|
47
|
+
*/
|
|
48
|
+
targetPx: {
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** A registered "panel program": the Canvas2D draw for one frame. */
|
|
54
|
+
export type PanelDraw<P extends PassParams = PassParams> = (panelCtx: CanvasRenderingContext2D, width: number, height: number, params: P, info: PanelFrameInfo) => void;
|
|
55
|
+
/** Config for one Canvas2D-panel effect. */
|
|
56
|
+
export interface PanelConfig<P extends PassParams = PassParams> {
|
|
57
|
+
/** Vertex + fragment GLSL (the per-effect look). */
|
|
58
|
+
vertex: string;
|
|
59
|
+
fragment: string;
|
|
60
|
+
/** Every uniform name the shader reads. */
|
|
61
|
+
uniforms: readonly string[];
|
|
62
|
+
/** Sampler uniform for the uploaded panel (bound to TEXTURE0). Default "uPanel". */
|
|
63
|
+
panelSampler?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Explicit `param name → uniform name` overrides for the scalar auto-binding;
|
|
66
|
+
* map to `null` to skip a param that isn't a uniform (e.g. a scatter seed).
|
|
67
|
+
*/
|
|
68
|
+
bindings?: Record<string, string | null>;
|
|
69
|
+
/** The shadow occluder "height" as a fraction of min canvas dim. */
|
|
70
|
+
shadowHeightFrac: number | ((params: P) => number);
|
|
71
|
+
/** The Canvas2D panel program (draws one frame). */
|
|
72
|
+
draw: PanelDraw<P>;
|
|
73
|
+
/**
|
|
74
|
+
* Compute the genuinely effect-specific TIME-VARYING uniforms for a frame
|
|
75
|
+
* (presence, flash, …). Returns a map of uniform name → float; `amp` (a
|
|
76
|
+
* well-known key) feeds shadowGeometry.
|
|
77
|
+
*/
|
|
78
|
+
frame(info: PanelFrameInfo, params: P): {
|
|
79
|
+
amp: number;
|
|
80
|
+
} & Record<string, number>;
|
|
81
|
+
/**
|
|
82
|
+
* Extra per-pass scalar uniforms that depend on the live canvas / dpr but are
|
|
83
|
+
* not plain params (e.g. `uDotSize = dotSize * dpr`, `uInkBoost`). Computed per
|
|
84
|
+
* pass.
|
|
85
|
+
*/
|
|
86
|
+
passUniforms?(canvas: HTMLCanvasElement, params: P, dpr: number, targetPx: {
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
}): Record<string, number>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build a drawable {@link EffectInstance} for a Canvas2D-panel effect from its
|
|
93
|
+
* config + resolved params + the runtime context.
|
|
94
|
+
*/
|
|
95
|
+
export declare function createPanelInstance<P extends PassParams>(config: PanelConfig<P>, params: P, ctx: EffectContext): EffectInstance;
|
|
96
|
+
//# sourceMappingURL=panel-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panel-runner.d.ts","sourceRoot":"","sources":["../../src/framework/panel-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAcnD,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC;;;OAGG;IACH,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,qEAAqE;AACrE,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,CACzD,QAAQ,EAAE,wBAAwB,EAClC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,CAAC,EACT,IAAI,EAAE,cAAc,KACjB,IAAI,CAAC;AAEV,4CAA4C;AAC5C,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU;IAC5D,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC;IACnD,oDAAoD;IACpD,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjF;;;;OAIG;IACH,YAAY,CAAC,CACX,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,CAAC,EACT,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAC1C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3B;AAQD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,UAAU,EACtD,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EACtB,MAAM,EAAE,CAAC,EACT,GAAG,EAAE,aAAa,GACjB,cAAc,CA6GhB"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Canvas2D "panel" runner — the shared backbone for HYBRID effects whose
|
|
3
|
+
* per-frame content is drawn with Canvas2D (vector / text / shape) and then lit
|
|
4
|
+
* by a fragment shader (Comic Impact's hand-lettered word + jagged starburst +
|
|
5
|
+
* ink, lit into Ben-Day halftone + action lines + flash).
|
|
6
|
+
*
|
|
7
|
+
* It owns ALL the renderer/texture/upload/shadow plumbing that a Canvas2D-panel
|
|
8
|
+
* effect would otherwise hand-wire:
|
|
9
|
+
* • the single offscreen panel canvas + its 2D context,
|
|
10
|
+
* • resizing the panel to track the live GL canvas each frame,
|
|
11
|
+
* • the per-frame draw → `texImage2D` UPLOAD into BOTH the light + shadow
|
|
12
|
+
* contexts (the panel pixels change every frame, so — unlike a static SDF
|
|
13
|
+
* aux — they are re-uploaded each frame), with the FLIP_Y + non-premultiplied
|
|
14
|
+
* channel-encoding convention the panel shaders expect,
|
|
15
|
+
* • the standard shader uniforms (resolution, center, life, time, style,
|
|
16
|
+
* palette, the shadow-pass uniforms via shadowGeometry) + scalar render.params
|
|
17
|
+
* auto-bound by name convention,
|
|
18
|
+
* • the light + (optional) shadow pass via the program-cached contexts, and
|
|
19
|
+
* • disposing the per-fire panel textures.
|
|
20
|
+
*
|
|
21
|
+
* What stays per-effect (the honest boundary): the GLSL, a small `draw()` panel
|
|
22
|
+
* program (the Canvas2D draw — genuinely code-shaped vector/text logic stays JS),
|
|
23
|
+
* and a tiny config naming the shader's uniforms + the per-frame timing.
|
|
24
|
+
*/
|
|
25
|
+
import { STANDARD_COMMON, applyFloatMap, beginProgram, bindFrameUniforms, bindPalette, bindScalars, bindShadowGeometry, bindTarget, compositeLightFragment, computeScalarBinds, } from "./pass-common.js";
|
|
26
|
+
// The panel runner adds `uCenter` (the panel composites around screen center)
|
|
27
|
+
// to the shared standard set — and binds the SAME anchor under the pass-runner's
|
|
28
|
+
// `uOrigin` name, so a panel shader may use either spelling (the single-source
|
|
29
|
+
// GLSL→MSL path maps `uOrigin` onto the packed struct's `origin` field).
|
|
30
|
+
const STANDARD = ["uCenter", "uOrigin", ...STANDARD_COMMON];
|
|
31
|
+
/**
|
|
32
|
+
* Build a drawable {@link EffectInstance} for a Canvas2D-panel effect from its
|
|
33
|
+
* config + resolved params + the runtime context.
|
|
34
|
+
*/
|
|
35
|
+
export function createPanelInstance(config, params, ctx) {
|
|
36
|
+
const pal = params.palette;
|
|
37
|
+
const dpr = ctx.dpr;
|
|
38
|
+
const sampler = config.panelSampler ?? "uPanel";
|
|
39
|
+
const allUniforms = [...new Set([...STANDARD, sampler, ...config.uniforms])];
|
|
40
|
+
// Backdrop-aware mode: the light pass emits premultiplied light (source-over,
|
|
41
|
+
// visible on any surface); the shadow pass keeps the opaque multiply fragment.
|
|
42
|
+
const lightFragment = ctx.composite?.premultiplied
|
|
43
|
+
? compositeLightFragment(config.fragment)
|
|
44
|
+
: config.fragment;
|
|
45
|
+
// The numeric params that auto-bind to a uniform.
|
|
46
|
+
const scalarBinds = computeScalarBinds(params, config.bindings ?? {});
|
|
47
|
+
// One offscreen Canvas2D panel, shared by both passes (drawn once per frame).
|
|
48
|
+
const panel = document.createElement("canvas");
|
|
49
|
+
const pctx = panel.getContext("2d", { alpha: true });
|
|
50
|
+
const lightTex = ctx.light.gl.createTexture();
|
|
51
|
+
const shadowTex = ctx.shadow ? ctx.shadow.gl.createTexture() : null;
|
|
52
|
+
for (const glc of [ctx.light, ctx.shadow]) {
|
|
53
|
+
if (!glc)
|
|
54
|
+
continue;
|
|
55
|
+
const { gl } = glc;
|
|
56
|
+
gl.bindTexture(gl.TEXTURE_2D, glc === ctx.light ? lightTex : shadowTex);
|
|
57
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
58
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
59
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
60
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
61
|
+
}
|
|
62
|
+
const heightFrac = () => typeof config.shadowHeightFrac === "function" ? config.shadowHeightFrac(params) : config.shadowHeightFrac;
|
|
63
|
+
const drawPass = (glc, tex, info, frameUniforms, isShadow) => {
|
|
64
|
+
const { gl } = glc;
|
|
65
|
+
const c = glc.canvas;
|
|
66
|
+
const { u } = beginProgram(glc, config.vertex, isShadow ? config.fragment : lightFragment, allUniforms);
|
|
67
|
+
// Upload the freshly-drawn panel (changes every frame).
|
|
68
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
69
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
70
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
71
|
+
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
|
|
72
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, panel);
|
|
73
|
+
if (u[sampler])
|
|
74
|
+
gl.uniform1i(u[sampler], 0);
|
|
75
|
+
// Extra per-pass scalar uniforms (dpr-scaled etc.).
|
|
76
|
+
applyFloatMap(gl, u, config.passUniforms?.(c, params, dpr, info.targetPx));
|
|
77
|
+
// Standard uniforms.
|
|
78
|
+
gl.uniform2f(u.uResolution, c.width, c.height);
|
|
79
|
+
bindTarget(gl, u, c, ctx.targetSize, dpr);
|
|
80
|
+
// uCenter is the impact/heart centre the PROCEDURAL parts radiate from (comic's
|
|
81
|
+
// action lines, heartburst's bloom). It must match where the panel centrepiece
|
|
82
|
+
// lands — the anchor — NOT the canvas centre, or they split (centrepiece on the
|
|
83
|
+
// target, lines/glow stuck at screen centre). `frag` here is vUv*uResolution
|
|
84
|
+
// (y-up, matching the flipped panel texture), so flip the anchor's y exactly as
|
|
85
|
+
// the pure-shader runner does for uOrigin.
|
|
86
|
+
gl.uniform2f(u.uCenter, ctx.anchor.x * dpr, c.height - ctx.anchor.y * dpr);
|
|
87
|
+
if (u.uOrigin)
|
|
88
|
+
gl.uniform2f(u.uOrigin, ctx.anchor.x * dpr, c.height - ctx.anchor.y * dpr);
|
|
89
|
+
gl.uniform1f(u.uLife, info.life);
|
|
90
|
+
gl.uniform1f(u.uTimeS, info.elapsedMs / 1000);
|
|
91
|
+
gl.uniform1f(u.uStyle, params.style);
|
|
92
|
+
bindPalette(gl, u, pal);
|
|
93
|
+
bindScalars(gl, u, params, scalarBinds);
|
|
94
|
+
bindFrameUniforms(gl, u, frameUniforms);
|
|
95
|
+
gl.uniform1f(u.uShadow, isShadow ? 1 : 0);
|
|
96
|
+
if (isShadow)
|
|
97
|
+
bindShadowGeometry(gl, u, c, heightFrac(), frameUniforms.amp, params.style);
|
|
98
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
99
|
+
};
|
|
100
|
+
let disposed = false;
|
|
101
|
+
return {
|
|
102
|
+
durationMs: params.durationMs,
|
|
103
|
+
renderAt(elapsedMs) {
|
|
104
|
+
if (disposed)
|
|
105
|
+
return;
|
|
106
|
+
const c = ctx.light.canvas;
|
|
107
|
+
if (panel.width !== c.width || panel.height !== c.height) {
|
|
108
|
+
panel.width = c.width;
|
|
109
|
+
panel.height = c.height;
|
|
110
|
+
}
|
|
111
|
+
const life = Math.min(Math.max(elapsedMs, 0) / params.durationMs, 1);
|
|
112
|
+
// The targeted element box → panel device px (y-down canvas space). Defaults
|
|
113
|
+
// to the canvas centre + full canvas, reproducing the old screen-centred pose.
|
|
114
|
+
const centerPx = { x: ctx.anchor.x * dpr, y: ctx.anchor.y * dpr };
|
|
115
|
+
const targetPx = ctx.targetSize
|
|
116
|
+
? { width: ctx.targetSize.width * dpr, height: ctx.targetSize.height * dpr }
|
|
117
|
+
: { width: c.width, height: c.height };
|
|
118
|
+
const info = { elapsedMs, life, dpr, centerPx, targetPx };
|
|
119
|
+
const frameUniforms = config.frame(info, params);
|
|
120
|
+
// Draw the shared offscreen panel once, then composite into each pass.
|
|
121
|
+
config.draw(pctx, c.width, c.height, params, info);
|
|
122
|
+
if (ctx.shadow && shadowTex)
|
|
123
|
+
drawPass(ctx.shadow, shadowTex, info, frameUniforms, true);
|
|
124
|
+
drawPass(ctx.light, lightTex, info, frameUniforms, false);
|
|
125
|
+
},
|
|
126
|
+
dispose() {
|
|
127
|
+
if (disposed)
|
|
128
|
+
return;
|
|
129
|
+
disposed = true;
|
|
130
|
+
if (lightTex)
|
|
131
|
+
ctx.light.gl.deleteTexture(lightTex);
|
|
132
|
+
if (shadowTex && ctx.shadow)
|
|
133
|
+
ctx.shadow.gl.deleteTexture(shadowTex);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=panel-runner.js.map
|