pixi-effects 0.30.1 → 0.32.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/CHANGELOG.md +33 -0
- package/README.md +6 -5
- package/ai/chat-template.html +3 -3
- package/ai/tools/check.mjs +42 -2
- package/ai/tools/render.mjs +34 -4
- package/ai/tools/varArgs.mjs +75 -0
- package/dist/{Base-Ne74ztNl.d.ts → Base-CXsMdmJB.d.ts} +1 -1
- package/dist/{Base-CB1_FPZD.d.cts → Base-pNq6sVGl.d.cts} +1 -1
- package/dist/Composition-LNMKWXSL.cjs +16 -0
- package/dist/{Composition-MUKR3FYD.cjs.map → Composition-LNMKWXSL.cjs.map} +1 -1
- package/dist/Composition-RF7QOEXT.js +7 -0
- package/dist/{Composition-RJGA7KDU.js.map → Composition-RF7QOEXT.js.map} +1 -1
- package/dist/Controller.cjs +42 -1
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +10 -2
- package/dist/Controller.d.ts +10 -2
- package/dist/Controller.js +42 -1
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-DdnpLpw1.d.cts → Movie-CL6SsAWe.d.cts} +90 -3
- package/dist/{Movie-C2rZFkrO.d.ts → Movie-f0tQG9y5.d.ts} +90 -3
- package/dist/Spans-NQGKVMR5.cjs +55 -0
- package/dist/Spans-NQGKVMR5.cjs.map +1 -0
- package/dist/Spans-SDXABOV6.js +53 -0
- package/dist/Spans-SDXABOV6.js.map +1 -0
- package/dist/{blendModes-RIVZXL5A.js → blendModes-MVRLG3T7.js} +4 -4
- package/dist/{blendModes-RIVZXL5A.js.map → blendModes-MVRLG3T7.js.map} +1 -1
- package/dist/{blendModes-ETMYWATF.cjs → blendModes-VTKDXWEN.cjs} +5 -5
- package/dist/{blendModes-ETMYWATF.cjs.map → blendModes-VTKDXWEN.cjs.map} +1 -1
- package/dist/{chunk-ZAXYPXKW.js → chunk-AW5NPDE4.js} +1367 -1150
- package/dist/chunk-AW5NPDE4.js.map +1 -0
- package/dist/{chunk-RCVZX2JP.js → chunk-C5BNHVKK.js} +36 -3
- package/dist/chunk-C5BNHVKK.js.map +1 -0
- package/dist/{chunk-WHNADXXG.cjs → chunk-CRUDOKSD.cjs} +91 -91
- package/dist/chunk-CRUDOKSD.cjs.map +1 -0
- package/dist/{chunk-A3Q3SDSH.js → chunk-D5BFQVKL.js} +6 -6
- package/dist/chunk-D5BFQVKL.js.map +1 -0
- package/dist/{chunk-LYOKH7KE.cjs → chunk-FCGYJCVO.cjs} +36 -3
- package/dist/chunk-FCGYJCVO.cjs.map +1 -0
- package/dist/{chunk-CRQFDY7E.js → chunk-FCMOREIS.js} +467 -58
- package/dist/chunk-FCMOREIS.js.map +1 -0
- package/dist/{chunk-HREBQAY4.cjs → chunk-K3MAEKFM.cjs} +493 -84
- package/dist/chunk-K3MAEKFM.cjs.map +1 -0
- package/dist/{chunk-4KB3GUEK.js → chunk-QFNS6BFL.js} +4 -4
- package/dist/{chunk-4KB3GUEK.js.map → chunk-QFNS6BFL.js.map} +1 -1
- package/dist/{chunk-ZVUO325O.cjs → chunk-QHDN6TF5.cjs} +1382 -1160
- package/dist/chunk-QHDN6TF5.cjs.map +1 -0
- package/dist/{chunk-2DXPTKQZ.cjs → chunk-Z437UWI4.cjs} +5 -5
- package/dist/{chunk-2DXPTKQZ.cjs.map → chunk-Z437UWI4.cjs.map} +1 -1
- package/dist/index.cjs +33 -33
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +5 -5
- package/dist/loader.css +1 -1
- package/dist/present.cjs +5 -5
- package/dist/present.d.cts +2 -2
- package/dist/present.d.ts +2 -2
- package/dist/present.js +5 -5
- package/dist/three.cjs +6 -6
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +2 -2
- package/dist/{types-BzIB65Vk.d.ts → types-CavnUvd5.d.cts} +28 -1
- package/dist/{types-BzIB65Vk.d.cts → types-CavnUvd5.d.ts} +28 -1
- package/docs/api.md +28 -1
- package/docs/dsl.md +46 -5
- package/llms-full.txt +136 -10
- package/package.json +83 -20
- package/skills/pixi-effects/SKILL.md +1 -1
- package/skills/pixi-effects/reference/cheatsheet.md +5 -2
- package/skills/pixi-effects/reference/pitfalls.md +5 -1
- package/skills/pixi-effects/reference/recipes.md +52 -0
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-MUKR3FYD.cjs +0 -16
- package/dist/Composition-RJGA7KDU.js +0 -7
- package/dist/chunk-A3Q3SDSH.js.map +0 -1
- package/dist/chunk-CRQFDY7E.js.map +0 -1
- package/dist/chunk-HREBQAY4.cjs.map +0 -1
- package/dist/chunk-LYOKH7KE.cjs.map +0 -1
- package/dist/chunk-RCVZX2JP.js.map +0 -1
- package/dist/chunk-WHNADXXG.cjs.map +0 -1
- package/dist/chunk-ZAXYPXKW.js.map +0 -1
- package/dist/chunk-ZVUO325O.cjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describeLayer, collectPropKeys } from './chunk-C5BNHVKK.js';
|
|
2
2
|
import { suggestName, warnUnknownOptions } from './chunk-BXOCEZ4U.js';
|
|
3
3
|
import * as PIXI from 'pixi.js';
|
|
4
|
-
import { Rectangle, Container, Color, GraphicsPath, Sprite, Texture, Graphics, Assets,
|
|
4
|
+
import { Rectangle, Container, FillGradient, Text, Color, GraphicsPath, Sprite, Texture, Graphics, Assets, Filter, GlProgram, defaultFilterVert, GpuProgram, UniformGroup, TextStyle, CanvasTextMetrics } from 'pixi.js';
|
|
5
5
|
|
|
6
6
|
// src/core/spring.ts
|
|
7
7
|
var SETTLE_TOL = 5e-3;
|
|
@@ -345,6 +345,35 @@ function pixiAccessors(target, key) {
|
|
|
345
345
|
return null;
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
|
+
function pathView(target, key) {
|
|
349
|
+
const parts = key.split(".");
|
|
350
|
+
const leaf = parts.pop();
|
|
351
|
+
const parentOf = () => {
|
|
352
|
+
let o = target;
|
|
353
|
+
for (const p of parts) {
|
|
354
|
+
o = o?.[p];
|
|
355
|
+
if (o === null || typeof o !== "object") return null;
|
|
356
|
+
}
|
|
357
|
+
return o;
|
|
358
|
+
};
|
|
359
|
+
const first = parentOf();
|
|
360
|
+
if (!first || !(leaf in first)) return null;
|
|
361
|
+
const view = {};
|
|
362
|
+
Object.defineProperty(view, leaf, {
|
|
363
|
+
get: () => parentOf()?.[leaf],
|
|
364
|
+
set: (v) => {
|
|
365
|
+
const parent = parentOf();
|
|
366
|
+
if (parent) parent[leaf] = v;
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
return { view, leaf };
|
|
370
|
+
}
|
|
371
|
+
var saidPaths = /* @__PURE__ */ new Set();
|
|
372
|
+
function warnLeadsNowhere(key) {
|
|
373
|
+
if (saidPaths.has(key)) return;
|
|
374
|
+
saidPaths.add(key);
|
|
375
|
+
console.warn(`pixi-effects: "${key}" leads nowhere on its target, so it is ignored (a point option of a filter must exist to be animated: give it in the filter's options, e.g. red: { x: 0, y: 0 })`);
|
|
376
|
+
}
|
|
348
377
|
function rawAccessor(target, key) {
|
|
349
378
|
return { id: key, kind: "raw", unit: 1, round: 1, get: () => target[key], set: (v) => {
|
|
350
379
|
target[key] = v;
|
|
@@ -462,7 +491,14 @@ var PureTimeline = class _PureTimeline {
|
|
|
462
491
|
for (const key of Object.keys(v)) {
|
|
463
492
|
if (RESERVED.has(key) || key === "pixi" || CALLBACKS.includes(key)) continue;
|
|
464
493
|
const value = v[key];
|
|
465
|
-
const
|
|
494
|
+
const path = key.includes(".") ? pathView(t, key) : null;
|
|
495
|
+
if (key.includes(".") && !path) {
|
|
496
|
+
warnLeadsNowhere(key);
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const at2 = path?.view ?? t, name = path?.leaf ?? key;
|
|
500
|
+
const acc = parseRel(value) ? plainAccessor(at2, name) : typeof value === "string" && parseCssColor(value) ? cssAccessor(at2, name) : rawAccessor(at2, name);
|
|
501
|
+
acc.id = key;
|
|
466
502
|
out.set(acc.id, { acc, value });
|
|
467
503
|
}
|
|
468
504
|
if (v.pixi) for (const key of Object.keys(v.pixi)) {
|
|
@@ -870,7 +906,14 @@ function setNow(target, vars) {
|
|
|
870
906
|
}
|
|
871
907
|
} else {
|
|
872
908
|
const v = vars[key];
|
|
873
|
-
|
|
909
|
+
const value = typeof v === "number" ? Math.round(v * 1e6) / 1e6 : v;
|
|
910
|
+
if (key.includes(".")) {
|
|
911
|
+
const path = pathView(t, key);
|
|
912
|
+
if (path) path.view[path.leaf] = value;
|
|
913
|
+
else warnLeadsNowhere(key);
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
plainAccessor(t, key).set(value);
|
|
874
917
|
}
|
|
875
918
|
}
|
|
876
919
|
}
|
|
@@ -954,6 +997,7 @@ var FUNCS = {
|
|
|
954
997
|
step: (edge, x) => x >= edge ? 1 : 0
|
|
955
998
|
};
|
|
956
999
|
var CONSTS = { PI: Math.PI };
|
|
1000
|
+
var EXPRESSION_NAMES = [...Object.keys(FUNCS), ...Object.keys(CONSTS)];
|
|
957
1001
|
function isDigit(c) {
|
|
958
1002
|
return c >= "0" && c <= "9";
|
|
959
1003
|
}
|
|
@@ -1460,6 +1504,8 @@ function buildScope(sequence, parent, root) {
|
|
|
1460
1504
|
const contain = w === 0 || h === 0 ? 1 : Math.min(W / w, H / h);
|
|
1461
1505
|
const cover = w === 0 || h === 0 ? 1 : Math.max(W / w, H / h);
|
|
1462
1506
|
return {
|
|
1507
|
+
...root.vars ?? {},
|
|
1508
|
+
// the declared numbers first: the built-in words below win
|
|
1463
1509
|
w,
|
|
1464
1510
|
h,
|
|
1465
1511
|
W,
|
|
@@ -2245,6 +2291,13 @@ var registry = {
|
|
|
2245
2291
|
function looksLikePixiFilter(x) {
|
|
2246
2292
|
return !!x && typeof x === "object" && typeof x.apply === "function";
|
|
2247
2293
|
}
|
|
2294
|
+
function copyPlain(v) {
|
|
2295
|
+
if (Array.isArray(v)) return v.map(copyPlain);
|
|
2296
|
+
if (v && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype) {
|
|
2297
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, copyPlain(x)]));
|
|
2298
|
+
}
|
|
2299
|
+
return v;
|
|
2300
|
+
}
|
|
2248
2301
|
function createFilter(spec) {
|
|
2249
2302
|
if (spec.type === "custom") {
|
|
2250
2303
|
const inst2 = spec.filter;
|
|
@@ -2255,7 +2308,8 @@ function createFilter(spec) {
|
|
|
2255
2308
|
named._name = spec.name;
|
|
2256
2309
|
return named;
|
|
2257
2310
|
}
|
|
2258
|
-
const { type, name, ...
|
|
2311
|
+
const { type, name, ...given } = spec;
|
|
2312
|
+
const params = copyPlain(given);
|
|
2259
2313
|
const Cls = registry[type];
|
|
2260
2314
|
const inst = Cls ? new Cls(params) : createNamedFilter(type, params);
|
|
2261
2315
|
inst._name = name;
|
|
@@ -2380,694 +2434,345 @@ var NullSequence = class extends Sequence {
|
|
|
2380
2434
|
}
|
|
2381
2435
|
};
|
|
2382
2436
|
|
|
2383
|
-
// src/
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
const hasInitial = !!spec.initial && "time" in spec.initial;
|
|
2387
|
-
const hasKf = (spec.keyframes ?? []).some((kf) => BAGS.some((b) => kf[b] && "time" in kf[b]));
|
|
2388
|
-
const speed = typeof spec.speed === "number" && Number.isFinite(spec.speed) && spec.speed !== 0 ? spec.speed : void 0;
|
|
2389
|
-
if (!hasInitial && !hasKf && speed === void 0) return null;
|
|
2390
|
-
const timeKfs = [];
|
|
2391
|
-
const restKfs = [];
|
|
2392
|
-
for (const kf of spec.keyframes ?? []) {
|
|
2393
|
-
const t = { ...kf }, r = { ...kf };
|
|
2394
|
-
let touched = false, left = false;
|
|
2395
|
-
for (const b of BAGS) {
|
|
2396
|
-
const bag = kf[b];
|
|
2397
|
-
if (!bag) {
|
|
2398
|
-
delete t[b];
|
|
2399
|
-
delete r[b];
|
|
2400
|
-
continue;
|
|
2401
|
-
}
|
|
2402
|
-
const { time, ...others } = bag;
|
|
2403
|
-
if ("time" in bag) {
|
|
2404
|
-
touched = true;
|
|
2405
|
-
t[b] = { [prop]: time };
|
|
2406
|
-
} else delete t[b];
|
|
2407
|
-
if (Object.keys(others).length) {
|
|
2408
|
-
r[b] = others;
|
|
2409
|
-
left = true;
|
|
2410
|
-
} else delete r[b];
|
|
2411
|
-
}
|
|
2412
|
-
if (touched) timeKfs.push(t);
|
|
2413
|
-
if (!touched || left) restKfs.push(r);
|
|
2414
|
-
}
|
|
2415
|
-
let restInitial = spec.initial;
|
|
2416
|
-
let start;
|
|
2417
|
-
if (hasInitial) {
|
|
2418
|
-
const { time, ...others } = spec.initial;
|
|
2419
|
-
start = typeof time === "number" && Number.isFinite(time) ? time : void 0;
|
|
2420
|
-
restInitial = others;
|
|
2421
|
-
}
|
|
2422
|
-
return { timeKfs, restKfs: restKfs.length ? restKfs : void 0, restInitial, start, speed };
|
|
2437
|
+
// src/sequences/gradient.ts
|
|
2438
|
+
function cssColor(c) {
|
|
2439
|
+
return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
|
|
2423
2440
|
}
|
|
2424
|
-
function
|
|
2425
|
-
const
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
for (const b of BAGS) {
|
|
2429
|
-
const bag = kf[b];
|
|
2430
|
-
if (bag && prop in bag) out[b] = { ...bag, [prop]: fix(bag[prop]) };
|
|
2431
|
-
}
|
|
2432
|
-
return out;
|
|
2433
|
-
});
|
|
2434
|
-
return { ...remap, timeKfs, start: remap.start === void 0 ? void 0 : fix(remap.start) };
|
|
2441
|
+
function normalizedStops(g) {
|
|
2442
|
+
const stops = g.stops ?? [];
|
|
2443
|
+
if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
|
|
2444
|
+
return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
|
|
2435
2445
|
}
|
|
2436
|
-
function
|
|
2437
|
-
const
|
|
2438
|
-
|
|
2446
|
+
function gradientOptions(g) {
|
|
2447
|
+
const colorStops = normalizedStops(g);
|
|
2448
|
+
const a = (g.angle ?? 90) * Math.PI / 180;
|
|
2449
|
+
const dx = Math.cos(a), dy = Math.sin(a);
|
|
2450
|
+
const h = (Math.abs(dx) + Math.abs(dy)) / 2;
|
|
2451
|
+
return {
|
|
2452
|
+
type: "linear",
|
|
2453
|
+
start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
|
|
2454
|
+
end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
|
|
2455
|
+
colorStops,
|
|
2456
|
+
textureSpace: "local"
|
|
2457
|
+
};
|
|
2439
2458
|
}
|
|
2440
|
-
function
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
for (const
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2459
|
+
function paintRadial(ctx, size, g) {
|
|
2460
|
+
const stops = normalizedStops(g);
|
|
2461
|
+
const [cx, cy] = g.center ?? [0.5, 0.5];
|
|
2462
|
+
const grad = ctx.createRadialGradient(cx * size, cy * size, (g.innerRadius ?? 0) * size, cx * size, cy * size, (g.radius ?? 0.5) * size);
|
|
2463
|
+
for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
|
|
2464
|
+
ctx.fillStyle = grad;
|
|
2465
|
+
ctx.fillRect(0, 0, size, size);
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
// src/expr/colorInterp.ts
|
|
2469
|
+
var CHROMA_FLOOR = 0.03;
|
|
2470
|
+
var CHROMA_FULL = 0.08;
|
|
2471
|
+
function buildColorInterp(from, to, space) {
|
|
2472
|
+
const fromRGBA = parseToRGBA(from);
|
|
2473
|
+
const toRGBA = parseToRGBA(to);
|
|
2474
|
+
const fromOk = rgbToOklab(fromRGBA);
|
|
2475
|
+
const toOk = rgbToOklab(toRGBA);
|
|
2476
|
+
if (space === "oklab") {
|
|
2477
|
+
return (t) => {
|
|
2478
|
+
const L = lerp(fromOk.L, toOk.L, t);
|
|
2479
|
+
const a = lerp(fromOk.a, toOk.a, t);
|
|
2480
|
+
const b = lerp(fromOk.b, toOk.b, t);
|
|
2481
|
+
const alpha = lerp(fromRGBA.a, toRGBA.a, t);
|
|
2482
|
+
return rgbaString(oklabToRgb({ L, a, b }), alpha);
|
|
2483
|
+
};
|
|
2450
2484
|
}
|
|
2451
|
-
|
|
2485
|
+
const fromLCH = oklabToOklch(fromOk);
|
|
2486
|
+
const toLCH = oklabToOklch(toOk);
|
|
2487
|
+
const fromHue = fromLCH.h, toHue = toLCH.h;
|
|
2488
|
+
fromLCH.h = borrowHue(fromHue, fromLCH.C, toHue);
|
|
2489
|
+
toLCH.h = borrowHue(toHue, toLCH.C, fromHue);
|
|
2490
|
+
const hueDelta = shortestHueDelta(fromLCH.h, toLCH.h);
|
|
2491
|
+
return (t) => {
|
|
2492
|
+
const L = lerp(fromLCH.L, toLCH.L, t);
|
|
2493
|
+
const C = lerp(fromLCH.C, toLCH.C, t);
|
|
2494
|
+
const h = fromLCH.h + hueDelta * t;
|
|
2495
|
+
const alpha = lerp(fromRGBA.a, toRGBA.a, t);
|
|
2496
|
+
const ab = oklchToOklab({ L, C, h });
|
|
2497
|
+
return rgbaString(oklabToRgb(ab), alpha);
|
|
2498
|
+
};
|
|
2452
2499
|
}
|
|
2453
|
-
function
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2500
|
+
function borrowHue(own, chroma, other) {
|
|
2501
|
+
const u = Math.min(1, Math.max(0, (chroma - CHROMA_FLOOR) / (CHROMA_FULL - CHROMA_FLOOR)));
|
|
2502
|
+
const keep = u * u * (3 - 2 * u);
|
|
2503
|
+
return own + shortestHueDelta(own, other) * (1 - keep);
|
|
2504
|
+
}
|
|
2505
|
+
function parseToRGBA(input) {
|
|
2506
|
+
if (typeof input === "number") {
|
|
2507
|
+
return { r: (input >> 16 & 255) / 255, g: (input >> 8 & 255) / 255, b: (input & 255) / 255, a: 1 };
|
|
2458
2508
|
}
|
|
2459
|
-
const s =
|
|
2460
|
-
|
|
2461
|
-
|
|
2509
|
+
const s = input.trim();
|
|
2510
|
+
if (s.startsWith("#")) return parseHex(s);
|
|
2511
|
+
if (s.startsWith("rgb")) return parseRgbFunc(s);
|
|
2512
|
+
return { r: 0, g: 0, b: 0, a: 1 };
|
|
2462
2513
|
}
|
|
2463
|
-
function
|
|
2464
|
-
const
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
tab[k] = v;
|
|
2477
|
-
const t = k * h;
|
|
2478
|
-
if (t >= at && t <= at + duration) {
|
|
2479
|
-
if (v < min) min = v;
|
|
2480
|
-
if (v > max) max = v;
|
|
2481
|
-
}
|
|
2514
|
+
function parseHex(s) {
|
|
2515
|
+
const hex = s.slice(1);
|
|
2516
|
+
let r = 0, g = 0, b = 0, a = 1;
|
|
2517
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
2518
|
+
r = parseInt(hex[0] + hex[0], 16) / 255;
|
|
2519
|
+
g = parseInt(hex[1] + hex[1], 16) / 255;
|
|
2520
|
+
b = parseInt(hex[2] + hex[2], 16) / 255;
|
|
2521
|
+
if (hex.length === 4) a = parseInt(hex[3] + hex[3], 16) / 255;
|
|
2522
|
+
} else if (hex.length === 6 || hex.length === 8) {
|
|
2523
|
+
r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
2524
|
+
g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
2525
|
+
b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
2526
|
+
if (hex.length === 8) a = parseInt(hex.slice(6, 8), 16) / 255;
|
|
2482
2527
|
}
|
|
2483
|
-
|
|
2528
|
+
return { r, g, b, a };
|
|
2529
|
+
}
|
|
2530
|
+
function parseRgbFunc(s) {
|
|
2531
|
+
const inner = s.replace(/^rgba?\(/, "").replace(/\)$/, "");
|
|
2532
|
+
const parts = inner.split(/[,\s/]+/).filter(Boolean).map(parseFloat);
|
|
2484
2533
|
return {
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
},
|
|
2490
|
-
min: Number.isFinite(min) ? min : 0,
|
|
2491
|
-
max: Number.isFinite(max) ? max : 0
|
|
2534
|
+
r: (parts[0] ?? 0) / 255,
|
|
2535
|
+
g: (parts[1] ?? 0) / 255,
|
|
2536
|
+
b: (parts[2] ?? 0) / 255,
|
|
2537
|
+
a: parts.length > 3 ? parts[3] ?? 1 : 1
|
|
2492
2538
|
};
|
|
2493
2539
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
var MAX_FOV = 179;
|
|
2500
|
-
var NEAR = 1;
|
|
2501
|
-
function clampFov(fov) {
|
|
2502
|
-
if (!Number.isFinite(fov)) return DEFAULT_FOV;
|
|
2503
|
-
return Math.min(MAX_FOV, Math.max(MIN_FOV, fov));
|
|
2540
|
+
function rgbaString(rgb, alpha) {
|
|
2541
|
+
const r = Math.round(clamp01(rgb.r) * 255);
|
|
2542
|
+
const g = Math.round(clamp01(rgb.g) * 255);
|
|
2543
|
+
const b = Math.round(clamp01(rgb.b) * 255);
|
|
2544
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
2504
2545
|
}
|
|
2505
|
-
function
|
|
2506
|
-
return
|
|
2546
|
+
function srgbToLinear(c) {
|
|
2547
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
2507
2548
|
}
|
|
2508
|
-
function
|
|
2549
|
+
function linearToSrgb(c) {
|
|
2550
|
+
return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
2551
|
+
}
|
|
2552
|
+
function rgbToOklab(rgba) {
|
|
2553
|
+
const r = srgbToLinear(rgba.r);
|
|
2554
|
+
const g = srgbToLinear(rgba.g);
|
|
2555
|
+
const b = srgbToLinear(rgba.b);
|
|
2556
|
+
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
2557
|
+
const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
2558
|
+
const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
2559
|
+
const l_ = Math.cbrt(l);
|
|
2560
|
+
const m_ = Math.cbrt(m);
|
|
2561
|
+
const s_ = Math.cbrt(s);
|
|
2509
2562
|
return {
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
lookAtX: compW / 2,
|
|
2514
|
-
lookAtY: compH / 2,
|
|
2515
|
-
lookAtZ: 0,
|
|
2516
|
-
fov
|
|
2563
|
+
L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
|
|
2564
|
+
a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
|
|
2565
|
+
b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_
|
|
2517
2566
|
};
|
|
2518
2567
|
}
|
|
2519
|
-
function
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
fy = 0;
|
|
2527
|
-
fz = -1;
|
|
2528
|
-
len = 1;
|
|
2529
|
-
}
|
|
2530
|
-
fx /= len;
|
|
2531
|
-
fy /= len;
|
|
2532
|
-
fz /= len;
|
|
2533
|
-
let rx = -fz;
|
|
2534
|
-
const ry = 0;
|
|
2535
|
-
let rz = fx;
|
|
2536
|
-
const rlen = Math.hypot(rx, rz);
|
|
2537
|
-
if (rlen < 1e-9) {
|
|
2538
|
-
rx = 1;
|
|
2539
|
-
rz = 0;
|
|
2540
|
-
} else {
|
|
2541
|
-
rx /= rlen;
|
|
2542
|
-
rz /= rlen;
|
|
2543
|
-
}
|
|
2544
|
-
const dx = ry * fz - rz * fy;
|
|
2545
|
-
const dy = rz * fx - rx * fz;
|
|
2546
|
-
const dz = rx * fy - ry * fx;
|
|
2568
|
+
function oklabToRgb(lab) {
|
|
2569
|
+
const l_ = lab.L + 0.3963377774 * lab.a + 0.2158037573 * lab.b;
|
|
2570
|
+
const m_ = lab.L - 0.1055613458 * lab.a - 0.0638541728 * lab.b;
|
|
2571
|
+
const s_ = lab.L - 0.0894841775 * lab.a - 1.291485548 * lab.b;
|
|
2572
|
+
const l = l_ * l_ * l_;
|
|
2573
|
+
const m = m_ * m_ * m_;
|
|
2574
|
+
const s = s_ * s_ * s_;
|
|
2547
2575
|
return {
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
rx,
|
|
2552
|
-
ry,
|
|
2553
|
-
rz,
|
|
2554
|
-
dx,
|
|
2555
|
-
dy,
|
|
2556
|
-
dz,
|
|
2557
|
-
fx,
|
|
2558
|
-
fy,
|
|
2559
|
-
fz,
|
|
2560
|
-
focal: compH / 2 / Math.tan(clampFov(cam.fov) * DEG3 / 2),
|
|
2561
|
-
halfW: compW / 2,
|
|
2562
|
-
halfH: compH / 2
|
|
2576
|
+
r: linearToSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
|
|
2577
|
+
g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
|
|
2578
|
+
b: linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)
|
|
2563
2579
|
};
|
|
2564
2580
|
}
|
|
2565
|
-
function
|
|
2566
|
-
|
|
2567
|
-
let y = (py - t.pivotY) * t.scaleY;
|
|
2568
|
-
let z = 0;
|
|
2569
|
-
let c = Math.cos(t.rotationX);
|
|
2570
|
-
let s = Math.sin(t.rotationX);
|
|
2571
|
-
[y, z] = [y * c - z * s, y * s + z * c];
|
|
2572
|
-
c = Math.cos(t.rotationY);
|
|
2573
|
-
s = Math.sin(t.rotationY);
|
|
2574
|
-
[x, z] = [x * c + z * s, -x * s + z * c];
|
|
2575
|
-
c = Math.cos(t.rotationZ);
|
|
2576
|
-
s = Math.sin(t.rotationZ);
|
|
2577
|
-
[x, y] = [x * c - y * s, x * s + y * c];
|
|
2578
|
-
return { x: x + t.x, y: y + t.y, z: z + t.z };
|
|
2579
|
-
}
|
|
2580
|
-
function projectPoint(b, p) {
|
|
2581
|
-
const vx = p.x - b.cx;
|
|
2582
|
-
const vy = p.y - b.cy;
|
|
2583
|
-
const vz = p.z - b.cz;
|
|
2584
|
-
const xc = vx * b.rx + vy * b.ry + vz * b.rz;
|
|
2585
|
-
const yc = vx * b.dx + vy * b.dy + vz * b.dz;
|
|
2586
|
-
const zc = vx * b.fx + vy * b.fy + vz * b.fz;
|
|
2587
|
-
return { x: b.halfW + b.focal * xc / zc, y: b.halfH + b.focal * yc / zc, depth: zc };
|
|
2581
|
+
function oklabToOklch(lab) {
|
|
2582
|
+
return { L: lab.L, C: Math.hypot(lab.a, lab.b), h: Math.atan2(lab.b, lab.a) };
|
|
2588
2583
|
}
|
|
2589
|
-
function
|
|
2590
|
-
|
|
2591
|
-
[frame.x, frame.y],
|
|
2592
|
-
[frame.x + frame.width, frame.y],
|
|
2593
|
-
[frame.x + frame.width, frame.y + frame.height],
|
|
2594
|
-
[frame.x, frame.y + frame.height]
|
|
2595
|
-
];
|
|
2596
|
-
let visible = true;
|
|
2597
|
-
const corners = [];
|
|
2598
|
-
for (const [px, py] of pts) {
|
|
2599
|
-
const p = projectPoint(b, layerToWorld(t, px, py));
|
|
2600
|
-
if (!(p.depth > NEAR)) visible = false;
|
|
2601
|
-
corners.push(p.x, p.y);
|
|
2602
|
-
}
|
|
2603
|
-
const origin = projectPoint(b, { x: t.x, y: t.y, z: t.z });
|
|
2604
|
-
return { corners, depth: origin.depth, visible };
|
|
2584
|
+
function oklchToOklab(lch) {
|
|
2585
|
+
return { L: lch.L, a: lch.C * Math.cos(lch.h), b: lch.C * Math.sin(lch.h) };
|
|
2605
2586
|
}
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
var DEFAULT_APERTURE = 30;
|
|
2609
|
-
var MIN_BLUR = 0.05;
|
|
2610
|
-
var MAX_BLUR = 32;
|
|
2611
|
-
var MANY_BLURRED = 20;
|
|
2612
|
-
function blurRadius(aperture, focal, depth, focusDepth) {
|
|
2613
|
-
if (!(aperture > 0) || !(focal > 0) || !(depth > 0) || !(focusDepth > 0)) return 0;
|
|
2614
|
-
const r = aperture * focal * Math.abs(1 / depth - 1 / focusDepth) / 2;
|
|
2615
|
-
return Math.min(r, MAX_BLUR);
|
|
2587
|
+
function lerp(a, b, t) {
|
|
2588
|
+
return a + (b - a) * t;
|
|
2616
2589
|
}
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
return /^[A-Za-z_][\w-]*$/.test(s) && !SCOPE_NAMES.has(s);
|
|
2590
|
+
function clamp01(v) {
|
|
2591
|
+
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
2620
2592
|
}
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
};
|
|
2628
|
-
read2(props.initial);
|
|
2629
|
-
for (const kf of props.keyframes ?? []) for (const bag of [kf.set, kf.to, kf.from]) read2(bag);
|
|
2630
|
-
return out;
|
|
2593
|
+
function shortestHueDelta(from, to) {
|
|
2594
|
+
const TWO_PI = Math.PI * 2;
|
|
2595
|
+
let d = (to - from) % TWO_PI;
|
|
2596
|
+
if (d > Math.PI) d -= TWO_PI;
|
|
2597
|
+
if (d < -Math.PI) d += TWO_PI;
|
|
2598
|
+
return d;
|
|
2631
2599
|
}
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
if (!bag || !isName(v, known)) return bag;
|
|
2637
|
-
return { ...bag, focus: zOf(v) ?? 0 };
|
|
2638
|
-
};
|
|
2600
|
+
|
|
2601
|
+
// src/sequences/gradientAnim.ts
|
|
2602
|
+
var PATCH_KEYS = ["type", "angle", "center", "innerRadius", "radius", "stops"];
|
|
2603
|
+
function gradStateFrom(g) {
|
|
2639
2604
|
return {
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2605
|
+
type: g.type ?? "linear",
|
|
2606
|
+
angle: g.angle ?? 90,
|
|
2607
|
+
center: [...g.center ?? [0.5, 0.5]],
|
|
2608
|
+
innerRadius: g.innerRadius ?? 0,
|
|
2609
|
+
radius: g.radius ?? 0.5,
|
|
2610
|
+
stops: (g.stops ?? []).map((s) => Array.isArray(s) ? { offset: s[0], color: cssColor(s[1]) } : { offset: s.offset, color: cssColor(s.color) })
|
|
2643
2611
|
};
|
|
2644
2612
|
}
|
|
2645
|
-
function
|
|
2646
|
-
const
|
|
2647
|
-
if (typeof
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
const known = new Set(siblings.map((s) => s.name).filter((n) => !!n));
|
|
2657
|
-
for (const name of focusNames(camera, known)) {
|
|
2658
|
-
if (said.has(name)) continue;
|
|
2659
|
-
said.add(name);
|
|
2660
|
-
const same = siblings.filter((s) => s.name === name);
|
|
2661
|
-
const hit = same.find((s) => s.threeD) ?? same[0];
|
|
2662
|
-
if (!hit) out.push({ kind: "missing", name, hint: suggestName(name, threeD) });
|
|
2663
|
-
else if (!hit.threeD) out.push({ kind: "not-threeD", name });
|
|
2664
|
-
else {
|
|
2665
|
-
if (same.length > 1) out.push({ kind: "duplicate", name, count: same.length });
|
|
2666
|
-
if (movesZ(hit)) out.push({ kind: "moving", name });
|
|
2667
|
-
}
|
|
2613
|
+
function mergeGrad(base, patch) {
|
|
2614
|
+
const out = { ...base, center: [...base.center], stops: base.stops.map((s) => ({ ...s })) };
|
|
2615
|
+
if (patch === null || typeof patch !== "object" || Array.isArray(patch)) return out;
|
|
2616
|
+
const p = patch;
|
|
2617
|
+
if (typeof p.angle === "number") out.angle = p.angle;
|
|
2618
|
+
if (Array.isArray(p.center)) out.center = [Number(p.center[0]), Number(p.center[1])];
|
|
2619
|
+
if (typeof p.innerRadius === "number") out.innerRadius = p.innerRadius;
|
|
2620
|
+
if (typeof p.radius === "number") out.radius = p.radius;
|
|
2621
|
+
if (p.stops !== void 0) {
|
|
2622
|
+
const next = gradStateFrom({ stops: p.stops }).stops;
|
|
2623
|
+
if (next.length === base.stops.length) out.stops = next;
|
|
2668
2624
|
}
|
|
2669
2625
|
return out;
|
|
2670
2626
|
}
|
|
2671
|
-
var
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
/** True when the camera writes `focus` or `aperture` anywhere: depth of field is on. */
|
|
2679
|
-
dof = false;
|
|
2680
|
-
/** True when the camera writes any fog key anywhere: depth fog is on (nothing is made for a camera without fog). */
|
|
2681
|
-
fog = false;
|
|
2682
|
-
fogC = null;
|
|
2683
|
-
zOfName = null;
|
|
2684
|
-
layerNames = /* @__PURE__ */ new Set();
|
|
2685
|
-
async build() {
|
|
2686
|
-
const compW = this.parent?.width ?? this.root.width;
|
|
2687
|
-
this.compH = this.parent?.height ?? this.root.height;
|
|
2688
|
-
if (this.duration === void 0) {
|
|
2689
|
-
this.duration = this.parent?.duration ?? this.root.duration;
|
|
2690
|
-
}
|
|
2691
|
-
const home = homeCamera(compW, this.compH);
|
|
2692
|
-
const carrier = new Container();
|
|
2693
|
-
carrier.x = home.x;
|
|
2694
|
-
carrier.y = home.y;
|
|
2695
|
-
carrier.z = home.z;
|
|
2696
|
-
carrier.lookAtX = home.lookAtX;
|
|
2697
|
-
carrier.lookAtY = home.lookAtY;
|
|
2698
|
-
carrier.lookAtZ = home.lookAtZ;
|
|
2699
|
-
carrier.fov = home.fov;
|
|
2700
|
-
carrier.focus = 0;
|
|
2701
|
-
carrier.aperture = DEFAULT_APERTURE;
|
|
2702
|
-
carrier.offsetX = carrier.offsetY = carrier.offsetZ = 0;
|
|
2703
|
-
carrier.lookOffsetX = carrier.lookOffsetY = carrier.lookOffsetZ = 0;
|
|
2704
|
-
carrier.fogNear = home.z;
|
|
2705
|
-
carrier.fogFar = home.z * 3;
|
|
2706
|
-
carrier.fogColor = "#000000";
|
|
2707
|
-
carrier.fogAmount = 1;
|
|
2708
|
-
this.target = carrier;
|
|
2709
|
-
const keys = collectPropKeys(this.spec);
|
|
2710
|
-
this.autoZ = !keys.has("z");
|
|
2711
|
-
this.dof = keys.has("focus") || keys.has("aperture");
|
|
2712
|
-
this.fog = keys.has("fogNear") || keys.has("fogFar") || keys.has("fogColor") || keys.has("fogAmount");
|
|
2713
|
-
}
|
|
2714
|
-
/** The aperture to use now: off unless depth of field is on and the value is a number of 0 or more (said once if it is not). */
|
|
2715
|
-
apertureNow(a) {
|
|
2716
|
-
if (!this.dof) return 0;
|
|
2717
|
-
if (!(a >= 0) && !this.warnedAperture) {
|
|
2718
|
-
this.warnedAperture = true;
|
|
2719
|
-
const who = this.spec.name ? ` "${this.spec.name}"` : "";
|
|
2720
|
-
console.warn(`pixi-effects: camera${who}: aperture ${a} is not a number of 0 or more; depth of field is off (it is the lens diameter in px: 30 is the default, 0 turns it off)`);
|
|
2627
|
+
var objectHint = "a keyframe's fillGradient must be an object like { angle: 200, stops: [[0, '#f00'], [1, '#00f']] }";
|
|
2628
|
+
function validateGradientKeyframes(spec, who, warn = (m) => console.warn(m), kind = "shape") {
|
|
2629
|
+
const said = /* @__PURE__ */ new Set();
|
|
2630
|
+
const say = (m) => {
|
|
2631
|
+
if (!said.has(m)) {
|
|
2632
|
+
said.add(m);
|
|
2633
|
+
warn(`pixi-effects: ${who}: ${m}`);
|
|
2721
2634
|
}
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
const base = super.displayProps();
|
|
2731
|
-
return this.zOfName ? withFocusResolved(base, this.zOfName, this.layerNames) : base;
|
|
2635
|
+
};
|
|
2636
|
+
const startSpec = spec.fillGradient ?? spec.initial?.fillGradient;
|
|
2637
|
+
const start = startSpec && typeof startSpec === "object" && Array.isArray(startSpec.stops) ? gradStateFrom(startSpec) : null;
|
|
2638
|
+
let animated = false;
|
|
2639
|
+
if (kind === "text") {
|
|
2640
|
+
const radial = (g) => !!g && typeof g === "object" && ["type", "center", "innerRadius", "radius"].some((k) => g[k] !== void 0 && !(k === "type" && g.type === "linear"));
|
|
2641
|
+
const asked = radial(startSpec) || (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => radial(b?.fillGradient)));
|
|
2642
|
+
if (asked) say("a text gradient is linear: type, center, innerRadius and radius are ignored (only angle and stops apply); use a shape for a radial gradient");
|
|
2732
2643
|
}
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
/** The fog now (colour as 0..1 channels, distances, 0..1 amount), or null when the camera writes no fog key. */
|
|
2761
|
-
fogState() {
|
|
2762
|
-
if (!this.fog) return null;
|
|
2763
|
-
const c = this.target;
|
|
2764
|
-
const fc = this.fogC ??= new Color();
|
|
2765
|
-
try {
|
|
2766
|
-
fc.setValue(c.fogColor);
|
|
2767
|
-
} catch {
|
|
2768
|
-
fc.setValue(0);
|
|
2644
|
+
for (const kf of spec.keyframes ?? []) {
|
|
2645
|
+
for (const bag of [kf.set, kf.to, kf.from]) {
|
|
2646
|
+
if (!bag) continue;
|
|
2647
|
+
for (const [key, value] of Object.entries(bag)) {
|
|
2648
|
+
if (key === "fillGradient") {
|
|
2649
|
+
animated = true;
|
|
2650
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
2651
|
+
say(objectHint);
|
|
2652
|
+
continue;
|
|
2653
|
+
}
|
|
2654
|
+
const p = value;
|
|
2655
|
+
for (const k of Object.keys(p)) {
|
|
2656
|
+
if (PATCH_KEYS.includes(k)) continue;
|
|
2657
|
+
const guess = suggestName(k, PATCH_KEYS.filter((x) => x !== "type"));
|
|
2658
|
+
say(`fillGradient.${k} is not a gradient property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${PATCH_KEYS.join(", ")}); it is ignored`);
|
|
2659
|
+
}
|
|
2660
|
+
if (p.type !== void 0 && start && p.type !== start.type) say("fillGradient.type cannot change over time (linear <-> radial); it is ignored");
|
|
2661
|
+
if (p.stops !== void 0 && start) {
|
|
2662
|
+
const n = Array.isArray(p.stops) ? p.stops.length : 0;
|
|
2663
|
+
if (n !== start.stops.length) say(`a fillGradient keyframe must give the same number of stops as the gradient has (${start.stops.length}, got ${n}); the stops are left as they were`);
|
|
2664
|
+
}
|
|
2665
|
+
} else if (/^fillGradient\./i.test(key) || /^gradient/i.test(key)) {
|
|
2666
|
+
const tail = key.replace(/^fillGradient\./i, "").replace(/^gradient/i, "");
|
|
2667
|
+
const sub2 = tail ? tail[0].toLowerCase() + tail.slice(1) : "angle";
|
|
2668
|
+
say(`"${key}" is not an animatable property: write fillGradient: { ${PATCH_KEYS.includes(sub2) ? sub2 : "angle"} } (the whole gradient is one object) in the keyframe's to / from / set`);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2769
2671
|
}
|
|
2770
|
-
const [r, g, b] = fc.toArray();
|
|
2771
|
-
const amount = Number.isFinite(c.fogAmount) ? Math.min(1, Math.max(0, c.fogAmount)) : 1;
|
|
2772
|
-
return { r, g, b, near: c.fogNear, far: c.fogFar, amount };
|
|
2773
|
-
}
|
|
2774
|
-
/** Lifespan on the global timeline (valid after bindTimeline). */
|
|
2775
|
-
window() {
|
|
2776
|
-
const start = this.absoluteStart ?? this.at;
|
|
2777
|
-
return { start, end: start + (this.duration ?? 0) };
|
|
2778
2672
|
}
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
// src/space/lighting.ts
|
|
2782
|
-
var LIGHT_KINDS = ["ambient", "point", "spot", "parallel"];
|
|
2783
|
-
var FALLOFFS = ["none", "smooth", "inverseSquare"];
|
|
2784
|
-
var LIGHT_PROPS = ["x", "y", "z", "lookAtX", "lookAtY", "lookAtZ", "intensity", "color", "coneAngle", "coneFeather", "radius", "falloffDistance", "shadowDarkness", "shadowDiffusion"];
|
|
2785
|
-
var MAX_LIGHTS = 8;
|
|
2786
|
-
var MAX_CASTERS = 4;
|
|
2787
|
-
var SHADOW_TAPS = 16;
|
|
2788
|
-
var FALLOFF_ID = { none: 0, smooth: 1, inverseSquare: 2 };
|
|
2789
|
-
var KIND_ID = { point: 1, spot: 2, parallel: 3 };
|
|
2790
|
-
function smoothstep(e0, e1, x) {
|
|
2791
|
-
const t = Math.min(1, Math.max(0, (x - e0) / (e1 - e0)));
|
|
2792
|
-
return t * t * (3 - 2 * t);
|
|
2793
|
-
}
|
|
2794
|
-
var DEG4 = Math.PI / 180;
|
|
2795
|
-
function norm(v) {
|
|
2796
|
-
const l = Math.hypot(v.x, v.y, v.z) || 1;
|
|
2797
|
-
return { x: v.x / l, y: v.y / l, z: v.z / l };
|
|
2673
|
+
if (animated && !start) say("a keyframe animates fillGradient but the layer has none to start from: give it a fillGradient first (top level or in initial)");
|
|
2798
2674
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2675
|
+
function tweenGradient(timeline, holder, fromPatch, toPatch, duration, ease, at, colorSpace, onChange, loop = {}) {
|
|
2676
|
+
timeline.tweenValue({
|
|
2677
|
+
holder,
|
|
2678
|
+
id: "grad",
|
|
2679
|
+
get: () => holder.grad,
|
|
2680
|
+
set: (g) => {
|
|
2681
|
+
holder.grad = g;
|
|
2682
|
+
onChange();
|
|
2683
|
+
},
|
|
2684
|
+
from: fromPatch !== void 0 ? (prev) => mergeGrad(prev, fromPatch) : void 0,
|
|
2685
|
+
to: (start, prev) => toPatch !== void 0 ? mergeGrad(start, toPatch) : prev,
|
|
2686
|
+
make: (a, b) => {
|
|
2687
|
+
const colors = a.stops.map((s, i) => colorSpace === "rgb" ? interpolateColors(s.color, b.stops[i].color) : buildColorInterp(s.color, b.stops[i].color, colorSpace));
|
|
2688
|
+
return (t) => {
|
|
2689
|
+
const l = (x, y) => x + (y - x) * t;
|
|
2690
|
+
return {
|
|
2691
|
+
type: a.type,
|
|
2692
|
+
angle: l(a.angle, b.angle),
|
|
2693
|
+
center: [l(a.center[0], b.center[0]), l(a.center[1], b.center[1])],
|
|
2694
|
+
innerRadius: l(a.innerRadius, b.innerRadius),
|
|
2695
|
+
radius: l(a.radius, b.radius),
|
|
2696
|
+
stops: a.stops.map((s, i) => ({ offset: l(s.offset, b.stops[i].offset), color: colors[i](t) }))
|
|
2697
|
+
};
|
|
2698
|
+
};
|
|
2699
|
+
},
|
|
2700
|
+
duration,
|
|
2701
|
+
ease,
|
|
2702
|
+
at,
|
|
2703
|
+
repeat: loop.repeat,
|
|
2704
|
+
yoyo: loop.yoyo,
|
|
2705
|
+
repeatDelay: loop.repeatDelay
|
|
2706
|
+
});
|
|
2807
2707
|
}
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
ambient[2] += l.b * l.intensity;
|
|
2817
|
-
continue;
|
|
2818
|
-
}
|
|
2819
|
-
if (n >= MAX_LIGHTS) continue;
|
|
2820
|
-
const d = norm({ x: l.lookAtX - l.x, y: l.lookAtY - l.y, z: l.lookAtZ - l.z });
|
|
2821
|
-
const { cosOuter, cosInner } = coneCos(l);
|
|
2822
|
-
pos.set([l.x, l.y, l.z, KIND_ID[l.kind]], 4 * n);
|
|
2823
|
-
dir.set([d.x, d.y, d.z, cosOuter], 4 * n);
|
|
2824
|
-
col.set([l.r * l.intensity, l.g * l.intensity, l.b * l.intensity, cosInner], 4 * n);
|
|
2825
|
-
fall.set([FALLOFF_ID[l.falloff], l.radius, l.falloffDistance, l.castsShadows ? 1 : 0], 4 * n);
|
|
2826
|
-
shadow.set([l.castsShadows ? l.shadowDarkness : 0, l.shadowDiffusion, 0, 0], 4 * n);
|
|
2827
|
-
n++;
|
|
2708
|
+
var PAINT_SIZE = 512;
|
|
2709
|
+
var GradientPainter = class {
|
|
2710
|
+
constructor(size = PAINT_SIZE) {
|
|
2711
|
+
this.size = size;
|
|
2712
|
+
this.canvas.width = size;
|
|
2713
|
+
this.canvas.height = size;
|
|
2714
|
+
this.tex = Texture.from(this.canvas);
|
|
2715
|
+
globalThis.__gradientPainters = (globalThis.__gradientPainters ?? 0) + 1;
|
|
2828
2716
|
}
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
else {
|
|
2847
|
-
const d = sub({ x: l.x, y: l.y, z: l.z }, p);
|
|
2848
|
-
dist = Math.hypot(d.x, d.y, d.z);
|
|
2849
|
-
L = { x: d.x / dist, y: d.y / dist, z: d.z / dist };
|
|
2717
|
+
size;
|
|
2718
|
+
canvas = document.createElement("canvas");
|
|
2719
|
+
tex;
|
|
2720
|
+
/** Paint `g` and return what `Graphics.fill(...)` takes. */
|
|
2721
|
+
paint(g) {
|
|
2722
|
+
const ctx = this.canvas.getContext("2d"), s = this.size;
|
|
2723
|
+
if (!ctx) return new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((x) => [x.offset, x.color]) }));
|
|
2724
|
+
ctx.clearRect(0, 0, s, s);
|
|
2725
|
+
if (g.type === "radial") {
|
|
2726
|
+
paintRadial(ctx, s, { stops: g.stops.map((x) => [x.offset, x.color]), center: g.center, innerRadius: g.innerRadius, radius: g.radius });
|
|
2727
|
+
} else {
|
|
2728
|
+
const a = g.angle * Math.PI / 180, dx = Math.cos(a), dy = Math.sin(a);
|
|
2729
|
+
const h = (Math.abs(dx) + Math.abs(dy)) / 2;
|
|
2730
|
+
const grad = ctx.createLinearGradient((0.5 - dx * h) * s, (0.5 - dy * h) * s, (0.5 + dx * h) * s, (0.5 + dy * h) * s);
|
|
2731
|
+
for (const st of g.stops) grad.addColorStop(Math.min(1, Math.max(0, st.offset)), st.color);
|
|
2732
|
+
ctx.fillStyle = grad;
|
|
2733
|
+
ctx.fillRect(0, 0, s, s);
|
|
2850
2734
|
}
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2735
|
+
this.tex.source.update();
|
|
2736
|
+
return { texture: this.tex, textureSpace: "local" };
|
|
2737
|
+
}
|
|
2738
|
+
/** Give the texture and the canvas back (a layer that is destroyed must not leave them). */
|
|
2739
|
+
destroy() {
|
|
2740
|
+
this.tex.destroy(true);
|
|
2741
|
+
this.canvas.width = this.canvas.height = 0;
|
|
2742
|
+
globalThis.__gradientPainters = Math.max(0, (globalThis.__gradientPainters ?? 1) - 1);
|
|
2743
|
+
}
|
|
2744
|
+
};
|
|
2745
|
+
function bindGradientKeyframes(timeline, holder, keyframes, duration, offset, colorSpace, onChange) {
|
|
2746
|
+
const bag = (b) => b?.fillGradient;
|
|
2747
|
+
for (const kf of keyframes) {
|
|
2748
|
+
const at = offset + resolveAt(kf.at, duration);
|
|
2749
|
+
const setV = bag(kf.set), fromV = bag(kf.from), toV = bag(kf.to);
|
|
2750
|
+
if (setV !== void 0) {
|
|
2751
|
+
timeline.tweenValue({
|
|
2752
|
+
holder,
|
|
2753
|
+
id: "grad",
|
|
2754
|
+
get: () => holder.grad,
|
|
2755
|
+
set: (g) => {
|
|
2756
|
+
holder.grad = g;
|
|
2757
|
+
onChange();
|
|
2758
|
+
},
|
|
2759
|
+
to: (start) => mergeGrad(start, setV),
|
|
2760
|
+
make: (_a, b) => () => b,
|
|
2761
|
+
duration: 0,
|
|
2762
|
+
ease: "none",
|
|
2763
|
+
at
|
|
2764
|
+
});
|
|
2856
2765
|
}
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
const { cosOuter, cosInner } = coneCos(l);
|
|
2860
|
-
cone = smoothstep(cosOuter, cosInner, -dot(L, dirLA));
|
|
2766
|
+
if (fromV !== void 0 || toV !== void 0) {
|
|
2767
|
+
tweenGradient(timeline, holder, fromV, toV, kfDuration(kf), kf.ease ?? "none", at, colorSpace, onChange, loopVars(kf));
|
|
2861
2768
|
}
|
|
2862
|
-
const k = l.intensity * ndl * att * cone;
|
|
2863
|
-
out[0] += l.r * k;
|
|
2864
|
-
out[1] += l.g * k;
|
|
2865
|
-
out[2] += l.b * k;
|
|
2866
2769
|
}
|
|
2867
|
-
return out;
|
|
2868
2770
|
}
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
}
|
|
2875
|
-
var LightSequence = class extends Sequence {
|
|
2876
|
-
color = new Color();
|
|
2877
|
-
async build() {
|
|
2878
|
-
const w = this.parent?.width ?? this.root.width;
|
|
2879
|
-
const h = this.parent?.height ?? this.root.height;
|
|
2880
|
-
if (this.duration === void 0) this.duration = this.parent?.duration ?? this.root.duration;
|
|
2881
|
-
const c = new Container();
|
|
2882
|
-
c.x = w * 0.3;
|
|
2883
|
-
c.y = h * 0.2;
|
|
2884
|
-
c.z = homeDistance(h, 40) * 0.6;
|
|
2885
|
-
c.lookAtX = w / 2;
|
|
2886
|
-
c.lookAtY = h / 2;
|
|
2887
|
-
c.lookAtZ = 0;
|
|
2888
|
-
c.intensity = 1;
|
|
2889
|
-
c.color = "#ffffff";
|
|
2890
|
-
c.coneAngle = 90;
|
|
2891
|
-
c.coneFeather = 0.5;
|
|
2892
|
-
c.radius = 500;
|
|
2893
|
-
c.falloffDistance = 500;
|
|
2894
|
-
c.shadowDarkness = 1;
|
|
2895
|
-
c.shadowDiffusion = 0;
|
|
2896
|
-
this.target = c;
|
|
2897
|
-
}
|
|
2898
|
-
state() {
|
|
2899
|
-
const c = this.target;
|
|
2900
|
-
try {
|
|
2901
|
-
this.color.setValue(c.color);
|
|
2902
|
-
} catch {
|
|
2903
|
-
this.color.setValue(16777215);
|
|
2904
|
-
}
|
|
2905
|
-
const [r, g, b] = this.color.toArray();
|
|
2906
|
-
return {
|
|
2907
|
-
// a kind or falloff nobody knows is the default the warning promises (the shader must never see an unknown id)
|
|
2908
|
-
kind: LIGHT_KINDS.includes(this.spec.kind) ? this.spec.kind : "point",
|
|
2909
|
-
x: c.x,
|
|
2910
|
-
y: c.y,
|
|
2911
|
-
z: c.z,
|
|
2912
|
-
lookAtX: c.lookAtX,
|
|
2913
|
-
lookAtY: c.lookAtY,
|
|
2914
|
-
lookAtZ: c.lookAtZ,
|
|
2915
|
-
r,
|
|
2916
|
-
g,
|
|
2917
|
-
b,
|
|
2918
|
-
intensity: Number.isFinite(c.intensity) ? Math.max(0, c.intensity) : 1,
|
|
2919
|
-
coneAngle: c.coneAngle,
|
|
2920
|
-
coneFeather: c.coneFeather,
|
|
2921
|
-
falloff: FALLOFFS.includes(this.spec.falloff) ? this.spec.falloff : "none",
|
|
2922
|
-
radius: c.radius,
|
|
2923
|
-
falloffDistance: c.falloffDistance,
|
|
2924
|
-
castsShadows: !!this.spec.castsShadows,
|
|
2925
|
-
shadowDarkness: Math.min(1, Math.max(0, c.shadowDarkness)),
|
|
2926
|
-
shadowDiffusion: Math.max(0, c.shadowDiffusion)
|
|
2927
|
-
};
|
|
2928
|
-
}
|
|
2929
|
-
window() {
|
|
2930
|
-
const start = this.absoluteStart ?? this.at;
|
|
2931
|
-
return { start, end: start + (this.duration ?? 0) };
|
|
2932
|
-
}
|
|
2933
|
-
};
|
|
2934
|
-
|
|
2935
|
-
// src/core/revertibleSet.ts
|
|
2936
|
-
function revertibleSet(timeline, at, read2, write2, value, property) {
|
|
2937
|
-
timeline.setValue(property?.[0] ?? read2, property?.[1] ?? "set", at, read2, write2, value);
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
|
-
// src/expr/colorInterp.ts
|
|
2941
|
-
var CHROMA_FLOOR = 0.03;
|
|
2942
|
-
var CHROMA_FULL = 0.08;
|
|
2943
|
-
function buildColorInterp(from, to, space) {
|
|
2944
|
-
const fromRGBA = parseToRGBA(from);
|
|
2945
|
-
const toRGBA = parseToRGBA(to);
|
|
2946
|
-
const fromOk = rgbToOklab(fromRGBA);
|
|
2947
|
-
const toOk = rgbToOklab(toRGBA);
|
|
2948
|
-
if (space === "oklab") {
|
|
2949
|
-
return (t) => {
|
|
2950
|
-
const L = lerp(fromOk.L, toOk.L, t);
|
|
2951
|
-
const a = lerp(fromOk.a, toOk.a, t);
|
|
2952
|
-
const b = lerp(fromOk.b, toOk.b, t);
|
|
2953
|
-
const alpha = lerp(fromRGBA.a, toRGBA.a, t);
|
|
2954
|
-
return rgbaString(oklabToRgb({ L, a, b }), alpha);
|
|
2955
|
-
};
|
|
2956
|
-
}
|
|
2957
|
-
const fromLCH = oklabToOklch(fromOk);
|
|
2958
|
-
const toLCH = oklabToOklch(toOk);
|
|
2959
|
-
const fromHue = fromLCH.h, toHue = toLCH.h;
|
|
2960
|
-
fromLCH.h = borrowHue(fromHue, fromLCH.C, toHue);
|
|
2961
|
-
toLCH.h = borrowHue(toHue, toLCH.C, fromHue);
|
|
2962
|
-
const hueDelta = shortestHueDelta(fromLCH.h, toLCH.h);
|
|
2963
|
-
return (t) => {
|
|
2964
|
-
const L = lerp(fromLCH.L, toLCH.L, t);
|
|
2965
|
-
const C = lerp(fromLCH.C, toLCH.C, t);
|
|
2966
|
-
const h = fromLCH.h + hueDelta * t;
|
|
2967
|
-
const alpha = lerp(fromRGBA.a, toRGBA.a, t);
|
|
2968
|
-
const ab = oklchToOklab({ L, C, h });
|
|
2969
|
-
return rgbaString(oklabToRgb(ab), alpha);
|
|
2970
|
-
};
|
|
2971
|
-
}
|
|
2972
|
-
function borrowHue(own, chroma, other) {
|
|
2973
|
-
const u = Math.min(1, Math.max(0, (chroma - CHROMA_FLOOR) / (CHROMA_FULL - CHROMA_FLOOR)));
|
|
2974
|
-
const keep = u * u * (3 - 2 * u);
|
|
2975
|
-
return own + shortestHueDelta(own, other) * (1 - keep);
|
|
2976
|
-
}
|
|
2977
|
-
function parseToRGBA(input) {
|
|
2978
|
-
if (typeof input === "number") {
|
|
2979
|
-
return { r: (input >> 16 & 255) / 255, g: (input >> 8 & 255) / 255, b: (input & 255) / 255, a: 1 };
|
|
2980
|
-
}
|
|
2981
|
-
const s = input.trim();
|
|
2982
|
-
if (s.startsWith("#")) return parseHex(s);
|
|
2983
|
-
if (s.startsWith("rgb")) return parseRgbFunc(s);
|
|
2984
|
-
return { r: 0, g: 0, b: 0, a: 1 };
|
|
2985
|
-
}
|
|
2986
|
-
function parseHex(s) {
|
|
2987
|
-
const hex = s.slice(1);
|
|
2988
|
-
let r = 0, g = 0, b = 0, a = 1;
|
|
2989
|
-
if (hex.length === 3 || hex.length === 4) {
|
|
2990
|
-
r = parseInt(hex[0] + hex[0], 16) / 255;
|
|
2991
|
-
g = parseInt(hex[1] + hex[1], 16) / 255;
|
|
2992
|
-
b = parseInt(hex[2] + hex[2], 16) / 255;
|
|
2993
|
-
if (hex.length === 4) a = parseInt(hex[3] + hex[3], 16) / 255;
|
|
2994
|
-
} else if (hex.length === 6 || hex.length === 8) {
|
|
2995
|
-
r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
2996
|
-
g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
2997
|
-
b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
2998
|
-
if (hex.length === 8) a = parseInt(hex.slice(6, 8), 16) / 255;
|
|
2999
|
-
}
|
|
3000
|
-
return { r, g, b, a };
|
|
3001
|
-
}
|
|
3002
|
-
function parseRgbFunc(s) {
|
|
3003
|
-
const inner = s.replace(/^rgba?\(/, "").replace(/\)$/, "");
|
|
3004
|
-
const parts = inner.split(/[,\s/]+/).filter(Boolean).map(parseFloat);
|
|
3005
|
-
return {
|
|
3006
|
-
r: (parts[0] ?? 0) / 255,
|
|
3007
|
-
g: (parts[1] ?? 0) / 255,
|
|
3008
|
-
b: (parts[2] ?? 0) / 255,
|
|
3009
|
-
a: parts.length > 3 ? parts[3] ?? 1 : 1
|
|
3010
|
-
};
|
|
3011
|
-
}
|
|
3012
|
-
function rgbaString(rgb, alpha) {
|
|
3013
|
-
const r = Math.round(clamp01(rgb.r) * 255);
|
|
3014
|
-
const g = Math.round(clamp01(rgb.g) * 255);
|
|
3015
|
-
const b = Math.round(clamp01(rgb.b) * 255);
|
|
3016
|
-
return `rgba(${r},${g},${b},${alpha})`;
|
|
3017
|
-
}
|
|
3018
|
-
function srgbToLinear(c) {
|
|
3019
|
-
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
3020
|
-
}
|
|
3021
|
-
function linearToSrgb(c) {
|
|
3022
|
-
return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
3023
|
-
}
|
|
3024
|
-
function rgbToOklab(rgba) {
|
|
3025
|
-
const r = srgbToLinear(rgba.r);
|
|
3026
|
-
const g = srgbToLinear(rgba.g);
|
|
3027
|
-
const b = srgbToLinear(rgba.b);
|
|
3028
|
-
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
3029
|
-
const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
3030
|
-
const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
3031
|
-
const l_ = Math.cbrt(l);
|
|
3032
|
-
const m_ = Math.cbrt(m);
|
|
3033
|
-
const s_ = Math.cbrt(s);
|
|
3034
|
-
return {
|
|
3035
|
-
L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
|
|
3036
|
-
a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
|
|
3037
|
-
b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_
|
|
3038
|
-
};
|
|
3039
|
-
}
|
|
3040
|
-
function oklabToRgb(lab) {
|
|
3041
|
-
const l_ = lab.L + 0.3963377774 * lab.a + 0.2158037573 * lab.b;
|
|
3042
|
-
const m_ = lab.L - 0.1055613458 * lab.a - 0.0638541728 * lab.b;
|
|
3043
|
-
const s_ = lab.L - 0.0894841775 * lab.a - 1.291485548 * lab.b;
|
|
3044
|
-
const l = l_ * l_ * l_;
|
|
3045
|
-
const m = m_ * m_ * m_;
|
|
3046
|
-
const s = s_ * s_ * s_;
|
|
3047
|
-
return {
|
|
3048
|
-
r: linearToSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
|
|
3049
|
-
g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
|
|
3050
|
-
b: linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)
|
|
3051
|
-
};
|
|
3052
|
-
}
|
|
3053
|
-
function oklabToOklch(lab) {
|
|
3054
|
-
return { L: lab.L, C: Math.hypot(lab.a, lab.b), h: Math.atan2(lab.b, lab.a) };
|
|
3055
|
-
}
|
|
3056
|
-
function oklchToOklab(lch) {
|
|
3057
|
-
return { L: lch.L, a: lch.C * Math.cos(lch.h), b: lch.C * Math.sin(lch.h) };
|
|
3058
|
-
}
|
|
3059
|
-
function lerp(a, b, t) {
|
|
3060
|
-
return a + (b - a) * t;
|
|
3061
|
-
}
|
|
3062
|
-
function clamp01(v) {
|
|
3063
|
-
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
3064
|
-
}
|
|
3065
|
-
function shortestHueDelta(from, to) {
|
|
3066
|
-
const TWO_PI = Math.PI * 2;
|
|
3067
|
-
let d = (to - from) % TWO_PI;
|
|
3068
|
-
if (d > Math.PI) d -= TWO_PI;
|
|
3069
|
-
if (d < -Math.PI) d += TWO_PI;
|
|
3070
|
-
return d;
|
|
2771
|
+
var hasGradientKeys = (keyframes) => (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => b && b.fillGradient !== void 0));
|
|
2772
|
+
|
|
2773
|
+
// src/core/revertibleSet.ts
|
|
2774
|
+
function revertibleSet(timeline, at, read2, write2, value, property) {
|
|
2775
|
+
timeline.setValue(property?.[0] ?? read2, property?.[1] ?? "set", at, read2, write2, value);
|
|
3071
2776
|
}
|
|
3072
2777
|
|
|
3073
2778
|
// src/expr/colorTween.ts
|
|
@@ -3095,555 +2800,1058 @@ function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, a
|
|
|
3095
2800
|
});
|
|
3096
2801
|
}
|
|
3097
2802
|
|
|
3098
|
-
// src/
|
|
3099
|
-
var
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
2803
|
+
// src/text/styleKeys.ts
|
|
2804
|
+
var STYLE_OPAQUE_KEYS = [
|
|
2805
|
+
"fontFamily",
|
|
2806
|
+
"fill",
|
|
2807
|
+
"align",
|
|
2808
|
+
"fontStyle",
|
|
2809
|
+
"fontWeight",
|
|
2810
|
+
"fontVariant",
|
|
2811
|
+
"textBaseline",
|
|
2812
|
+
"whiteSpace",
|
|
2813
|
+
"lineJoin",
|
|
2814
|
+
"join",
|
|
2815
|
+
"cap",
|
|
2816
|
+
"color",
|
|
2817
|
+
"stroke"
|
|
2818
|
+
];
|
|
2819
|
+
var TEXT_STYLE_DEFAULTS = {
|
|
2820
|
+
fontFamily: "Arial",
|
|
2821
|
+
fontSize: 36,
|
|
2822
|
+
fill: "#ffffff",
|
|
2823
|
+
align: "center"
|
|
2824
|
+
};
|
|
2825
|
+
|
|
2826
|
+
// src/sequences/Text.ts
|
|
2827
|
+
function formatValue(v, f) {
|
|
2828
|
+
const fixed = v.toFixed(Math.max(0, Math.floor(f?.decimals ?? 0)));
|
|
2829
|
+
const pad = Math.max(0, Math.floor(f?.pad ?? 0));
|
|
2830
|
+
if (!f?.grouping && !pad) return fixed;
|
|
2831
|
+
const [signed, frac] = fixed.split(".");
|
|
2832
|
+
const sign = signed.startsWith("-") ? "-" : "";
|
|
2833
|
+
let int = sign ? signed.slice(1) : signed;
|
|
2834
|
+
if (pad) int = int.padStart(pad, "0");
|
|
2835
|
+
if (f?.grouping) int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
2836
|
+
return sign + int + (frac !== void 0 ? "." + frac : "");
|
|
2837
|
+
}
|
|
2838
|
+
var TextSequence = class extends Sequence {
|
|
2839
|
+
/** `value`: the number for the `{value}` placeholder; `visibleChars`: how many characters show (typewriter). Both tweened by keyframes. */
|
|
2840
|
+
_counters = { value: 0, visibleChars: 0 };
|
|
2841
|
+
/** The string now on the layer (before `{value}` / `visibleChars`): `text`, or the last `set: { text }`. */
|
|
2842
|
+
_template = "";
|
|
2843
|
+
/** `visibleChars` is in play (initial or animated): otherwise the whole text shows. */
|
|
2844
|
+
_typed = false;
|
|
2845
|
+
_lastText = "";
|
|
2846
|
+
/** A gradient fill for the text (`fillGradient`, static or animated): rebuilt as a FillGradient on each change (the text is re-rasterised). */
|
|
2847
|
+
_grad = null;
|
|
2848
|
+
_fillGradient = null;
|
|
2849
|
+
_applyGradient() {
|
|
2850
|
+
const g = this._grad.grad;
|
|
2851
|
+
const next = new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((s) => [s.offset, s.color]) }));
|
|
2852
|
+
this.target.style.fill = next;
|
|
2853
|
+
this._fillGradient?.destroy();
|
|
2854
|
+
this._fillGradient = next;
|
|
3111
2855
|
}
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
2856
|
+
destroy() {
|
|
2857
|
+
super.destroy();
|
|
2858
|
+
this._fillGradient?.destroy();
|
|
2859
|
+
this._fillGradient = null;
|
|
2860
|
+
}
|
|
2861
|
+
/** @internal A `spans` group that types over several pieces: this piece shows its first `n` characters (the piece was built typed). */
|
|
2862
|
+
setVisibleChars(n) {
|
|
2863
|
+
this._counters.visibleChars = n;
|
|
2864
|
+
this._refreshText();
|
|
2865
|
+
}
|
|
2866
|
+
/** A typewriter that has typed nothing yet: the layer is empty on purpose (inspect must not call that "no size"). */
|
|
2867
|
+
get showsNothingYet() {
|
|
2868
|
+
return this._typed && this._lastText === "" && this._template !== "";
|
|
2869
|
+
}
|
|
2870
|
+
/** Rewrite the displayed string from the template, the current `value` and `visibleChars`. */
|
|
2871
|
+
_refreshText() {
|
|
2872
|
+
let next = this._template.includes("{value}") ? this._template.split("{value}").join(formatValue(this._counters.value, this.spec.format)) : this._template;
|
|
2873
|
+
if (this._typed) next = Array.from(next).slice(0, Math.max(0, Math.floor(this._counters.visibleChars))).join("");
|
|
2874
|
+
if (next === this._lastText) return;
|
|
2875
|
+
this._lastText = next;
|
|
2876
|
+
this.target.text = next;
|
|
2877
|
+
}
|
|
2878
|
+
async build() {
|
|
2879
|
+
const baseStyle = { ...TEXT_STYLE_DEFAULTS };
|
|
2880
|
+
const initialProps = this.spec.initial ?? {};
|
|
2881
|
+
this._template = String(initialProps.text ?? this.spec.text ?? "");
|
|
2882
|
+
const text = new Text({
|
|
2883
|
+
text: this._template,
|
|
2884
|
+
style: baseStyle,
|
|
2885
|
+
label: this.spec.name
|
|
2886
|
+
});
|
|
2887
|
+
text.cullable = true;
|
|
2888
|
+
this.target = text;
|
|
2889
|
+
this.intrinsicWidth = text.width;
|
|
2890
|
+
this.intrinsicHeight = text.height;
|
|
2891
|
+
if (this.duration === void 0) {
|
|
2892
|
+
this.duration = this.parent?.duration ?? this.root.duration;
|
|
2893
|
+
}
|
|
2894
|
+
if (this.spec.style) {
|
|
2895
|
+
const scope2 = this.scope();
|
|
2896
|
+
const resolved = normalizeProps(
|
|
2897
|
+
this.spec.style,
|
|
2898
|
+
scope2,
|
|
2899
|
+
{ skipKeys: STYLE_OPAQUE_KEYS }
|
|
2900
|
+
);
|
|
2901
|
+
const unset = Object.keys(resolved).filter((k) => resolved[k] === void 0);
|
|
2902
|
+
if (unset.length) console.warn(`pixi-effects: ${describeLayer(this.spec)}: ${unset.map((k) => `style.${k}`).join(", ")} ${unset.length > 1 ? "are" : "is"} undefined (a variable that was never set?), so ${unset.length > 1 ? "they are" : "it is"} ignored and the default is used: omit the key instead`);
|
|
2903
|
+
const outline = resolved.stroke;
|
|
2904
|
+
if (outline && typeof outline === "object" && !("join" in outline) && !("lineJoin" in outline)) resolved.stroke = { ...outline, join: "round" };
|
|
2905
|
+
for (const k of Object.keys(resolved)) {
|
|
2906
|
+
if (unset.includes(k)) continue;
|
|
2907
|
+
text.style[k] = resolved[k];
|
|
3154
2908
|
}
|
|
2909
|
+
this.intrinsicWidth = text.width;
|
|
2910
|
+
this.intrinsicHeight = text.height;
|
|
3155
2911
|
}
|
|
3156
|
-
this.
|
|
3157
|
-
const
|
|
3158
|
-
if (
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
2912
|
+
validateGradientKeyframes(this.spec, describeLayer(this.spec), void 0, "text");
|
|
2913
|
+
const gradientSpec = this.spec.fillGradient ?? initialProps.fillGradient;
|
|
2914
|
+
if (gradientSpec) {
|
|
2915
|
+
this._grad = { grad: gradStateFrom(gradientSpec) };
|
|
2916
|
+
this._applyGradient();
|
|
2917
|
+
}
|
|
2918
|
+
const scope = this.scope();
|
|
2919
|
+
if (initialProps.value !== void 0) this._counters.value = resolveNumber(initialProps.value, scope);
|
|
2920
|
+
this._typed = initialProps.visibleChars !== void 0 || animates(this.spec.keyframes, "visibleChars");
|
|
2921
|
+
if (initialProps.visibleChars !== void 0) this._counters.visibleChars = resolveNumber(initialProps.visibleChars, scope);
|
|
2922
|
+
const typed = this._typed;
|
|
2923
|
+
this._typed = false;
|
|
2924
|
+
this._refreshText();
|
|
2925
|
+
if (this._lastText !== "") {
|
|
2926
|
+
this.intrinsicWidth = text.width;
|
|
2927
|
+
this.intrinsicHeight = text.height;
|
|
2928
|
+
}
|
|
2929
|
+
this._typed = typed;
|
|
2930
|
+
this._refreshText();
|
|
2931
|
+
this.buildFilters();
|
|
2932
|
+
}
|
|
2933
|
+
bindTimeline(timeline, offset = 0) {
|
|
2934
|
+
if (!this.target) return;
|
|
2935
|
+
const colorSpace = this.spec.colorSpace ?? "rgb";
|
|
2936
|
+
const stripped = stripKeys(this.spec, ["fill", "value", "text", "visibleChars", "fillGradient"]);
|
|
2937
|
+
runSuperWithStrippedSpec(this, stripped, timeline, offset);
|
|
2938
|
+
const keyframes = this.spec.keyframes ?? [];
|
|
2939
|
+
const origin = offset + this.at;
|
|
2940
|
+
bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
|
|
2941
|
+
if (this._grad) bindGradientKeyframes(timeline, this._grad, keyframes, this.duration, origin, colorSpace, () => this._applyGradient());
|
|
2942
|
+
const refresh = () => this._refreshText();
|
|
2943
|
+
bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
|
|
2944
|
+
bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
|
|
2945
|
+
bindTextKeyframes(timeline, keyframes, this.duration, origin, () => this._template, (t) => {
|
|
2946
|
+
this._template = t;
|
|
2947
|
+
refresh();
|
|
2948
|
+
}, describeLayer(this.spec));
|
|
3162
2949
|
}
|
|
3163
2950
|
};
|
|
3164
|
-
function
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
changed = true;
|
|
3172
|
-
continue;
|
|
3173
|
-
}
|
|
3174
|
-
out[k] = props[k];
|
|
2951
|
+
function runSuperWithStrippedSpec(inst, strippedSpec, timeline, offset) {
|
|
2952
|
+
const originalSpec = inst.spec;
|
|
2953
|
+
inst.spec = strippedSpec;
|
|
2954
|
+
try {
|
|
2955
|
+
Sequence.prototype.bindTimeline.call(inst, timeline, offset);
|
|
2956
|
+
} finally {
|
|
2957
|
+
inst.spec = originalSpec;
|
|
3175
2958
|
}
|
|
3176
|
-
return changed ? out : props;
|
|
3177
|
-
}
|
|
3178
|
-
function stripKeysInKeyframe(kf, keys) {
|
|
3179
|
-
return {
|
|
3180
|
-
...kf,
|
|
3181
|
-
set: stripKeys(kf.set, keys),
|
|
3182
|
-
to: stripKeys(kf.to, keys),
|
|
3183
|
-
from: stripKeys(kf.from, keys)
|
|
3184
|
-
};
|
|
3185
2959
|
}
|
|
3186
|
-
|
|
3187
|
-
// src/sequences/gradient.ts
|
|
3188
|
-
function cssColor(c) {
|
|
3189
|
-
return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
|
|
3190
|
-
}
|
|
3191
|
-
function normalizedStops(g) {
|
|
3192
|
-
const stops = g.stops ?? [];
|
|
3193
|
-
if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
|
|
3194
|
-
return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
|
|
3195
|
-
}
|
|
3196
|
-
function gradientOptions(g) {
|
|
3197
|
-
const colorStops = normalizedStops(g);
|
|
3198
|
-
const a = (g.angle ?? 90) * Math.PI / 180;
|
|
3199
|
-
const dx = Math.cos(a), dy = Math.sin(a);
|
|
3200
|
-
const h = (Math.abs(dx) + Math.abs(dy)) / 2;
|
|
2960
|
+
function stripKeys(spec, keys) {
|
|
3201
2961
|
return {
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
2962
|
+
...spec,
|
|
2963
|
+
initial: stripProps(spec.initial, keys),
|
|
2964
|
+
keyframes: spec.keyframes ? spec.keyframes.map((kf) => ({
|
|
2965
|
+
...kf,
|
|
2966
|
+
set: stripProps(kf.set, keys),
|
|
2967
|
+
to: stripProps(kf.to, keys),
|
|
2968
|
+
from: stripProps(kf.from, keys)
|
|
2969
|
+
})) : void 0
|
|
3207
2970
|
};
|
|
3208
2971
|
}
|
|
3209
|
-
function
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
const
|
|
3213
|
-
for (const
|
|
3214
|
-
|
|
3215
|
-
ctx.fillRect(0, 0, size, size);
|
|
2972
|
+
function stripProps(props, keys) {
|
|
2973
|
+
if (!props) return props;
|
|
2974
|
+
if (!keys.some((k) => k in props)) return props;
|
|
2975
|
+
const out = {};
|
|
2976
|
+
for (const k of Object.keys(props)) if (!keys.includes(k)) out[k] = props[k];
|
|
2977
|
+
return out;
|
|
3216
2978
|
}
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
return {
|
|
3222
|
-
type: g.type ?? "linear",
|
|
3223
|
-
angle: g.angle ?? 90,
|
|
3224
|
-
center: [...g.center ?? [0.5, 0.5]],
|
|
3225
|
-
innerRadius: g.innerRadius ?? 0,
|
|
3226
|
-
radius: g.radius ?? 0.5,
|
|
3227
|
-
stops: (g.stops ?? []).map((s) => Array.isArray(s) ? { offset: s[0], color: cssColor(s[1]) } : { offset: s.offset, color: cssColor(s.color) })
|
|
3228
|
-
};
|
|
2979
|
+
function resolveNumber(v, scope) {
|
|
2980
|
+
if (typeof v === "number") return v;
|
|
2981
|
+
const out = normalizeProps({ v }, scope).v;
|
|
2982
|
+
return typeof out === "number" ? out : Number(out) || 0;
|
|
3229
2983
|
}
|
|
3230
|
-
function
|
|
3231
|
-
const
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
const
|
|
3240
|
-
if (
|
|
2984
|
+
function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDuration, origin, scope) {
|
|
2985
|
+
for (const kf of keyframes) {
|
|
2986
|
+
const setV = kf.set?.[key];
|
|
2987
|
+
const fromV = kf.from?.[key];
|
|
2988
|
+
const toV = kf.to?.[key];
|
|
2989
|
+
if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
|
|
2990
|
+
const at = origin + resolveAt(kf.at, parentDuration);
|
|
2991
|
+
const duration = kfDuration(kf);
|
|
2992
|
+
const ease = kf.ease ?? "none";
|
|
2993
|
+
const loop = loopVars(kf);
|
|
2994
|
+
if (setV !== void 0) {
|
|
2995
|
+
const v = resolveNumber(setV, scope);
|
|
2996
|
+
revertibleSet(timeline, at, () => state[key], (n) => {
|
|
2997
|
+
state[key] = n;
|
|
2998
|
+
refresh();
|
|
2999
|
+
}, v, [state, key]);
|
|
3000
|
+
}
|
|
3001
|
+
if (toV !== void 0 && fromV !== void 0) {
|
|
3002
|
+
timeline.fromTo(
|
|
3003
|
+
state,
|
|
3004
|
+
{ [key]: resolveNumber(fromV, scope) },
|
|
3005
|
+
{ [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
|
|
3006
|
+
at
|
|
3007
|
+
);
|
|
3008
|
+
} else if (toV !== void 0) {
|
|
3009
|
+
timeline.to(state, { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
|
|
3010
|
+
} else if (fromV !== void 0) {
|
|
3011
|
+
timeline.from(state, { [key]: resolveNumber(fromV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
|
|
3012
|
+
}
|
|
3241
3013
|
}
|
|
3242
|
-
return out;
|
|
3243
3014
|
}
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
warn(`pixi-effects: ${who}: ${m}`);
|
|
3015
|
+
function bindTextKeyframes(timeline, keyframes, parentDuration, origin, read2, write2, who) {
|
|
3016
|
+
for (const kf of keyframes) {
|
|
3017
|
+
for (const [what, props] of [["to", kf.to], ["from", kf.from]]) {
|
|
3018
|
+
if (props && props.text !== void 0) {
|
|
3019
|
+
console.warn(`pixi-effects: ${who}: a keyframe's ${what}: { text } cannot tween a string: use set: { text: '\u2026' } at the time it should change`);
|
|
3020
|
+
}
|
|
3251
3021
|
}
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
let animated = false;
|
|
3256
|
-
if (kind === "text") {
|
|
3257
|
-
const radial = (g) => !!g && typeof g === "object" && ["type", "center", "innerRadius", "radius"].some((k) => g[k] !== void 0 && !(k === "type" && g.type === "linear"));
|
|
3258
|
-
const asked = radial(startSpec) || (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => radial(b?.fillGradient)));
|
|
3259
|
-
if (asked) say("a text gradient is linear: type, center, innerRadius and radius are ignored (only angle and stops apply); use a shape for a radial gradient");
|
|
3022
|
+
const t = kf.set?.text;
|
|
3023
|
+
if (t === void 0) continue;
|
|
3024
|
+
revertibleSet(timeline, origin + resolveAt(kf.at, parentDuration), read2, write2, String(t));
|
|
3260
3025
|
}
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3026
|
+
}
|
|
3027
|
+
function animates(keyframes, key) {
|
|
3028
|
+
return (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((p) => p && p[key] !== void 0));
|
|
3029
|
+
}
|
|
3030
|
+
function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, colorSpace) {
|
|
3031
|
+
const state = { fill: text.style.fill };
|
|
3032
|
+
const writeFill = () => {
|
|
3033
|
+
text.style.fill = state.fill;
|
|
3034
|
+
};
|
|
3035
|
+
for (const kf of keyframes) {
|
|
3036
|
+
const at = offset + resolveAt(kf.at, parentDuration);
|
|
3037
|
+
const duration = kfDuration(kf);
|
|
3038
|
+
const ease = kf.ease ?? "none";
|
|
3039
|
+
const setFill = pickFill(kf.set);
|
|
3040
|
+
const fromFill = pickFill(kf.from);
|
|
3041
|
+
const toFill = pickFill(kf.to);
|
|
3042
|
+
if (setFill !== void 0) {
|
|
3043
|
+
revertibleSet(timeline, at, () => state.fill, (c) => {
|
|
3044
|
+
state.fill = c;
|
|
3045
|
+
writeFill();
|
|
3046
|
+
}, setFill, [state, "fill"]);
|
|
3047
|
+
}
|
|
3048
|
+
if (toFill !== void 0) {
|
|
3049
|
+
tweenColor(
|
|
3050
|
+
timeline,
|
|
3051
|
+
state,
|
|
3052
|
+
"fill",
|
|
3053
|
+
fromFill,
|
|
3054
|
+
toFill,
|
|
3055
|
+
duration,
|
|
3056
|
+
ease,
|
|
3057
|
+
at,
|
|
3058
|
+
colorSpace,
|
|
3059
|
+
writeFill,
|
|
3060
|
+
loopVars(kf)
|
|
3061
|
+
);
|
|
3288
3062
|
}
|
|
3289
3063
|
}
|
|
3290
|
-
if (animated && !start) say("a keyframe animates fillGradient but the layer has none to start from: give it a fillGradient first (top level or in initial)");
|
|
3291
3064
|
}
|
|
3292
|
-
function
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
set: (g) => {
|
|
3298
|
-
holder.grad = g;
|
|
3299
|
-
onChange();
|
|
3300
|
-
},
|
|
3301
|
-
from: fromPatch !== void 0 ? (prev) => mergeGrad(prev, fromPatch) : void 0,
|
|
3302
|
-
to: (start, prev) => toPatch !== void 0 ? mergeGrad(start, toPatch) : prev,
|
|
3303
|
-
make: (a, b) => {
|
|
3304
|
-
const colors = a.stops.map((s, i) => colorSpace === "rgb" ? interpolateColors(s.color, b.stops[i].color) : buildColorInterp(s.color, b.stops[i].color, colorSpace));
|
|
3305
|
-
return (t) => {
|
|
3306
|
-
const l = (x, y) => x + (y - x) * t;
|
|
3307
|
-
return {
|
|
3308
|
-
type: a.type,
|
|
3309
|
-
angle: l(a.angle, b.angle),
|
|
3310
|
-
center: [l(a.center[0], b.center[0]), l(a.center[1], b.center[1])],
|
|
3311
|
-
innerRadius: l(a.innerRadius, b.innerRadius),
|
|
3312
|
-
radius: l(a.radius, b.radius),
|
|
3313
|
-
stops: a.stops.map((s, i) => ({ offset: l(s.offset, b.stops[i].offset), color: colors[i](t) }))
|
|
3314
|
-
};
|
|
3315
|
-
};
|
|
3316
|
-
},
|
|
3317
|
-
duration,
|
|
3318
|
-
ease,
|
|
3319
|
-
at,
|
|
3320
|
-
repeat: loop.repeat,
|
|
3321
|
-
yoyo: loop.yoyo,
|
|
3322
|
-
repeatDelay: loop.repeatDelay
|
|
3323
|
-
});
|
|
3065
|
+
function pickFill(props) {
|
|
3066
|
+
if (!props) return void 0;
|
|
3067
|
+
const v = props.fill;
|
|
3068
|
+
if (typeof v === "string" || typeof v === "number") return v;
|
|
3069
|
+
return void 0;
|
|
3324
3070
|
}
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
const
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3071
|
+
|
|
3072
|
+
// src/core/remap.ts
|
|
3073
|
+
var BAGS = ["set", "to", "from"];
|
|
3074
|
+
function timeRemapOf(spec, prop = "time") {
|
|
3075
|
+
const hasInitial = !!spec.initial && "time" in spec.initial;
|
|
3076
|
+
const hasKf = (spec.keyframes ?? []).some((kf) => BAGS.some((b) => kf[b] && "time" in kf[b]));
|
|
3077
|
+
const speed = typeof spec.speed === "number" && Number.isFinite(spec.speed) && spec.speed !== 0 ? spec.speed : void 0;
|
|
3078
|
+
if (!hasInitial && !hasKf && speed === void 0) return null;
|
|
3079
|
+
const timeKfs = [];
|
|
3080
|
+
const restKfs = [];
|
|
3081
|
+
for (const kf of spec.keyframes ?? []) {
|
|
3082
|
+
const t = { ...kf }, r = { ...kf };
|
|
3083
|
+
let touched = false, left = false;
|
|
3084
|
+
for (const b of BAGS) {
|
|
3085
|
+
const bag = kf[b];
|
|
3086
|
+
if (!bag) {
|
|
3087
|
+
delete t[b];
|
|
3088
|
+
delete r[b];
|
|
3089
|
+
continue;
|
|
3090
|
+
}
|
|
3091
|
+
const { time, ...others } = bag;
|
|
3092
|
+
if ("time" in bag) {
|
|
3093
|
+
touched = true;
|
|
3094
|
+
t[b] = { [prop]: time };
|
|
3095
|
+
} else delete t[b];
|
|
3096
|
+
if (Object.keys(others).length) {
|
|
3097
|
+
r[b] = others;
|
|
3098
|
+
left = true;
|
|
3099
|
+
} else delete r[b];
|
|
3351
3100
|
}
|
|
3352
|
-
|
|
3353
|
-
|
|
3101
|
+
if (touched) timeKfs.push(t);
|
|
3102
|
+
if (!touched || left) restKfs.push(r);
|
|
3354
3103
|
}
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3104
|
+
let restInitial = spec.initial;
|
|
3105
|
+
let start;
|
|
3106
|
+
if (hasInitial) {
|
|
3107
|
+
const { time, ...others } = spec.initial;
|
|
3108
|
+
start = typeof time === "number" && Number.isFinite(time) ? time : void 0;
|
|
3109
|
+
restInitial = others;
|
|
3360
3110
|
}
|
|
3361
|
-
};
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
const
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
id: "grad",
|
|
3371
|
-
get: () => holder.grad,
|
|
3372
|
-
set: (g) => {
|
|
3373
|
-
holder.grad = g;
|
|
3374
|
-
onChange();
|
|
3375
|
-
},
|
|
3376
|
-
to: (start) => mergeGrad(start, setV),
|
|
3377
|
-
make: (_a, b) => () => b,
|
|
3378
|
-
duration: 0,
|
|
3379
|
-
ease: "none",
|
|
3380
|
-
at
|
|
3381
|
-
});
|
|
3111
|
+
return { timeKfs, restKfs: restKfs.length ? restKfs : void 0, restInitial, start, speed };
|
|
3112
|
+
}
|
|
3113
|
+
function resolveFromEnd(remap, length, prop) {
|
|
3114
|
+
const fix = (v) => typeof v === "number" && v < 0 ? Math.max(0, length + v) : v;
|
|
3115
|
+
const timeKfs = remap.timeKfs.map((kf) => {
|
|
3116
|
+
const out = { ...kf };
|
|
3117
|
+
for (const b of BAGS) {
|
|
3118
|
+
const bag = kf[b];
|
|
3119
|
+
if (bag && prop in bag) out[b] = { ...bag, [prop]: fix(bag[prop]) };
|
|
3382
3120
|
}
|
|
3383
|
-
|
|
3384
|
-
|
|
3121
|
+
return out;
|
|
3122
|
+
});
|
|
3123
|
+
return { ...remap, timeKfs, start: remap.start === void 0 ? void 0 : fix(remap.start) };
|
|
3124
|
+
}
|
|
3125
|
+
function remapOf(spec, prop, length) {
|
|
3126
|
+
const r = timeRemapOf(spec, prop);
|
|
3127
|
+
return r && length !== null ? resolveFromEnd(r, length, prop) : r;
|
|
3128
|
+
}
|
|
3129
|
+
function contentLength(remap, duration) {
|
|
3130
|
+
let m = duration;
|
|
3131
|
+
if (remap.speed !== void 0) m = Math.max(m, Math.abs(remap.speed) * duration);
|
|
3132
|
+
if (remap.start !== void 0) m = Math.max(m, remap.start + (remap.speed ?? 1) * duration);
|
|
3133
|
+
for (const kf of remap.timeKfs) {
|
|
3134
|
+
for (const b of BAGS) {
|
|
3135
|
+
const bag = kf[b];
|
|
3136
|
+
const v = bag && Object.values(bag)[0];
|
|
3137
|
+
if (typeof v === "number") m = Math.max(m, v);
|
|
3385
3138
|
}
|
|
3386
3139
|
}
|
|
3140
|
+
return m;
|
|
3387
3141
|
}
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
"
|
|
3397
|
-
"fontVariant",
|
|
3398
|
-
"textBaseline",
|
|
3399
|
-
"whiteSpace",
|
|
3400
|
-
"lineJoin",
|
|
3401
|
-
"join",
|
|
3402
|
-
"cap",
|
|
3403
|
-
"color",
|
|
3404
|
-
"stroke"
|
|
3405
|
-
];
|
|
3406
|
-
function formatValue(v, f) {
|
|
3407
|
-
const fixed = v.toFixed(Math.max(0, Math.floor(f?.decimals ?? 0)));
|
|
3408
|
-
const pad = Math.max(0, Math.floor(f?.pad ?? 0));
|
|
3409
|
-
if (!f?.grouping && !pad) return fixed;
|
|
3410
|
-
const [signed, frac] = fixed.split(".");
|
|
3411
|
-
const sign = signed.startsWith("-") ? "-" : "";
|
|
3412
|
-
let int = sign ? signed.slice(1) : signed;
|
|
3413
|
-
if (pad) int = int.padStart(pad, "0");
|
|
3414
|
-
if (f?.grouping) int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
3415
|
-
return sign + int + (frac !== void 0 ? "." + frac : "");
|
|
3142
|
+
function bindClock(timeline, clock, prop, remap, duration, at, scope) {
|
|
3143
|
+
if (remap.timeKfs.length) {
|
|
3144
|
+
if (remap.start !== void 0) timeline.set(clock, { [prop]: remap.start }, at);
|
|
3145
|
+
applyKeyframes(timeline, clock, remap.timeKfs, duration, scope, [], at);
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
const s = remap.speed ?? 1;
|
|
3149
|
+
const from = remap.start ?? (s < 0 ? -s * duration : 0);
|
|
3150
|
+
timeline.fromTo(clock, { [prop]: from }, { [prop]: from + s * duration, duration, ease: "none" }, at);
|
|
3416
3151
|
}
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
const
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3152
|
+
function clockTable(remap, prop, duration, at, scope, to) {
|
|
3153
|
+
const probe = createTimeline({ paused: true });
|
|
3154
|
+
const clock = { [prop]: remap.start ?? 0 };
|
|
3155
|
+
bindClock(probe, clock, prop, remap, duration, at, scope);
|
|
3156
|
+
probe.time(probe.duration());
|
|
3157
|
+
probe.time(0);
|
|
3158
|
+
const h = 1 / 1e3;
|
|
3159
|
+
const n = Math.max(2, Math.ceil(to / h) + 2);
|
|
3160
|
+
const tab = new Float64Array(n);
|
|
3161
|
+
let min = Infinity, max = -Infinity;
|
|
3162
|
+
for (let k = 0; k < n; k++) {
|
|
3163
|
+
probe.time(k * h);
|
|
3164
|
+
const v = clock[prop];
|
|
3165
|
+
tab[k] = v;
|
|
3166
|
+
const t = k * h;
|
|
3167
|
+
if (t >= at && t <= at + duration) {
|
|
3168
|
+
if (v < min) min = v;
|
|
3169
|
+
if (v > max) max = v;
|
|
3170
|
+
}
|
|
3434
3171
|
}
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3172
|
+
probe.kill();
|
|
3173
|
+
return {
|
|
3174
|
+
at(t) {
|
|
3175
|
+
const x = Math.min(Math.max(t / h, 0), n - 1.000001);
|
|
3176
|
+
const k = Math.floor(x), f = x - k;
|
|
3177
|
+
return tab[k] * (1 - f) + tab[k + 1] * f;
|
|
3178
|
+
},
|
|
3179
|
+
min: Number.isFinite(min) ? min : 0,
|
|
3180
|
+
max: Number.isFinite(max) ? max : 0
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
// src/space/math.ts
|
|
3185
|
+
var DEG3 = Math.PI / 180;
|
|
3186
|
+
var DEFAULT_FOV = 40;
|
|
3187
|
+
var MIN_FOV = 1;
|
|
3188
|
+
var MAX_FOV = 179;
|
|
3189
|
+
var NEAR = 1;
|
|
3190
|
+
function clampFov(fov) {
|
|
3191
|
+
if (!Number.isFinite(fov)) return DEFAULT_FOV;
|
|
3192
|
+
return Math.min(MAX_FOV, Math.max(MIN_FOV, fov));
|
|
3193
|
+
}
|
|
3194
|
+
function homeDistance(compHeight, fovDeg) {
|
|
3195
|
+
return compHeight / 2 / Math.tan(clampFov(fovDeg) * DEG3 / 2);
|
|
3196
|
+
}
|
|
3197
|
+
function homeCamera(compW, compH, fov = DEFAULT_FOV) {
|
|
3198
|
+
return {
|
|
3199
|
+
x: compW / 2,
|
|
3200
|
+
y: compH / 2,
|
|
3201
|
+
z: homeDistance(compH, fov),
|
|
3202
|
+
lookAtX: compW / 2,
|
|
3203
|
+
lookAtY: compH / 2,
|
|
3204
|
+
lookAtZ: 0,
|
|
3205
|
+
fov
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
function cameraBasis(cam, compW, compH) {
|
|
3209
|
+
let fx = cam.lookAtX - cam.x;
|
|
3210
|
+
let fy = cam.lookAtY - cam.y;
|
|
3211
|
+
let fz = cam.lookAtZ - cam.z;
|
|
3212
|
+
let len = Math.hypot(fx, fy, fz);
|
|
3213
|
+
if (len < 1e-9) {
|
|
3214
|
+
fx = 0;
|
|
3215
|
+
fy = 0;
|
|
3216
|
+
fz = -1;
|
|
3217
|
+
len = 1;
|
|
3439
3218
|
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3219
|
+
fx /= len;
|
|
3220
|
+
fy /= len;
|
|
3221
|
+
fz /= len;
|
|
3222
|
+
let rx = -fz;
|
|
3223
|
+
const ry = 0;
|
|
3224
|
+
let rz = fx;
|
|
3225
|
+
const rlen = Math.hypot(rx, rz);
|
|
3226
|
+
if (rlen < 1e-9) {
|
|
3227
|
+
rx = 1;
|
|
3228
|
+
rz = 0;
|
|
3229
|
+
} else {
|
|
3230
|
+
rx /= rlen;
|
|
3231
|
+
rz /= rlen;
|
|
3443
3232
|
}
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3233
|
+
const dx = ry * fz - rz * fy;
|
|
3234
|
+
const dy = rz * fx - rx * fz;
|
|
3235
|
+
const dz = rx * fy - ry * fx;
|
|
3236
|
+
return {
|
|
3237
|
+
cx: cam.x,
|
|
3238
|
+
cy: cam.y,
|
|
3239
|
+
cz: cam.z,
|
|
3240
|
+
rx,
|
|
3241
|
+
ry,
|
|
3242
|
+
rz,
|
|
3243
|
+
dx,
|
|
3244
|
+
dy,
|
|
3245
|
+
dz,
|
|
3246
|
+
fx,
|
|
3247
|
+
fy,
|
|
3248
|
+
fz,
|
|
3249
|
+
focal: compH / 2 / Math.tan(clampFov(cam.fov) * DEG3 / 2),
|
|
3250
|
+
halfW: compW / 2,
|
|
3251
|
+
halfH: compH / 2
|
|
3252
|
+
};
|
|
3253
|
+
}
|
|
3254
|
+
function layerToWorld(t, px, py) {
|
|
3255
|
+
let x = (px - t.pivotX) * t.scaleX;
|
|
3256
|
+
let y = (py - t.pivotY) * t.scaleY;
|
|
3257
|
+
let z = 0;
|
|
3258
|
+
let c = Math.cos(t.rotationX);
|
|
3259
|
+
let s = Math.sin(t.rotationX);
|
|
3260
|
+
[y, z] = [y * c - z * s, y * s + z * c];
|
|
3261
|
+
c = Math.cos(t.rotationY);
|
|
3262
|
+
s = Math.sin(t.rotationY);
|
|
3263
|
+
[x, z] = [x * c + z * s, -x * s + z * c];
|
|
3264
|
+
c = Math.cos(t.rotationZ);
|
|
3265
|
+
s = Math.sin(t.rotationZ);
|
|
3266
|
+
[x, y] = [x * c - y * s, x * s + y * c];
|
|
3267
|
+
return { x: x + t.x, y: y + t.y, z: z + t.z };
|
|
3268
|
+
}
|
|
3269
|
+
function projectPoint(b, p) {
|
|
3270
|
+
const vx = p.x - b.cx;
|
|
3271
|
+
const vy = p.y - b.cy;
|
|
3272
|
+
const vz = p.z - b.cz;
|
|
3273
|
+
const xc = vx * b.rx + vy * b.ry + vz * b.rz;
|
|
3274
|
+
const yc = vx * b.dx + vy * b.dy + vz * b.dz;
|
|
3275
|
+
const zc = vx * b.fx + vy * b.fy + vz * b.fz;
|
|
3276
|
+
return { x: b.halfW + b.focal * xc / zc, y: b.halfH + b.focal * yc / zc, depth: zc };
|
|
3277
|
+
}
|
|
3278
|
+
function projectLayer(t, frame, b) {
|
|
3279
|
+
const pts = [
|
|
3280
|
+
[frame.x, frame.y],
|
|
3281
|
+
[frame.x + frame.width, frame.y],
|
|
3282
|
+
[frame.x + frame.width, frame.y + frame.height],
|
|
3283
|
+
[frame.x, frame.y + frame.height]
|
|
3284
|
+
];
|
|
3285
|
+
let visible = true;
|
|
3286
|
+
const corners = [];
|
|
3287
|
+
for (const [px, py] of pts) {
|
|
3288
|
+
const p = projectPoint(b, layerToWorld(t, px, py));
|
|
3289
|
+
if (!(p.depth > NEAR)) visible = false;
|
|
3290
|
+
corners.push(p.x, p.y);
|
|
3451
3291
|
}
|
|
3292
|
+
const origin = projectPoint(b, { x: t.x, y: t.y, z: t.z });
|
|
3293
|
+
return { corners, depth: origin.depth, visible };
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
// src/space/focus.ts
|
|
3297
|
+
var DEFAULT_APERTURE = 30;
|
|
3298
|
+
var MIN_BLUR = 0.05;
|
|
3299
|
+
var MAX_BLUR = 32;
|
|
3300
|
+
var MANY_BLURRED = 20;
|
|
3301
|
+
function blurRadius(aperture, focal, depth, focusDepth) {
|
|
3302
|
+
if (!(aperture > 0) || !(focal > 0) || !(depth > 0) || !(focusDepth > 0)) return 0;
|
|
3303
|
+
const r = aperture * focal * Math.abs(1 / depth - 1 / focusDepth) / 2;
|
|
3304
|
+
return Math.min(r, MAX_BLUR);
|
|
3305
|
+
}
|
|
3306
|
+
var SCOPE_NAMES = /* @__PURE__ */ new Set(["w", "h", "W", "H", "GW", "GH", "contain", "cover", "t", "d", "T"]);
|
|
3307
|
+
function looksLikeLayerName(s) {
|
|
3308
|
+
return /^[A-Za-z_][\w-]*$/.test(s) && !SCOPE_NAMES.has(s);
|
|
3309
|
+
}
|
|
3310
|
+
var isName = (v, known) => typeof v === "string" && (known.has(v) || looksLikeLayerName(v));
|
|
3311
|
+
function focusNames(props, known) {
|
|
3312
|
+
const out = [];
|
|
3313
|
+
const read2 = (bag) => {
|
|
3314
|
+
const v = bag?.focus;
|
|
3315
|
+
if (isName(v, known)) out.push(v);
|
|
3316
|
+
};
|
|
3317
|
+
read2(props.initial);
|
|
3318
|
+
for (const kf of props.keyframes ?? []) for (const bag of [kf.set, kf.to, kf.from]) read2(bag);
|
|
3319
|
+
return out;
|
|
3320
|
+
}
|
|
3321
|
+
function withFocusResolved(props, zOf, known = /* @__PURE__ */ new Set()) {
|
|
3322
|
+
if (focusNames(props, known).length === 0) return props;
|
|
3323
|
+
const fix = (bag) => {
|
|
3324
|
+
const v = bag?.focus;
|
|
3325
|
+
if (!bag || !isName(v, known)) return bag;
|
|
3326
|
+
return { ...bag, focus: zOf(v) ?? 0 };
|
|
3327
|
+
};
|
|
3328
|
+
return {
|
|
3329
|
+
...props,
|
|
3330
|
+
initial: fix(props.initial),
|
|
3331
|
+
keyframes: props.keyframes?.map((kf) => ({ ...kf, set: fix(kf.set), to: fix(kf.to), from: fix(kf.from) }))
|
|
3332
|
+
};
|
|
3333
|
+
}
|
|
3334
|
+
function layerInitialZ(spec, scope) {
|
|
3335
|
+
const z = spec.initial?.z;
|
|
3336
|
+
if (typeof z === "number") return z;
|
|
3337
|
+
if (typeof z === "string") return evaluateExpr(z, scope);
|
|
3338
|
+
return 0;
|
|
3339
|
+
}
|
|
3340
|
+
var movesZ = (spec) => (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((bag) => bag && "z" in bag));
|
|
3341
|
+
function focusProblems(camera, siblings) {
|
|
3342
|
+
const out = [];
|
|
3343
|
+
const said = /* @__PURE__ */ new Set();
|
|
3344
|
+
const threeD = siblings.filter((s) => s.threeD && s.name).map((s) => s.name);
|
|
3345
|
+
const known = new Set(siblings.map((s) => s.name).filter((n) => !!n));
|
|
3346
|
+
for (const name of focusNames(camera, known)) {
|
|
3347
|
+
if (said.has(name)) continue;
|
|
3348
|
+
said.add(name);
|
|
3349
|
+
const same = siblings.filter((s) => s.name === name);
|
|
3350
|
+
const hit = same.find((s) => s.threeD) ?? same[0];
|
|
3351
|
+
if (!hit) out.push({ kind: "missing", name, hint: suggestName(name, threeD) });
|
|
3352
|
+
else if (!hit.threeD) out.push({ kind: "not-threeD", name });
|
|
3353
|
+
else {
|
|
3354
|
+
if (same.length > 1) out.push({ kind: "duplicate", name, count: same.length });
|
|
3355
|
+
if (movesZ(hit)) out.push({ kind: "moving", name });
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
return out;
|
|
3359
|
+
}
|
|
3360
|
+
var CameraSequence = class extends Sequence {
|
|
3361
|
+
/** True when `z` is never specified anywhere: z then follows `fov`. */
|
|
3362
|
+
autoZ = true;
|
|
3363
|
+
compH = 0;
|
|
3364
|
+
warnedFov = false;
|
|
3365
|
+
warnedLookAt = false;
|
|
3366
|
+
warnedAperture = false;
|
|
3367
|
+
/** True when the camera writes `focus` or `aperture` anywhere: depth of field is on. */
|
|
3368
|
+
dof = false;
|
|
3369
|
+
/** True when the camera writes any fog key anywhere: depth fog is on (nothing is made for a camera without fog). */
|
|
3370
|
+
fog = false;
|
|
3371
|
+
fogC = null;
|
|
3372
|
+
zOfName = null;
|
|
3373
|
+
layerNames = /* @__PURE__ */ new Set();
|
|
3452
3374
|
async build() {
|
|
3453
|
-
const
|
|
3454
|
-
|
|
3455
|
-
fontSize: 36,
|
|
3456
|
-
fill: "#ffffff",
|
|
3457
|
-
align: "center"
|
|
3458
|
-
};
|
|
3459
|
-
const initialProps = this.spec.initial ?? {};
|
|
3460
|
-
this._template = String(initialProps.text ?? this.spec.text ?? "");
|
|
3461
|
-
const text = new Text({
|
|
3462
|
-
text: this._template,
|
|
3463
|
-
style: baseStyle,
|
|
3464
|
-
label: this.spec.name
|
|
3465
|
-
});
|
|
3466
|
-
text.cullable = true;
|
|
3467
|
-
this.target = text;
|
|
3468
|
-
this.intrinsicWidth = text.width;
|
|
3469
|
-
this.intrinsicHeight = text.height;
|
|
3375
|
+
const compW = this.parent?.width ?? this.root.width;
|
|
3376
|
+
this.compH = this.parent?.height ?? this.root.height;
|
|
3470
3377
|
if (this.duration === void 0) {
|
|
3471
3378
|
this.duration = this.parent?.duration ?? this.root.duration;
|
|
3472
3379
|
}
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3380
|
+
const home = homeCamera(compW, this.compH);
|
|
3381
|
+
const carrier = new Container();
|
|
3382
|
+
carrier.x = home.x;
|
|
3383
|
+
carrier.y = home.y;
|
|
3384
|
+
carrier.z = home.z;
|
|
3385
|
+
carrier.lookAtX = home.lookAtX;
|
|
3386
|
+
carrier.lookAtY = home.lookAtY;
|
|
3387
|
+
carrier.lookAtZ = home.lookAtZ;
|
|
3388
|
+
carrier.fov = home.fov;
|
|
3389
|
+
carrier.focus = 0;
|
|
3390
|
+
carrier.aperture = DEFAULT_APERTURE;
|
|
3391
|
+
carrier.offsetX = carrier.offsetY = carrier.offsetZ = 0;
|
|
3392
|
+
carrier.lookOffsetX = carrier.lookOffsetY = carrier.lookOffsetZ = 0;
|
|
3393
|
+
carrier.fogNear = home.z;
|
|
3394
|
+
carrier.fogFar = home.z * 3;
|
|
3395
|
+
carrier.fogColor = "#000000";
|
|
3396
|
+
carrier.fogAmount = 1;
|
|
3397
|
+
this.target = carrier;
|
|
3398
|
+
const keys = collectPropKeys(this.spec);
|
|
3399
|
+
this.autoZ = !keys.has("z");
|
|
3400
|
+
this.dof = keys.has("focus") || keys.has("aperture");
|
|
3401
|
+
this.fog = keys.has("fogNear") || keys.has("fogFar") || keys.has("fogColor") || keys.has("fogAmount");
|
|
3402
|
+
}
|
|
3403
|
+
/** The aperture to use now: off unless depth of field is on and the value is a number of 0 or more (said once if it is not). */
|
|
3404
|
+
apertureNow(a) {
|
|
3405
|
+
if (!this.dof) return 0;
|
|
3406
|
+
if (!(a >= 0) && !this.warnedAperture) {
|
|
3407
|
+
this.warnedAperture = true;
|
|
3408
|
+
const who = this.spec.name ? ` "${this.spec.name}"` : "";
|
|
3409
|
+
console.warn(`pixi-effects: camera${who}: aperture ${a} is not a number of 0 or more; depth of field is off (it is the lens diameter in px: 30 is the default, 0 turns it off)`);
|
|
3488
3410
|
}
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3411
|
+
return a > 0 ? a : 0;
|
|
3412
|
+
}
|
|
3413
|
+
/** Called by the composition once its children exist: how a layer name in `focus` becomes the z of that layer. */
|
|
3414
|
+
resolveFocusNames(zOf, names = /* @__PURE__ */ new Set()) {
|
|
3415
|
+
this.zOfName = zOf;
|
|
3416
|
+
this.layerNames = names;
|
|
3417
|
+
}
|
|
3418
|
+
displayProps() {
|
|
3419
|
+
const base = super.displayProps();
|
|
3420
|
+
return this.zOfName ? withFocusResolved(base, this.zOfName, this.layerNames) : base;
|
|
3421
|
+
}
|
|
3422
|
+
/** The camera's state at the current timeline position. */
|
|
3423
|
+
state() {
|
|
3424
|
+
const c = this.target;
|
|
3425
|
+
if (!this.warnedFov && !(c.fov >= MIN_FOV && c.fov <= MAX_FOV)) {
|
|
3426
|
+
this.warnedFov = true;
|
|
3427
|
+
const who = this.spec.name ? ` "${this.spec.name}"` : "";
|
|
3428
|
+
console.warn(`pixi-effects: camera${who}: fov ${c.fov} is outside [${MIN_FOV}, ${MAX_FOV}]; clamped`);
|
|
3494
3429
|
}
|
|
3495
|
-
const
|
|
3496
|
-
|
|
3497
|
-
this.
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3430
|
+
const fov = clampFov(c.fov);
|
|
3431
|
+
const z = this.autoZ ? homeDistance(this.compH, fov) : c.z;
|
|
3432
|
+
if (!this.warnedLookAt && c.lookAtX === c.x && c.lookAtY === c.y && c.lookAtZ === z) {
|
|
3433
|
+
this.warnedLookAt = true;
|
|
3434
|
+
const who = this.spec.name ? ` "${this.spec.name}"` : "";
|
|
3435
|
+
console.warn(`pixi-effects: camera${who}: lookAt equals the camera position; looking along -z instead`);
|
|
3436
|
+
}
|
|
3437
|
+
return {
|
|
3438
|
+
x: c.x + c.offsetX,
|
|
3439
|
+
y: c.y + c.offsetY,
|
|
3440
|
+
z: z + c.offsetZ,
|
|
3441
|
+
lookAtX: c.lookAtX + c.lookOffsetX,
|
|
3442
|
+
lookAtY: c.lookAtY + c.lookOffsetY,
|
|
3443
|
+
lookAtZ: c.lookAtZ + c.lookOffsetZ,
|
|
3444
|
+
fov,
|
|
3445
|
+
focus: Number.isFinite(c.focus) ? c.focus : 0,
|
|
3446
|
+
aperture: this.apertureNow(c.aperture)
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
/** The fog now (colour as 0..1 channels, distances, 0..1 amount), or null when the camera writes no fog key. */
|
|
3450
|
+
fogState() {
|
|
3451
|
+
if (!this.fog) return null;
|
|
3452
|
+
const c = this.target;
|
|
3453
|
+
const fc = this.fogC ??= new Color();
|
|
3454
|
+
try {
|
|
3455
|
+
fc.setValue(c.fogColor);
|
|
3456
|
+
} catch {
|
|
3457
|
+
fc.setValue(0);
|
|
3458
|
+
}
|
|
3459
|
+
const [r, g, b] = fc.toArray();
|
|
3460
|
+
const amount = Number.isFinite(c.fogAmount) ? Math.min(1, Math.max(0, c.fogAmount)) : 1;
|
|
3461
|
+
return { r, g, b, near: c.fogNear, far: c.fogFar, amount };
|
|
3462
|
+
}
|
|
3463
|
+
/** Lifespan on the global timeline (valid after bindTimeline). */
|
|
3464
|
+
window() {
|
|
3465
|
+
const start = this.absoluteStart ?? this.at;
|
|
3466
|
+
return { start, end: start + (this.duration ?? 0) };
|
|
3467
|
+
}
|
|
3468
|
+
};
|
|
3469
|
+
|
|
3470
|
+
// src/space/lighting.ts
|
|
3471
|
+
var LIGHT_KINDS = ["ambient", "point", "spot", "parallel"];
|
|
3472
|
+
var FALLOFFS = ["none", "smooth", "inverseSquare"];
|
|
3473
|
+
var LIGHT_PROPS = ["x", "y", "z", "lookAtX", "lookAtY", "lookAtZ", "intensity", "color", "coneAngle", "coneFeather", "radius", "falloffDistance", "shadowDarkness", "shadowDiffusion"];
|
|
3474
|
+
var MAX_LIGHTS = 8;
|
|
3475
|
+
var MAX_CASTERS = 4;
|
|
3476
|
+
var SHADOW_TAPS = 16;
|
|
3477
|
+
var FALLOFF_ID = { none: 0, smooth: 1, inverseSquare: 2 };
|
|
3478
|
+
var KIND_ID = { point: 1, spot: 2, parallel: 3 };
|
|
3479
|
+
function smoothstep(e0, e1, x) {
|
|
3480
|
+
const t = Math.min(1, Math.max(0, (x - e0) / (e1 - e0)));
|
|
3481
|
+
return t * t * (3 - 2 * t);
|
|
3482
|
+
}
|
|
3483
|
+
var DEG4 = Math.PI / 180;
|
|
3484
|
+
function norm(v) {
|
|
3485
|
+
const l = Math.hypot(v.x, v.y, v.z) || 1;
|
|
3486
|
+
return { x: v.x / l, y: v.y / l, z: v.z / l };
|
|
3487
|
+
}
|
|
3488
|
+
var dot = (a, b) => a.x * b.x + a.y * b.y + a.z * b.z;
|
|
3489
|
+
var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y, z: a.z - b.z });
|
|
3490
|
+
function coneCos(l) {
|
|
3491
|
+
const half = Math.min(179, Math.max(0, l.coneAngle)) * DEG4 / 2;
|
|
3492
|
+
const inner = half * (1 - Math.min(1, Math.max(0, l.coneFeather)));
|
|
3493
|
+
const cosOuter = Math.cos(half);
|
|
3494
|
+
const cosInner = Math.max(Math.cos(inner), cosOuter + 1e-4);
|
|
3495
|
+
return { cosOuter, cosInner };
|
|
3496
|
+
}
|
|
3497
|
+
function packLights(lights) {
|
|
3498
|
+
const pos = new Float32Array(4 * MAX_LIGHTS), dir = new Float32Array(4 * MAX_LIGHTS), col = new Float32Array(4 * MAX_LIGHTS), fall = new Float32Array(4 * MAX_LIGHTS), shadow = new Float32Array(4 * MAX_LIGHTS);
|
|
3499
|
+
const ambient = [0, 0, 0];
|
|
3500
|
+
let n = 0;
|
|
3501
|
+
for (const l of lights) {
|
|
3502
|
+
if (l.kind === "ambient") {
|
|
3503
|
+
ambient[0] += l.r * l.intensity;
|
|
3504
|
+
ambient[1] += l.g * l.intensity;
|
|
3505
|
+
ambient[2] += l.b * l.intensity;
|
|
3506
|
+
continue;
|
|
3507
|
+
}
|
|
3508
|
+
if (n >= MAX_LIGHTS) continue;
|
|
3509
|
+
const d = norm({ x: l.lookAtX - l.x, y: l.lookAtY - l.y, z: l.lookAtZ - l.z });
|
|
3510
|
+
const { cosOuter, cosInner } = coneCos(l);
|
|
3511
|
+
pos.set([l.x, l.y, l.z, KIND_ID[l.kind]], 4 * n);
|
|
3512
|
+
dir.set([d.x, d.y, d.z, cosOuter], 4 * n);
|
|
3513
|
+
col.set([l.r * l.intensity, l.g * l.intensity, l.b * l.intensity, cosInner], 4 * n);
|
|
3514
|
+
fall.set([FALLOFF_ID[l.falloff], l.radius, l.falloffDistance, l.castsShadows ? 1 : 0], 4 * n);
|
|
3515
|
+
shadow.set([l.castsShadows ? l.shadowDarkness : 0, l.shadowDiffusion, 0, 0], 4 * n);
|
|
3516
|
+
n++;
|
|
3517
|
+
}
|
|
3518
|
+
return { count: n, ambient, pos, dir, col, fall, shadow };
|
|
3519
|
+
}
|
|
3520
|
+
function shadeReference(p, n, cam, lights) {
|
|
3521
|
+
let N = norm(n);
|
|
3522
|
+
if (dot(N, sub(cam, p)) < 0) N = { x: -N.x, y: -N.y, z: -N.z };
|
|
3523
|
+
const out = [0, 0, 0];
|
|
3524
|
+
for (const l of lights) {
|
|
3525
|
+
if (l.kind === "ambient") {
|
|
3526
|
+
out[0] += l.r * l.intensity;
|
|
3527
|
+
out[1] += l.g * l.intensity;
|
|
3528
|
+
out[2] += l.b * l.intensity;
|
|
3529
|
+
continue;
|
|
3530
|
+
}
|
|
3531
|
+
const dirLA = norm({ x: l.lookAtX - l.x, y: l.lookAtY - l.y, z: l.lookAtZ - l.z });
|
|
3532
|
+
let L;
|
|
3533
|
+
let dist = 0;
|
|
3534
|
+
if (l.kind === "parallel") L = { x: -dirLA.x, y: -dirLA.y, z: -dirLA.z };
|
|
3535
|
+
else {
|
|
3536
|
+
const d = sub({ x: l.x, y: l.y, z: l.z }, p);
|
|
3537
|
+
dist = Math.hypot(d.x, d.y, d.z);
|
|
3538
|
+
L = { x: d.x / dist, y: d.y / dist, z: d.z / dist };
|
|
3539
|
+
}
|
|
3540
|
+
const ndl = Math.max(0, dot(N, L));
|
|
3541
|
+
let att = 1;
|
|
3542
|
+
if (l.kind !== "parallel") {
|
|
3543
|
+
if (l.falloff === "smooth") att = 1 - smoothstep(l.radius, l.radius + Math.max(l.falloffDistance, 1e-3), dist);
|
|
3544
|
+
else if (l.falloff === "inverseSquare") att = Math.min(1, l.radius * l.radius / Math.max(dist * dist, 1e-6));
|
|
3545
|
+
}
|
|
3546
|
+
let cone = 1;
|
|
3547
|
+
if (l.kind === "spot") {
|
|
3548
|
+
const { cosOuter, cosInner } = coneCos(l);
|
|
3549
|
+
cone = smoothstep(cosOuter, cosInner, -dot(L, dirLA));
|
|
3550
|
+
}
|
|
3551
|
+
const k = l.intensity * ndl * att * cone;
|
|
3552
|
+
out[0] += l.r * k;
|
|
3553
|
+
out[1] += l.g * k;
|
|
3554
|
+
out[2] += l.b * k;
|
|
3555
|
+
}
|
|
3556
|
+
return out;
|
|
3557
|
+
}
|
|
3558
|
+
function lightLevel(frame, cam, lights) {
|
|
3559
|
+
const mid = { x: frame.o.x + 0.5 * frame.u.x + 0.5 * frame.v.x, y: frame.o.y + 0.5 * frame.u.y + 0.5 * frame.v.y, z: frame.o.z + 0.5 * frame.u.z + 0.5 * frame.v.z };
|
|
3560
|
+
const n = { x: frame.u.y * frame.v.z - frame.u.z * frame.v.y, y: frame.u.z * frame.v.x - frame.u.x * frame.v.z, z: frame.u.x * frame.v.y - frame.u.y * frame.v.x };
|
|
3561
|
+
const m = shadeReference(mid, n, cam, lights);
|
|
3562
|
+
return 0.2126 * m[0] + 0.7152 * m[1] + 0.0722 * m[2];
|
|
3563
|
+
}
|
|
3564
|
+
var LightSequence = class extends Sequence {
|
|
3565
|
+
color = new Color();
|
|
3566
|
+
async build() {
|
|
3567
|
+
const w = this.parent?.width ?? this.root.width;
|
|
3568
|
+
const h = this.parent?.height ?? this.root.height;
|
|
3569
|
+
if (this.duration === void 0) this.duration = this.parent?.duration ?? this.root.duration;
|
|
3570
|
+
const c = new Container();
|
|
3571
|
+
c.x = w * 0.3;
|
|
3572
|
+
c.y = h * 0.2;
|
|
3573
|
+
c.z = homeDistance(h, 40) * 0.6;
|
|
3574
|
+
c.lookAtX = w / 2;
|
|
3575
|
+
c.lookAtY = h / 2;
|
|
3576
|
+
c.lookAtZ = 0;
|
|
3577
|
+
c.intensity = 1;
|
|
3578
|
+
c.color = "#ffffff";
|
|
3579
|
+
c.coneAngle = 90;
|
|
3580
|
+
c.coneFeather = 0.5;
|
|
3581
|
+
c.radius = 500;
|
|
3582
|
+
c.falloffDistance = 500;
|
|
3583
|
+
c.shadowDarkness = 1;
|
|
3584
|
+
c.shadowDiffusion = 0;
|
|
3585
|
+
this.target = c;
|
|
3586
|
+
}
|
|
3587
|
+
state() {
|
|
3588
|
+
const c = this.target;
|
|
3589
|
+
try {
|
|
3590
|
+
this.color.setValue(c.color);
|
|
3591
|
+
} catch {
|
|
3592
|
+
this.color.setValue(16777215);
|
|
3505
3593
|
}
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3594
|
+
const [r, g, b] = this.color.toArray();
|
|
3595
|
+
return {
|
|
3596
|
+
// a kind or falloff nobody knows is the default the warning promises (the shader must never see an unknown id)
|
|
3597
|
+
kind: LIGHT_KINDS.includes(this.spec.kind) ? this.spec.kind : "point",
|
|
3598
|
+
x: c.x,
|
|
3599
|
+
y: c.y,
|
|
3600
|
+
z: c.z,
|
|
3601
|
+
lookAtX: c.lookAtX,
|
|
3602
|
+
lookAtY: c.lookAtY,
|
|
3603
|
+
lookAtZ: c.lookAtZ,
|
|
3604
|
+
r,
|
|
3605
|
+
g,
|
|
3606
|
+
b,
|
|
3607
|
+
intensity: Number.isFinite(c.intensity) ? Math.max(0, c.intensity) : 1,
|
|
3608
|
+
coneAngle: c.coneAngle,
|
|
3609
|
+
coneFeather: c.coneFeather,
|
|
3610
|
+
falloff: FALLOFFS.includes(this.spec.falloff) ? this.spec.falloff : "none",
|
|
3611
|
+
radius: c.radius,
|
|
3612
|
+
falloffDistance: c.falloffDistance,
|
|
3613
|
+
castsShadows: !!this.spec.castsShadows,
|
|
3614
|
+
shadowDarkness: Math.min(1, Math.max(0, c.shadowDarkness)),
|
|
3615
|
+
shadowDiffusion: Math.max(0, c.shadowDiffusion)
|
|
3616
|
+
};
|
|
3509
3617
|
}
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars", "fillGradient"]);
|
|
3514
|
-
runSuperWithStrippedSpec(this, stripped, timeline, offset);
|
|
3515
|
-
const keyframes = this.spec.keyframes ?? [];
|
|
3516
|
-
const origin = offset + this.at;
|
|
3517
|
-
bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
|
|
3518
|
-
if (this._grad) bindGradientKeyframes(timeline, this._grad, keyframes, this.duration, origin, colorSpace, () => this._applyGradient());
|
|
3519
|
-
const refresh = () => this._refreshText();
|
|
3520
|
-
bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
|
|
3521
|
-
bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
|
|
3522
|
-
bindTextKeyframes(timeline, keyframes, this.duration, origin, () => this._template, (t) => {
|
|
3523
|
-
this._template = t;
|
|
3524
|
-
refresh();
|
|
3525
|
-
}, describeLayer(this.spec));
|
|
3618
|
+
window() {
|
|
3619
|
+
const start = this.absoluteStart ?? this.at;
|
|
3620
|
+
return { start, end: start + (this.duration ?? 0) };
|
|
3526
3621
|
}
|
|
3527
3622
|
};
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3623
|
+
var defaultWarn = (m) => console.warn(m);
|
|
3624
|
+
var SPAN_STYLE_KEYS = ["fill", "fontWeight", "fontStyle", "fontVariant", "letterSpacing", "dropShadow"];
|
|
3625
|
+
var LINE_KEYS = ["fontSize", "fontFamily", "lineHeight", "wordWrap", "wordWrapWidth", "align", "leading", "textBaseline", "whiteSpace", "stroke", "padding"];
|
|
3626
|
+
var spansMeta = /* @__PURE__ */ new WeakMap();
|
|
3627
|
+
var widthOf = (text, style) => CanvasTextMetrics.measureText(text, style).width;
|
|
3628
|
+
function normalize(spans, who, warn) {
|
|
3629
|
+
if (!Array.isArray(spans) || spans.length === 0) {
|
|
3630
|
+
warn(`pixi-effects: ${who}: spans is empty: write an array of strings and { text, fill \u2026 } objects (nothing is drawn)`);
|
|
3631
|
+
return [];
|
|
3535
3632
|
}
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
...kf,
|
|
3543
|
-
set: stripProps(kf.set, keys),
|
|
3544
|
-
to: stripProps(kf.to, keys),
|
|
3545
|
-
from: stripProps(kf.from, keys)
|
|
3546
|
-
})) : void 0
|
|
3547
|
-
};
|
|
3548
|
-
}
|
|
3549
|
-
function stripProps(props, keys) {
|
|
3550
|
-
if (!props) return props;
|
|
3551
|
-
if (!keys.some((k) => k in props)) return props;
|
|
3552
|
-
const out = {};
|
|
3553
|
-
for (const k of Object.keys(props)) if (!keys.includes(k)) out[k] = props[k];
|
|
3554
|
-
return out;
|
|
3555
|
-
}
|
|
3556
|
-
function resolveNumber(v, scope) {
|
|
3557
|
-
if (typeof v === "number") return v;
|
|
3558
|
-
const out = normalizeProps({ v }, scope).v;
|
|
3559
|
-
return typeof out === "number" ? out : Number(out) || 0;
|
|
3560
|
-
}
|
|
3561
|
-
function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDuration, origin, scope) {
|
|
3562
|
-
for (const kf of keyframes) {
|
|
3563
|
-
const setV = kf.set?.[key];
|
|
3564
|
-
const fromV = kf.from?.[key];
|
|
3565
|
-
const toV = kf.to?.[key];
|
|
3566
|
-
if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
|
|
3567
|
-
const at = origin + resolveAt(kf.at, parentDuration);
|
|
3568
|
-
const duration = kfDuration(kf);
|
|
3569
|
-
const ease = kf.ease ?? "none";
|
|
3570
|
-
const loop = loopVars(kf);
|
|
3571
|
-
if (setV !== void 0) {
|
|
3572
|
-
const v = resolveNumber(setV, scope);
|
|
3573
|
-
revertibleSet(timeline, at, () => state[key], (n) => {
|
|
3574
|
-
state[key] = n;
|
|
3575
|
-
refresh();
|
|
3576
|
-
}, v, [state, key]);
|
|
3633
|
+
const out = [];
|
|
3634
|
+
const lineKeys = /* @__PURE__ */ new Set();
|
|
3635
|
+
spans.forEach((sp, i) => {
|
|
3636
|
+
if (typeof sp === "string") {
|
|
3637
|
+
out.push({ text: sp, overrides: {} });
|
|
3638
|
+
return;
|
|
3577
3639
|
}
|
|
3578
|
-
if (
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
{ [key]: resolveNumber(fromV, scope) },
|
|
3582
|
-
{ [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
|
|
3583
|
-
at
|
|
3584
|
-
);
|
|
3585
|
-
} else if (toV !== void 0) {
|
|
3586
|
-
timeline.to(state, { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
|
|
3587
|
-
} else if (fromV !== void 0) {
|
|
3588
|
-
timeline.from(state, { [key]: resolveNumber(fromV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
|
|
3640
|
+
if (!sp || typeof sp !== "object" || typeof sp.text !== "string") {
|
|
3641
|
+
warn(`pixi-effects: ${who}: spans[${i}] must be a string or { text: '\u2026', fill: '#\u2026' }, got ${JSON.stringify(sp)}; skipped`);
|
|
3642
|
+
return;
|
|
3589
3643
|
}
|
|
3590
|
-
|
|
3644
|
+
const { text, ...rest } = sp;
|
|
3645
|
+
const overrides = {};
|
|
3646
|
+
for (const [k, v] of Object.entries(rest)) {
|
|
3647
|
+
if (v === void 0) continue;
|
|
3648
|
+
if (SPAN_STYLE_KEYS.includes(k)) overrides[k] = v;
|
|
3649
|
+
else if (LINE_KEYS.includes(k)) lineKeys.add(k);
|
|
3650
|
+
else {
|
|
3651
|
+
const guess = suggestName(k, [...SPAN_STYLE_KEYS]);
|
|
3652
|
+
warn(`pixi-effects: ${who}: spans[${i}]: "${k}" is not a span key${guess ? ` \u2014 did you mean "${guess}"?` : ""} (it is ignored). A span takes text and ${SPAN_STYLE_KEYS.join(", ")}`);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
out.push({ text, overrides });
|
|
3656
|
+
});
|
|
3657
|
+
if (lineKeys.size) warn(`pixi-effects: ${who}: a span cannot set ${[...lineKeys].join(", ")}: they move the baseline, the line or the pieces' places (a stroke shifts a piece by half its width); put them in the style of the layer (one size, one font and one outline per layer). They are ignored`);
|
|
3658
|
+
return out;
|
|
3591
3659
|
}
|
|
3592
|
-
function
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3660
|
+
function layoutSpans(spans, style, align2, who, warn = defaultWarn) {
|
|
3661
|
+
const clean = normalize(spans, who, warn);
|
|
3662
|
+
const base = new TextStyle(style);
|
|
3663
|
+
const m = CanvasTextMetrics.measureText("M", base);
|
|
3664
|
+
const lineHeight = m.lineHeight ?? m.height;
|
|
3665
|
+
const sentinel = (ts) => widthOf("|", ts);
|
|
3666
|
+
const measure = (text, ts) => widthOf(text + "|", ts) - sentinel(ts);
|
|
3667
|
+
const pieces = [];
|
|
3668
|
+
let line = 0, chars = 0, x = 0;
|
|
3669
|
+
clean.forEach((sp, spanIdx) => {
|
|
3670
|
+
const ts = Object.keys(sp.overrides).length ? new TextStyle({ ...style, ...sp.overrides }) : base;
|
|
3671
|
+
sp.text.split("\n").forEach((part, k) => {
|
|
3672
|
+
if (k > 0) {
|
|
3673
|
+
line++;
|
|
3674
|
+
x = 0;
|
|
3675
|
+
chars += 1;
|
|
3676
|
+
}
|
|
3677
|
+
if (part === "") return;
|
|
3678
|
+
const width2 = measure(part, ts);
|
|
3679
|
+
const length = Array.from(part).length;
|
|
3680
|
+
pieces.push({ text: part, x, y: line * lineHeight, width: width2, line, span: spanIdx, start: chars, length, style: sp.overrides, blank: part.trim() === "" });
|
|
3681
|
+
x += width2;
|
|
3682
|
+
chars += length;
|
|
3683
|
+
});
|
|
3684
|
+
});
|
|
3685
|
+
const lineWidth = /* @__PURE__ */ new Map();
|
|
3686
|
+
for (const p of pieces) lineWidth.set(p.line, Math.max(lineWidth.get(p.line) ?? 0, p.x + p.width));
|
|
3687
|
+
const width = Math.max(0, ...lineWidth.values());
|
|
3688
|
+
for (const p of pieces) {
|
|
3689
|
+
const room = width - (lineWidth.get(p.line) ?? 0);
|
|
3690
|
+
p.x += align2 === "center" ? room / 2 : align2 === "right" ? room : 0;
|
|
3691
|
+
}
|
|
3692
|
+
return { pieces, width, height: pieces.length ? (line + 1) * lineHeight : 0, lineHeight, chars };
|
|
3693
|
+
}
|
|
3694
|
+
function resolveSpansStyle(style, scope, who, warn = defaultWarn) {
|
|
3695
|
+
const out = { ...TEXT_STYLE_DEFAULTS };
|
|
3696
|
+
for (const [k, raw] of Object.entries(style)) {
|
|
3697
|
+
if (raw === void 0) continue;
|
|
3698
|
+
let v = raw;
|
|
3699
|
+
if (scope) {
|
|
3700
|
+
try {
|
|
3701
|
+
v = normalizeProps({ [k]: raw }, scope, { skipKeys: STYLE_OPAQUE_KEYS })[k];
|
|
3702
|
+
} catch (e) {
|
|
3703
|
+
v = NaN;
|
|
3704
|
+
if (!(k in TEXT_STYLE_DEFAULTS) || k === "fontSize") warn(`pixi-effects: ${who}: style.${k} ${JSON.stringify(raw)} could not be resolved (${e.message}); ${k === "fontSize" ? `the default, ${TEXT_STYLE_DEFAULTS.fontSize}, is used` : "it is left out"}`);
|
|
3705
|
+
continue;
|
|
3597
3706
|
}
|
|
3598
3707
|
}
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3708
|
+
if (["fontSize", "letterSpacing", "lineHeight"].includes(k) && (typeof v !== "number" || !Number.isFinite(v) || k !== "letterSpacing" && v <= 0)) {
|
|
3709
|
+
warn(`pixi-effects: ${who}: style.${k} ${JSON.stringify(raw)} is not a usable number here (a name that is not declared counts as 0; spans measure the text when the layer is built); ${k === "fontSize" ? `the default, ${TEXT_STYLE_DEFAULTS.fontSize}, is used` : "it is left out"}`);
|
|
3710
|
+
continue;
|
|
3711
|
+
}
|
|
3712
|
+
out[k] = v;
|
|
3602
3713
|
}
|
|
3714
|
+
return out;
|
|
3603
3715
|
}
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
const
|
|
3609
|
-
|
|
3610
|
-
|
|
3716
|
+
var TEXT_ONLY = ["type", "spans", "text", "style", "format", "colorSpace", "fillGradient", "initial", "keyframes"];
|
|
3717
|
+
var NOT_FOR_A_GROUP = ["fill", "text", "value", "fillGradient"];
|
|
3718
|
+
function expandSpans(spec, warn = defaultWarn, scope) {
|
|
3719
|
+
const who = describeLayer(spec);
|
|
3720
|
+
const s = spec;
|
|
3721
|
+
if (s.text !== void 0) warn(`pixi-effects: ${who}: "text" is ignored when the layer has spans: write the text as spans`);
|
|
3722
|
+
const style = resolveSpansStyle(s.style ?? {}, scope, who, warn);
|
|
3723
|
+
const noLayout = ["wordWrap", "wordWrapWidth", "leading"].filter((k) => style[k] !== void 0 && style[k] !== false && style[k] !== 0);
|
|
3724
|
+
for (const k of ["wordWrap", "wordWrapWidth", "leading"]) delete style[k];
|
|
3725
|
+
if (noLayout.length) warn(`pixi-effects: ${who}: style.${noLayout.join(", style.")} ${noLayout.length > 1 ? "do" : "does"} not apply to a spans layer (the lines are the \\n of the spans); break the lines with \\n. ${noLayout.length > 1 ? "They are" : "It is"} ignored`);
|
|
3726
|
+
const layout = layoutSpans(s.spans, style, style.align, who, warn);
|
|
3727
|
+
const dropped = new Set(["fillGradient", "format", "colorSpace"].filter((k) => s[k] !== void 0));
|
|
3728
|
+
const clean = (bag) => {
|
|
3729
|
+
if (!bag) return bag;
|
|
3730
|
+
const out = {};
|
|
3731
|
+
for (const [k, v] of Object.entries(bag)) {
|
|
3732
|
+
if (NOT_FOR_A_GROUP.includes(k)) dropped.add(k);
|
|
3733
|
+
else out[k] = v;
|
|
3734
|
+
}
|
|
3735
|
+
return out;
|
|
3611
3736
|
};
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
if (
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
}
|
|
3737
|
+
const initial = clean(s.initial) ?? {};
|
|
3738
|
+
const keyframes = (s.keyframes ?? []).map((kf) => ({ ...kf, set: clean(kf.set), to: clean(kf.to), from: clean(kf.from) }));
|
|
3739
|
+
if (dropped.size) warn(`pixi-effects: ${who}: ${[...dropped].join(", ")} do not apply to a spans layer (it is a group of text layers): use a separate text layer, or put the colour in a span`);
|
|
3740
|
+
const typed = initial.visibleChars !== void 0 || keyframes.some((kf) => [kf.set, kf.to, kf.from].some((p) => p && p.visibleChars !== void 0));
|
|
3741
|
+
const width = Math.max(1, layout.width), height = Math.max(1, layout.height);
|
|
3742
|
+
for (const [a, pivot, size] of [["anchorX", "pivotX", width], ["anchorY", "pivotY", height]]) {
|
|
3743
|
+
if (initial[a] === void 0) continue;
|
|
3744
|
+
if (typeof initial[a] === "number") {
|
|
3745
|
+
initial[pivot] = initial[a] * size;
|
|
3746
|
+
delete initial[a];
|
|
3747
|
+
} else {
|
|
3748
|
+
warn(`pixi-effects: ${who}: initial.${a} ${JSON.stringify(initial[a])} must be a number on a spans layer (it becomes the pivot of the block); it is ignored`);
|
|
3749
|
+
delete initial[a];
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
const animatedAnchors = ["anchorX", "anchorY"].filter((a) => keyframes.some((kf) => [kf.set, kf.to, kf.from].some((p) => p && p[a] !== void 0)));
|
|
3753
|
+
if (animatedAnchors.length) warn(`pixi-effects: ${who}: ${animatedAnchors.join(", ")} cannot be keyframed on a spans layer (the anchor is the pivot of the block, set once in initial); the keyframes do nothing`);
|
|
3754
|
+
const baseName = typeof s.name === "string" && s.name ? s.name : "spans";
|
|
3755
|
+
const drawn = layout.pieces.filter((p) => !p.blank);
|
|
3756
|
+
const sequences = drawn.map((p, i) => ({
|
|
3757
|
+
type: "text",
|
|
3758
|
+
name: `${baseName}.${i}`,
|
|
3759
|
+
text: p.text,
|
|
3760
|
+
style: { ...style, ...p.style },
|
|
3761
|
+
initial: { x: p.x, y: p.y, ...typed ? { visibleChars: p.length } : {} }
|
|
3762
|
+
}));
|
|
3763
|
+
const rest = {};
|
|
3764
|
+
for (const [k, v] of Object.entries(s)) if (!TEXT_ONLY.includes(k)) rest[k] = v;
|
|
3765
|
+
const group = { type: "composition", ...rest, width, height, initial, keyframes, sequences };
|
|
3766
|
+
spansMeta.set(group, { typed, pieces: drawn.map((p) => ({ start: p.start, length: p.length })), preview: layout.pieces.map((p, i) => (i > 0 && p.line !== layout.pieces[i - 1].line ? " " : "") + p.text).join("") });
|
|
3767
|
+
return group;
|
|
3768
|
+
}
|
|
3769
|
+
var ImageSequence = class extends Sequence {
|
|
3770
|
+
async build() {
|
|
3771
|
+
const texture = await Assets.get(this.spec.asset);
|
|
3772
|
+
const sprite = new Sprite({ texture, label: this.spec.name });
|
|
3773
|
+
sprite.cullable = true;
|
|
3774
|
+
this.target = sprite;
|
|
3775
|
+
this.intrinsicWidth = texture.width;
|
|
3776
|
+
this.intrinsicHeight = texture.height;
|
|
3777
|
+
if (this.duration === void 0) {
|
|
3778
|
+
this.duration = this.parent?.duration ?? this.root.duration;
|
|
3624
3779
|
}
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3780
|
+
this.buildFilters();
|
|
3781
|
+
}
|
|
3782
|
+
bindTimeline(timeline, offset = 0) {
|
|
3783
|
+
if (!this.target) return;
|
|
3784
|
+
const colorSpace = this.spec.colorSpace;
|
|
3785
|
+
if (!colorSpace || colorSpace === "rgb") {
|
|
3786
|
+
super.bindTimeline(timeline, offset);
|
|
3787
|
+
return;
|
|
3788
|
+
}
|
|
3789
|
+
const scope = this.scope();
|
|
3790
|
+
const startTime = offset + this.at;
|
|
3791
|
+
const initial = stripKeys2(this.spec.initial, ["tint"]);
|
|
3792
|
+
const keyframes = this.spec.keyframes ? this.spec.keyframes.map((kf) => stripKeysInKeyframe(kf, ["tint"])) : void 0;
|
|
3793
|
+
applyInitial(this.target, initial, scope);
|
|
3794
|
+
applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime);
|
|
3795
|
+
const initTint = this.spec.initial?.tint;
|
|
3796
|
+
if (initTint !== void 0) this.target.tint = initTint;
|
|
3797
|
+
for (const kf of this.spec.keyframes ?? []) {
|
|
3798
|
+
const at = startTime + resolveAt(kf.at, this.duration);
|
|
3799
|
+
const duration = kfDuration(kf);
|
|
3800
|
+
const ease = kf.ease ?? "none";
|
|
3801
|
+
const fromTint = kf.from?.tint;
|
|
3802
|
+
const toTint = kf.to?.tint;
|
|
3803
|
+
const setTint = kf.set?.tint;
|
|
3804
|
+
if (setTint !== void 0) {
|
|
3805
|
+
const sprite = this.target;
|
|
3806
|
+
revertibleSet(timeline, at, () => sprite.tint, (c) => {
|
|
3807
|
+
sprite.tint = c;
|
|
3808
|
+
}, setTint, [sprite, "tint"]);
|
|
3809
|
+
}
|
|
3810
|
+
if (toTint !== void 0) {
|
|
3811
|
+
tweenColor(
|
|
3812
|
+
timeline,
|
|
3813
|
+
this.target,
|
|
3814
|
+
"tint",
|
|
3815
|
+
fromTint,
|
|
3816
|
+
toTint,
|
|
3817
|
+
duration,
|
|
3818
|
+
ease,
|
|
3819
|
+
at,
|
|
3820
|
+
colorSpace,
|
|
3821
|
+
void 0,
|
|
3822
|
+
loopVars(kf)
|
|
3823
|
+
);
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
this.absoluteStart = startTime;
|
|
3827
|
+
const endTime = startTime + this.duration;
|
|
3828
|
+
if (this.keepHidden) return;
|
|
3829
|
+
this.target.renderable = startTime <= 0;
|
|
3830
|
+
timeline.set(this.target, { renderable: true }, startTime);
|
|
3831
|
+
timeline.set(this.target, { renderable: false }, endTime);
|
|
3832
|
+
}
|
|
3833
|
+
};
|
|
3834
|
+
function stripKeys2(props, keys) {
|
|
3835
|
+
if (!props) return props;
|
|
3836
|
+
const drop = new Set(keys);
|
|
3837
|
+
const out = {};
|
|
3838
|
+
let changed = false;
|
|
3839
|
+
for (const k of Object.keys(props)) {
|
|
3840
|
+
if (drop.has(k)) {
|
|
3841
|
+
changed = true;
|
|
3842
|
+
continue;
|
|
3639
3843
|
}
|
|
3844
|
+
out[k] = props[k];
|
|
3640
3845
|
}
|
|
3846
|
+
return changed ? out : props;
|
|
3641
3847
|
}
|
|
3642
|
-
function
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3848
|
+
function stripKeysInKeyframe(kf, keys) {
|
|
3849
|
+
return {
|
|
3850
|
+
...kf,
|
|
3851
|
+
set: stripKeys2(kf.set, keys),
|
|
3852
|
+
to: stripKeys2(kf.to, keys),
|
|
3853
|
+
from: stripKeys2(kf.from, keys)
|
|
3854
|
+
};
|
|
3647
3855
|
}
|
|
3648
3856
|
var AudioSequence = class extends Sequence {
|
|
3649
3857
|
_audioBuffer = null;
|
|
@@ -5398,10 +5606,15 @@ function resolveDim(v, scope) {
|
|
|
5398
5606
|
var _CompositionSequence = null;
|
|
5399
5607
|
async function getCompositionSequence() {
|
|
5400
5608
|
if (!_CompositionSequence) {
|
|
5401
|
-
_CompositionSequence = (await import('./Composition-
|
|
5609
|
+
_CompositionSequence = (await import('./Composition-RF7QOEXT.js')).CompositionSequence;
|
|
5402
5610
|
}
|
|
5403
5611
|
return _CompositionSequence;
|
|
5404
5612
|
}
|
|
5613
|
+
var _SpansSequence = null;
|
|
5614
|
+
async function getSpansSequence() {
|
|
5615
|
+
if (!_SpansSequence) _SpansSequence = (await import('./Spans-SDXABOV6.js')).SpansSequence;
|
|
5616
|
+
return _SpansSequence;
|
|
5617
|
+
}
|
|
5405
5618
|
var staticTypes = {
|
|
5406
5619
|
image: ImageSequence,
|
|
5407
5620
|
text: TextSequence,
|
|
@@ -5419,8 +5632,12 @@ function registerSequenceType(type, ctor) {
|
|
|
5419
5632
|
}
|
|
5420
5633
|
async function buildSequenceTree(specs, parent, root) {
|
|
5421
5634
|
const out = [];
|
|
5422
|
-
for (
|
|
5635
|
+
for (let spec of specs) {
|
|
5423
5636
|
let Cls = staticTypes[spec.type] ?? registry2[spec.type];
|
|
5637
|
+
if (spec.type === "text" && spec.spans !== void 0) {
|
|
5638
|
+
spec = expandSpans(spec, void 0, buildScope({ intrinsicWidth: 0, intrinsicHeight: 0, at: spec.at, duration: spec.duration }, parent, root));
|
|
5639
|
+
Cls = await getSpansSequence();
|
|
5640
|
+
}
|
|
5424
5641
|
if (!Cls && spec.type === "composition") {
|
|
5425
5642
|
Cls = await getCompositionSequence();
|
|
5426
5643
|
}
|
|
@@ -5440,6 +5657,6 @@ async function buildSequenceTree(specs, parent, root) {
|
|
|
5440
5657
|
return out;
|
|
5441
5658
|
}
|
|
5442
5659
|
|
|
5443
|
-
export { CameraSequence, DEFAULT_FOV, DEG3 as DEG, FALLOFFS, LIGHT_KINDS, LIGHT_PROPS, LightSequence, MANY_BLURRED, MAX_BLUR, MAX_CASTERS, MAX_LIGHTS, MIN_BLUR, NEAR, NullSequence, SHADOW_TAPS, Sequence, bindClock, blurRadius, buildScope, buildSequenceTree, cameraBasis, checkEase, clockTable, contentLength, createTimeline, ensureFilterLibrary, evaluateExpr, flattenSvgPath, focusProblems, homeCamera, homeDistance, layerInitialZ, layerToWorld, lengthen, lightLevel, noise, packLights, parseEase, projectLayer, projectPoint, rand, random, registerSequenceType, remapOf, resolveAt, timeRemapOf };
|
|
5444
|
-
//# sourceMappingURL=chunk-
|
|
5445
|
-
//# sourceMappingURL=chunk-
|
|
5660
|
+
export { CameraSequence, DEFAULT_FOV, DEG3 as DEG, EXPRESSION_NAMES, FALLOFFS, LIGHT_KINDS, LIGHT_PROPS, LightSequence, MANY_BLURRED, MAX_BLUR, MAX_CASTERS, MAX_LIGHTS, MIN_BLUR, NEAR, NullSequence, SHADOW_TAPS, Sequence, TextSequence, bindClock, bindCounterKeyframes, blurRadius, buildScope, buildSequenceTree, cameraBasis, checkEase, clockTable, contentLength, createTimeline, ensureFilterLibrary, evaluateExpr, flattenSvgPath, focusProblems, homeCamera, homeDistance, layerInitialZ, layerToWorld, lengthen, lightLevel, noise, packLights, parseEase, projectLayer, projectPoint, rand, random, registerSequenceType, remapOf, resolveAt, resolveNumber, spansMeta, timeRemapOf };
|
|
5661
|
+
//# sourceMappingURL=chunk-AW5NPDE4.js.map
|
|
5662
|
+
//# sourceMappingURL=chunk-AW5NPDE4.js.map
|