pixi-effects 0.31.0 → 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 +18 -0
- package/README.md +5 -5
- package/ai/chat-template.html +3 -3
- package/ai/tools/render.mjs +19 -2
- package/dist/{Base-jRiMHcWT.d.ts → Base-CXsMdmJB.d.ts} +1 -1
- package/dist/{Base-BKagtWc0.d.cts → Base-pNq6sVGl.d.cts} +1 -1
- package/dist/Composition-LNMKWXSL.cjs +16 -0
- package/dist/{Composition-V2XYSW6P.cjs.map → Composition-LNMKWXSL.cjs.map} +1 -1
- package/dist/Composition-RF7QOEXT.js +7 -0
- package/dist/{Composition-DQCPJPFE.js.map → Composition-RF7QOEXT.js.map} +1 -1
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/{Movie-BH81stkb.d.cts → Movie-CL6SsAWe.d.cts} +1 -1
- package/dist/{Movie-CfFPItoL.d.ts → Movie-f0tQG9y5.d.ts} +1 -1
- 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-MMTESMJ5.js → chunk-AW5NPDE4.js} +1363 -1151
- 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-L73KRFXI.cjs → chunk-CRUDOKSD.cjs} +91 -91
- package/dist/chunk-CRUDOKSD.cjs.map +1 -0
- package/dist/{chunk-BGMCZA36.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-UTIBYMIO.js → chunk-FCMOREIS.js} +10 -6
- package/dist/chunk-FCMOREIS.js.map +1 -0
- package/dist/{chunk-X3WHETQ3.cjs → chunk-K3MAEKFM.cjs} +42 -38
- 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-DJ5B7IKT.cjs → chunk-QHDN6TF5.cjs} +1377 -1161
- 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-CK_crNAI.d.ts → types-CavnUvd5.d.cts} +26 -1
- package/dist/{types-CK_crNAI.d.cts → types-CavnUvd5.d.ts} +26 -1
- package/docs/dsl.md +14 -4
- package/llms-full.txt +19 -8
- package/package.json +1 -1
- package/skills/pixi-effects/SKILL.md +1 -1
- package/skills/pixi-effects/reference/cheatsheet.md +2 -2
- package/skills/pixi-effects/reference/pitfalls.md +2 -2
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-DQCPJPFE.js +0 -7
- package/dist/Composition-V2XYSW6P.cjs +0 -16
- package/dist/chunk-BGMCZA36.js.map +0 -1
- package/dist/chunk-DJ5B7IKT.cjs.map +0 -1
- package/dist/chunk-L73KRFXI.cjs.map +0 -1
- package/dist/chunk-LYOKH7KE.cjs.map +0 -1
- package/dist/chunk-MMTESMJ5.js.map +0 -1
- package/dist/chunk-RCVZX2JP.js.map +0 -1
- package/dist/chunk-UTIBYMIO.js.map +0 -1
- package/dist/chunk-X3WHETQ3.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
|
}
|
|
@@ -2248,6 +2291,13 @@ var registry = {
|
|
|
2248
2291
|
function looksLikePixiFilter(x) {
|
|
2249
2292
|
return !!x && typeof x === "object" && typeof x.apply === "function";
|
|
2250
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
|
+
}
|
|
2251
2301
|
function createFilter(spec) {
|
|
2252
2302
|
if (spec.type === "custom") {
|
|
2253
2303
|
const inst2 = spec.filter;
|
|
@@ -2258,7 +2308,8 @@ function createFilter(spec) {
|
|
|
2258
2308
|
named._name = spec.name;
|
|
2259
2309
|
return named;
|
|
2260
2310
|
}
|
|
2261
|
-
const { type, name, ...
|
|
2311
|
+
const { type, name, ...given } = spec;
|
|
2312
|
+
const params = copyPlain(given);
|
|
2262
2313
|
const Cls = registry[type];
|
|
2263
2314
|
const inst = Cls ? new Cls(params) : createNamedFilter(type, params);
|
|
2264
2315
|
inst._name = name;
|
|
@@ -2383,694 +2434,345 @@ var NullSequence = class extends Sequence {
|
|
|
2383
2434
|
}
|
|
2384
2435
|
};
|
|
2385
2436
|
|
|
2386
|
-
// src/
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
const hasInitial = !!spec.initial && "time" in spec.initial;
|
|
2390
|
-
const hasKf = (spec.keyframes ?? []).some((kf) => BAGS.some((b) => kf[b] && "time" in kf[b]));
|
|
2391
|
-
const speed = typeof spec.speed === "number" && Number.isFinite(spec.speed) && spec.speed !== 0 ? spec.speed : void 0;
|
|
2392
|
-
if (!hasInitial && !hasKf && speed === void 0) return null;
|
|
2393
|
-
const timeKfs = [];
|
|
2394
|
-
const restKfs = [];
|
|
2395
|
-
for (const kf of spec.keyframes ?? []) {
|
|
2396
|
-
const t = { ...kf }, r = { ...kf };
|
|
2397
|
-
let touched = false, left = false;
|
|
2398
|
-
for (const b of BAGS) {
|
|
2399
|
-
const bag = kf[b];
|
|
2400
|
-
if (!bag) {
|
|
2401
|
-
delete t[b];
|
|
2402
|
-
delete r[b];
|
|
2403
|
-
continue;
|
|
2404
|
-
}
|
|
2405
|
-
const { time, ...others } = bag;
|
|
2406
|
-
if ("time" in bag) {
|
|
2407
|
-
touched = true;
|
|
2408
|
-
t[b] = { [prop]: time };
|
|
2409
|
-
} else delete t[b];
|
|
2410
|
-
if (Object.keys(others).length) {
|
|
2411
|
-
r[b] = others;
|
|
2412
|
-
left = true;
|
|
2413
|
-
} else delete r[b];
|
|
2414
|
-
}
|
|
2415
|
-
if (touched) timeKfs.push(t);
|
|
2416
|
-
if (!touched || left) restKfs.push(r);
|
|
2417
|
-
}
|
|
2418
|
-
let restInitial = spec.initial;
|
|
2419
|
-
let start;
|
|
2420
|
-
if (hasInitial) {
|
|
2421
|
-
const { time, ...others } = spec.initial;
|
|
2422
|
-
start = typeof time === "number" && Number.isFinite(time) ? time : void 0;
|
|
2423
|
-
restInitial = others;
|
|
2424
|
-
}
|
|
2425
|
-
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;
|
|
2426
2440
|
}
|
|
2427
|
-
function
|
|
2428
|
-
const
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
for (const b of BAGS) {
|
|
2432
|
-
const bag = kf[b];
|
|
2433
|
-
if (bag && prop in bag) out[b] = { ...bag, [prop]: fix(bag[prop]) };
|
|
2434
|
-
}
|
|
2435
|
-
return out;
|
|
2436
|
-
});
|
|
2437
|
-
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 });
|
|
2438
2445
|
}
|
|
2439
|
-
function
|
|
2440
|
-
const
|
|
2441
|
-
|
|
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
|
+
};
|
|
2442
2458
|
}
|
|
2443
|
-
function
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
for (const
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
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
|
+
};
|
|
2453
2484
|
}
|
|
2454
|
-
|
|
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
|
+
};
|
|
2455
2499
|
}
|
|
2456
|
-
function
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
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 };
|
|
2461
2508
|
}
|
|
2462
|
-
const s =
|
|
2463
|
-
|
|
2464
|
-
|
|
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 };
|
|
2465
2513
|
}
|
|
2466
|
-
function
|
|
2467
|
-
const
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
tab[k] = v;
|
|
2480
|
-
const t = k * h;
|
|
2481
|
-
if (t >= at && t <= at + duration) {
|
|
2482
|
-
if (v < min) min = v;
|
|
2483
|
-
if (v > max) max = v;
|
|
2484
|
-
}
|
|
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;
|
|
2485
2527
|
}
|
|
2486
|
-
|
|
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);
|
|
2487
2533
|
return {
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
},
|
|
2493
|
-
min: Number.isFinite(min) ? min : 0,
|
|
2494
|
-
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
|
|
2495
2538
|
};
|
|
2496
2539
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
var MAX_FOV = 179;
|
|
2503
|
-
var NEAR = 1;
|
|
2504
|
-
function clampFov(fov) {
|
|
2505
|
-
if (!Number.isFinite(fov)) return DEFAULT_FOV;
|
|
2506
|
-
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})`;
|
|
2507
2545
|
}
|
|
2508
|
-
function
|
|
2509
|
-
return
|
|
2546
|
+
function srgbToLinear(c) {
|
|
2547
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
2510
2548
|
}
|
|
2511
|
-
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);
|
|
2512
2562
|
return {
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
lookAtX: compW / 2,
|
|
2517
|
-
lookAtY: compH / 2,
|
|
2518
|
-
lookAtZ: 0,
|
|
2519
|
-
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_
|
|
2520
2566
|
};
|
|
2521
2567
|
}
|
|
2522
|
-
function
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
fy = 0;
|
|
2530
|
-
fz = -1;
|
|
2531
|
-
len = 1;
|
|
2532
|
-
}
|
|
2533
|
-
fx /= len;
|
|
2534
|
-
fy /= len;
|
|
2535
|
-
fz /= len;
|
|
2536
|
-
let rx = -fz;
|
|
2537
|
-
const ry = 0;
|
|
2538
|
-
let rz = fx;
|
|
2539
|
-
const rlen = Math.hypot(rx, rz);
|
|
2540
|
-
if (rlen < 1e-9) {
|
|
2541
|
-
rx = 1;
|
|
2542
|
-
rz = 0;
|
|
2543
|
-
} else {
|
|
2544
|
-
rx /= rlen;
|
|
2545
|
-
rz /= rlen;
|
|
2546
|
-
}
|
|
2547
|
-
const dx = ry * fz - rz * fy;
|
|
2548
|
-
const dy = rz * fx - rx * fz;
|
|
2549
|
-
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_;
|
|
2550
2575
|
return {
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
rx,
|
|
2555
|
-
ry,
|
|
2556
|
-
rz,
|
|
2557
|
-
dx,
|
|
2558
|
-
dy,
|
|
2559
|
-
dz,
|
|
2560
|
-
fx,
|
|
2561
|
-
fy,
|
|
2562
|
-
fz,
|
|
2563
|
-
focal: compH / 2 / Math.tan(clampFov(cam.fov) * DEG3 / 2),
|
|
2564
|
-
halfW: compW / 2,
|
|
2565
|
-
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)
|
|
2566
2579
|
};
|
|
2567
2580
|
}
|
|
2568
|
-
function
|
|
2569
|
-
|
|
2570
|
-
let y = (py - t.pivotY) * t.scaleY;
|
|
2571
|
-
let z = 0;
|
|
2572
|
-
let c = Math.cos(t.rotationX);
|
|
2573
|
-
let s = Math.sin(t.rotationX);
|
|
2574
|
-
[y, z] = [y * c - z * s, y * s + z * c];
|
|
2575
|
-
c = Math.cos(t.rotationY);
|
|
2576
|
-
s = Math.sin(t.rotationY);
|
|
2577
|
-
[x, z] = [x * c + z * s, -x * s + z * c];
|
|
2578
|
-
c = Math.cos(t.rotationZ);
|
|
2579
|
-
s = Math.sin(t.rotationZ);
|
|
2580
|
-
[x, y] = [x * c - y * s, x * s + y * c];
|
|
2581
|
-
return { x: x + t.x, y: y + t.y, z: z + t.z };
|
|
2582
|
-
}
|
|
2583
|
-
function projectPoint(b, p) {
|
|
2584
|
-
const vx = p.x - b.cx;
|
|
2585
|
-
const vy = p.y - b.cy;
|
|
2586
|
-
const vz = p.z - b.cz;
|
|
2587
|
-
const xc = vx * b.rx + vy * b.ry + vz * b.rz;
|
|
2588
|
-
const yc = vx * b.dx + vy * b.dy + vz * b.dz;
|
|
2589
|
-
const zc = vx * b.fx + vy * b.fy + vz * b.fz;
|
|
2590
|
-
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) };
|
|
2591
2583
|
}
|
|
2592
|
-
function
|
|
2593
|
-
|
|
2594
|
-
[frame.x, frame.y],
|
|
2595
|
-
[frame.x + frame.width, frame.y],
|
|
2596
|
-
[frame.x + frame.width, frame.y + frame.height],
|
|
2597
|
-
[frame.x, frame.y + frame.height]
|
|
2598
|
-
];
|
|
2599
|
-
let visible = true;
|
|
2600
|
-
const corners = [];
|
|
2601
|
-
for (const [px, py] of pts) {
|
|
2602
|
-
const p = projectPoint(b, layerToWorld(t, px, py));
|
|
2603
|
-
if (!(p.depth > NEAR)) visible = false;
|
|
2604
|
-
corners.push(p.x, p.y);
|
|
2605
|
-
}
|
|
2606
|
-
const origin = projectPoint(b, { x: t.x, y: t.y, z: t.z });
|
|
2607
|
-
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) };
|
|
2608
2586
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
var DEFAULT_APERTURE = 30;
|
|
2612
|
-
var MIN_BLUR = 0.05;
|
|
2613
|
-
var MAX_BLUR = 32;
|
|
2614
|
-
var MANY_BLURRED = 20;
|
|
2615
|
-
function blurRadius(aperture, focal, depth, focusDepth) {
|
|
2616
|
-
if (!(aperture > 0) || !(focal > 0) || !(depth > 0) || !(focusDepth > 0)) return 0;
|
|
2617
|
-
const r = aperture * focal * Math.abs(1 / depth - 1 / focusDepth) / 2;
|
|
2618
|
-
return Math.min(r, MAX_BLUR);
|
|
2587
|
+
function lerp(a, b, t) {
|
|
2588
|
+
return a + (b - a) * t;
|
|
2619
2589
|
}
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
return /^[A-Za-z_][\w-]*$/.test(s) && !SCOPE_NAMES.has(s);
|
|
2590
|
+
function clamp01(v) {
|
|
2591
|
+
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
2623
2592
|
}
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
};
|
|
2631
|
-
read2(props.initial);
|
|
2632
|
-
for (const kf of props.keyframes ?? []) for (const bag of [kf.set, kf.to, kf.from]) read2(bag);
|
|
2633
|
-
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;
|
|
2634
2599
|
}
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
if (!bag || !isName(v, known)) return bag;
|
|
2640
|
-
return { ...bag, focus: zOf(v) ?? 0 };
|
|
2641
|
-
};
|
|
2600
|
+
|
|
2601
|
+
// src/sequences/gradientAnim.ts
|
|
2602
|
+
var PATCH_KEYS = ["type", "angle", "center", "innerRadius", "radius", "stops"];
|
|
2603
|
+
function gradStateFrom(g) {
|
|
2642
2604
|
return {
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
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) })
|
|
2646
2611
|
};
|
|
2647
2612
|
}
|
|
2648
|
-
function
|
|
2649
|
-
const
|
|
2650
|
-
if (typeof
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
const known = new Set(siblings.map((s) => s.name).filter((n) => !!n));
|
|
2660
|
-
for (const name of focusNames(camera, known)) {
|
|
2661
|
-
if (said.has(name)) continue;
|
|
2662
|
-
said.add(name);
|
|
2663
|
-
const same = siblings.filter((s) => s.name === name);
|
|
2664
|
-
const hit = same.find((s) => s.threeD) ?? same[0];
|
|
2665
|
-
if (!hit) out.push({ kind: "missing", name, hint: suggestName(name, threeD) });
|
|
2666
|
-
else if (!hit.threeD) out.push({ kind: "not-threeD", name });
|
|
2667
|
-
else {
|
|
2668
|
-
if (same.length > 1) out.push({ kind: "duplicate", name, count: same.length });
|
|
2669
|
-
if (movesZ(hit)) out.push({ kind: "moving", name });
|
|
2670
|
-
}
|
|
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;
|
|
2671
2624
|
}
|
|
2672
2625
|
return out;
|
|
2673
2626
|
}
|
|
2674
|
-
var
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
/** True when the camera writes `focus` or `aperture` anywhere: depth of field is on. */
|
|
2682
|
-
dof = false;
|
|
2683
|
-
/** True when the camera writes any fog key anywhere: depth fog is on (nothing is made for a camera without fog). */
|
|
2684
|
-
fog = false;
|
|
2685
|
-
fogC = null;
|
|
2686
|
-
zOfName = null;
|
|
2687
|
-
layerNames = /* @__PURE__ */ new Set();
|
|
2688
|
-
async build() {
|
|
2689
|
-
const compW = this.parent?.width ?? this.root.width;
|
|
2690
|
-
this.compH = this.parent?.height ?? this.root.height;
|
|
2691
|
-
if (this.duration === void 0) {
|
|
2692
|
-
this.duration = this.parent?.duration ?? this.root.duration;
|
|
2693
|
-
}
|
|
2694
|
-
const home = homeCamera(compW, this.compH);
|
|
2695
|
-
const carrier = new Container();
|
|
2696
|
-
carrier.x = home.x;
|
|
2697
|
-
carrier.y = home.y;
|
|
2698
|
-
carrier.z = home.z;
|
|
2699
|
-
carrier.lookAtX = home.lookAtX;
|
|
2700
|
-
carrier.lookAtY = home.lookAtY;
|
|
2701
|
-
carrier.lookAtZ = home.lookAtZ;
|
|
2702
|
-
carrier.fov = home.fov;
|
|
2703
|
-
carrier.focus = 0;
|
|
2704
|
-
carrier.aperture = DEFAULT_APERTURE;
|
|
2705
|
-
carrier.offsetX = carrier.offsetY = carrier.offsetZ = 0;
|
|
2706
|
-
carrier.lookOffsetX = carrier.lookOffsetY = carrier.lookOffsetZ = 0;
|
|
2707
|
-
carrier.fogNear = home.z;
|
|
2708
|
-
carrier.fogFar = home.z * 3;
|
|
2709
|
-
carrier.fogColor = "#000000";
|
|
2710
|
-
carrier.fogAmount = 1;
|
|
2711
|
-
this.target = carrier;
|
|
2712
|
-
const keys = collectPropKeys(this.spec);
|
|
2713
|
-
this.autoZ = !keys.has("z");
|
|
2714
|
-
this.dof = keys.has("focus") || keys.has("aperture");
|
|
2715
|
-
this.fog = keys.has("fogNear") || keys.has("fogFar") || keys.has("fogColor") || keys.has("fogAmount");
|
|
2716
|
-
}
|
|
2717
|
-
/** 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). */
|
|
2718
|
-
apertureNow(a) {
|
|
2719
|
-
if (!this.dof) return 0;
|
|
2720
|
-
if (!(a >= 0) && !this.warnedAperture) {
|
|
2721
|
-
this.warnedAperture = true;
|
|
2722
|
-
const who = this.spec.name ? ` "${this.spec.name}"` : "";
|
|
2723
|
-
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}`);
|
|
2724
2634
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
const base = super.displayProps();
|
|
2734
|
-
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");
|
|
2735
2643
|
}
|
|
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
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
/** The fog now (colour as 0..1 channels, distances, 0..1 amount), or null when the camera writes no fog key. */
|
|
2764
|
-
fogState() {
|
|
2765
|
-
if (!this.fog) return null;
|
|
2766
|
-
const c = this.target;
|
|
2767
|
-
const fc = this.fogC ??= new Color();
|
|
2768
|
-
try {
|
|
2769
|
-
fc.setValue(c.fogColor);
|
|
2770
|
-
} catch {
|
|
2771
|
-
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
|
+
}
|
|
2772
2671
|
}
|
|
2773
|
-
const [r, g, b] = fc.toArray();
|
|
2774
|
-
const amount = Number.isFinite(c.fogAmount) ? Math.min(1, Math.max(0, c.fogAmount)) : 1;
|
|
2775
|
-
return { r, g, b, near: c.fogNear, far: c.fogFar, amount };
|
|
2776
|
-
}
|
|
2777
|
-
/** Lifespan on the global timeline (valid after bindTimeline). */
|
|
2778
|
-
window() {
|
|
2779
|
-
const start = this.absoluteStart ?? this.at;
|
|
2780
|
-
return { start, end: start + (this.duration ?? 0) };
|
|
2781
2672
|
}
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
// src/space/lighting.ts
|
|
2785
|
-
var LIGHT_KINDS = ["ambient", "point", "spot", "parallel"];
|
|
2786
|
-
var FALLOFFS = ["none", "smooth", "inverseSquare"];
|
|
2787
|
-
var LIGHT_PROPS = ["x", "y", "z", "lookAtX", "lookAtY", "lookAtZ", "intensity", "color", "coneAngle", "coneFeather", "radius", "falloffDistance", "shadowDarkness", "shadowDiffusion"];
|
|
2788
|
-
var MAX_LIGHTS = 8;
|
|
2789
|
-
var MAX_CASTERS = 4;
|
|
2790
|
-
var SHADOW_TAPS = 16;
|
|
2791
|
-
var FALLOFF_ID = { none: 0, smooth: 1, inverseSquare: 2 };
|
|
2792
|
-
var KIND_ID = { point: 1, spot: 2, parallel: 3 };
|
|
2793
|
-
function smoothstep(e0, e1, x) {
|
|
2794
|
-
const t = Math.min(1, Math.max(0, (x - e0) / (e1 - e0)));
|
|
2795
|
-
return t * t * (3 - 2 * t);
|
|
2796
|
-
}
|
|
2797
|
-
var DEG4 = Math.PI / 180;
|
|
2798
|
-
function norm(v) {
|
|
2799
|
-
const l = Math.hypot(v.x, v.y, v.z) || 1;
|
|
2800
|
-
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)");
|
|
2801
2674
|
}
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
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
|
+
});
|
|
2810
2707
|
}
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
ambient[2] += l.b * l.intensity;
|
|
2820
|
-
continue;
|
|
2821
|
-
}
|
|
2822
|
-
if (n >= MAX_LIGHTS) continue;
|
|
2823
|
-
const d = norm({ x: l.lookAtX - l.x, y: l.lookAtY - l.y, z: l.lookAtZ - l.z });
|
|
2824
|
-
const { cosOuter, cosInner } = coneCos(l);
|
|
2825
|
-
pos.set([l.x, l.y, l.z, KIND_ID[l.kind]], 4 * n);
|
|
2826
|
-
dir.set([d.x, d.y, d.z, cosOuter], 4 * n);
|
|
2827
|
-
col.set([l.r * l.intensity, l.g * l.intensity, l.b * l.intensity, cosInner], 4 * n);
|
|
2828
|
-
fall.set([FALLOFF_ID[l.falloff], l.radius, l.falloffDistance, l.castsShadows ? 1 : 0], 4 * n);
|
|
2829
|
-
shadow.set([l.castsShadows ? l.shadowDarkness : 0, l.shadowDiffusion, 0, 0], 4 * n);
|
|
2830
|
-
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;
|
|
2831
2716
|
}
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
else {
|
|
2850
|
-
const d = sub({ x: l.x, y: l.y, z: l.z }, p);
|
|
2851
|
-
dist = Math.hypot(d.x, d.y, d.z);
|
|
2852
|
-
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);
|
|
2853
2734
|
}
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
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
|
+
});
|
|
2859
2765
|
}
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
const { cosOuter, cosInner } = coneCos(l);
|
|
2863
|
-
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));
|
|
2864
2768
|
}
|
|
2865
|
-
const k = l.intensity * ndl * att * cone;
|
|
2866
|
-
out[0] += l.r * k;
|
|
2867
|
-
out[1] += l.g * k;
|
|
2868
|
-
out[2] += l.b * k;
|
|
2869
2769
|
}
|
|
2870
|
-
return out;
|
|
2871
2770
|
}
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
}
|
|
2878
|
-
var LightSequence = class extends Sequence {
|
|
2879
|
-
color = new Color();
|
|
2880
|
-
async build() {
|
|
2881
|
-
const w = this.parent?.width ?? this.root.width;
|
|
2882
|
-
const h = this.parent?.height ?? this.root.height;
|
|
2883
|
-
if (this.duration === void 0) this.duration = this.parent?.duration ?? this.root.duration;
|
|
2884
|
-
const c = new Container();
|
|
2885
|
-
c.x = w * 0.3;
|
|
2886
|
-
c.y = h * 0.2;
|
|
2887
|
-
c.z = homeDistance(h, 40) * 0.6;
|
|
2888
|
-
c.lookAtX = w / 2;
|
|
2889
|
-
c.lookAtY = h / 2;
|
|
2890
|
-
c.lookAtZ = 0;
|
|
2891
|
-
c.intensity = 1;
|
|
2892
|
-
c.color = "#ffffff";
|
|
2893
|
-
c.coneAngle = 90;
|
|
2894
|
-
c.coneFeather = 0.5;
|
|
2895
|
-
c.radius = 500;
|
|
2896
|
-
c.falloffDistance = 500;
|
|
2897
|
-
c.shadowDarkness = 1;
|
|
2898
|
-
c.shadowDiffusion = 0;
|
|
2899
|
-
this.target = c;
|
|
2900
|
-
}
|
|
2901
|
-
state() {
|
|
2902
|
-
const c = this.target;
|
|
2903
|
-
try {
|
|
2904
|
-
this.color.setValue(c.color);
|
|
2905
|
-
} catch {
|
|
2906
|
-
this.color.setValue(16777215);
|
|
2907
|
-
}
|
|
2908
|
-
const [r, g, b] = this.color.toArray();
|
|
2909
|
-
return {
|
|
2910
|
-
// a kind or falloff nobody knows is the default the warning promises (the shader must never see an unknown id)
|
|
2911
|
-
kind: LIGHT_KINDS.includes(this.spec.kind) ? this.spec.kind : "point",
|
|
2912
|
-
x: c.x,
|
|
2913
|
-
y: c.y,
|
|
2914
|
-
z: c.z,
|
|
2915
|
-
lookAtX: c.lookAtX,
|
|
2916
|
-
lookAtY: c.lookAtY,
|
|
2917
|
-
lookAtZ: c.lookAtZ,
|
|
2918
|
-
r,
|
|
2919
|
-
g,
|
|
2920
|
-
b,
|
|
2921
|
-
intensity: Number.isFinite(c.intensity) ? Math.max(0, c.intensity) : 1,
|
|
2922
|
-
coneAngle: c.coneAngle,
|
|
2923
|
-
coneFeather: c.coneFeather,
|
|
2924
|
-
falloff: FALLOFFS.includes(this.spec.falloff) ? this.spec.falloff : "none",
|
|
2925
|
-
radius: c.radius,
|
|
2926
|
-
falloffDistance: c.falloffDistance,
|
|
2927
|
-
castsShadows: !!this.spec.castsShadows,
|
|
2928
|
-
shadowDarkness: Math.min(1, Math.max(0, c.shadowDarkness)),
|
|
2929
|
-
shadowDiffusion: Math.max(0, c.shadowDiffusion)
|
|
2930
|
-
};
|
|
2931
|
-
}
|
|
2932
|
-
window() {
|
|
2933
|
-
const start = this.absoluteStart ?? this.at;
|
|
2934
|
-
return { start, end: start + (this.duration ?? 0) };
|
|
2935
|
-
}
|
|
2936
|
-
};
|
|
2937
|
-
|
|
2938
|
-
// src/core/revertibleSet.ts
|
|
2939
|
-
function revertibleSet(timeline, at, read2, write2, value, property) {
|
|
2940
|
-
timeline.setValue(property?.[0] ?? read2, property?.[1] ?? "set", at, read2, write2, value);
|
|
2941
|
-
}
|
|
2942
|
-
|
|
2943
|
-
// src/expr/colorInterp.ts
|
|
2944
|
-
var CHROMA_FLOOR = 0.03;
|
|
2945
|
-
var CHROMA_FULL = 0.08;
|
|
2946
|
-
function buildColorInterp(from, to, space) {
|
|
2947
|
-
const fromRGBA = parseToRGBA(from);
|
|
2948
|
-
const toRGBA = parseToRGBA(to);
|
|
2949
|
-
const fromOk = rgbToOklab(fromRGBA);
|
|
2950
|
-
const toOk = rgbToOklab(toRGBA);
|
|
2951
|
-
if (space === "oklab") {
|
|
2952
|
-
return (t) => {
|
|
2953
|
-
const L = lerp(fromOk.L, toOk.L, t);
|
|
2954
|
-
const a = lerp(fromOk.a, toOk.a, t);
|
|
2955
|
-
const b = lerp(fromOk.b, toOk.b, t);
|
|
2956
|
-
const alpha = lerp(fromRGBA.a, toRGBA.a, t);
|
|
2957
|
-
return rgbaString(oklabToRgb({ L, a, b }), alpha);
|
|
2958
|
-
};
|
|
2959
|
-
}
|
|
2960
|
-
const fromLCH = oklabToOklch(fromOk);
|
|
2961
|
-
const toLCH = oklabToOklch(toOk);
|
|
2962
|
-
const fromHue = fromLCH.h, toHue = toLCH.h;
|
|
2963
|
-
fromLCH.h = borrowHue(fromHue, fromLCH.C, toHue);
|
|
2964
|
-
toLCH.h = borrowHue(toHue, toLCH.C, fromHue);
|
|
2965
|
-
const hueDelta = shortestHueDelta(fromLCH.h, toLCH.h);
|
|
2966
|
-
return (t) => {
|
|
2967
|
-
const L = lerp(fromLCH.L, toLCH.L, t);
|
|
2968
|
-
const C = lerp(fromLCH.C, toLCH.C, t);
|
|
2969
|
-
const h = fromLCH.h + hueDelta * t;
|
|
2970
|
-
const alpha = lerp(fromRGBA.a, toRGBA.a, t);
|
|
2971
|
-
const ab = oklchToOklab({ L, C, h });
|
|
2972
|
-
return rgbaString(oklabToRgb(ab), alpha);
|
|
2973
|
-
};
|
|
2974
|
-
}
|
|
2975
|
-
function borrowHue(own, chroma, other) {
|
|
2976
|
-
const u = Math.min(1, Math.max(0, (chroma - CHROMA_FLOOR) / (CHROMA_FULL - CHROMA_FLOOR)));
|
|
2977
|
-
const keep = u * u * (3 - 2 * u);
|
|
2978
|
-
return own + shortestHueDelta(own, other) * (1 - keep);
|
|
2979
|
-
}
|
|
2980
|
-
function parseToRGBA(input) {
|
|
2981
|
-
if (typeof input === "number") {
|
|
2982
|
-
return { r: (input >> 16 & 255) / 255, g: (input >> 8 & 255) / 255, b: (input & 255) / 255, a: 1 };
|
|
2983
|
-
}
|
|
2984
|
-
const s = input.trim();
|
|
2985
|
-
if (s.startsWith("#")) return parseHex(s);
|
|
2986
|
-
if (s.startsWith("rgb")) return parseRgbFunc(s);
|
|
2987
|
-
return { r: 0, g: 0, b: 0, a: 1 };
|
|
2988
|
-
}
|
|
2989
|
-
function parseHex(s) {
|
|
2990
|
-
const hex = s.slice(1);
|
|
2991
|
-
let r = 0, g = 0, b = 0, a = 1;
|
|
2992
|
-
if (hex.length === 3 || hex.length === 4) {
|
|
2993
|
-
r = parseInt(hex[0] + hex[0], 16) / 255;
|
|
2994
|
-
g = parseInt(hex[1] + hex[1], 16) / 255;
|
|
2995
|
-
b = parseInt(hex[2] + hex[2], 16) / 255;
|
|
2996
|
-
if (hex.length === 4) a = parseInt(hex[3] + hex[3], 16) / 255;
|
|
2997
|
-
} else if (hex.length === 6 || hex.length === 8) {
|
|
2998
|
-
r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
2999
|
-
g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
3000
|
-
b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
3001
|
-
if (hex.length === 8) a = parseInt(hex.slice(6, 8), 16) / 255;
|
|
3002
|
-
}
|
|
3003
|
-
return { r, g, b, a };
|
|
3004
|
-
}
|
|
3005
|
-
function parseRgbFunc(s) {
|
|
3006
|
-
const inner = s.replace(/^rgba?\(/, "").replace(/\)$/, "");
|
|
3007
|
-
const parts = inner.split(/[,\s/]+/).filter(Boolean).map(parseFloat);
|
|
3008
|
-
return {
|
|
3009
|
-
r: (parts[0] ?? 0) / 255,
|
|
3010
|
-
g: (parts[1] ?? 0) / 255,
|
|
3011
|
-
b: (parts[2] ?? 0) / 255,
|
|
3012
|
-
a: parts.length > 3 ? parts[3] ?? 1 : 1
|
|
3013
|
-
};
|
|
3014
|
-
}
|
|
3015
|
-
function rgbaString(rgb, alpha) {
|
|
3016
|
-
const r = Math.round(clamp01(rgb.r) * 255);
|
|
3017
|
-
const g = Math.round(clamp01(rgb.g) * 255);
|
|
3018
|
-
const b = Math.round(clamp01(rgb.b) * 255);
|
|
3019
|
-
return `rgba(${r},${g},${b},${alpha})`;
|
|
3020
|
-
}
|
|
3021
|
-
function srgbToLinear(c) {
|
|
3022
|
-
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
3023
|
-
}
|
|
3024
|
-
function linearToSrgb(c) {
|
|
3025
|
-
return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
3026
|
-
}
|
|
3027
|
-
function rgbToOklab(rgba) {
|
|
3028
|
-
const r = srgbToLinear(rgba.r);
|
|
3029
|
-
const g = srgbToLinear(rgba.g);
|
|
3030
|
-
const b = srgbToLinear(rgba.b);
|
|
3031
|
-
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
3032
|
-
const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
3033
|
-
const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
3034
|
-
const l_ = Math.cbrt(l);
|
|
3035
|
-
const m_ = Math.cbrt(m);
|
|
3036
|
-
const s_ = Math.cbrt(s);
|
|
3037
|
-
return {
|
|
3038
|
-
L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
|
|
3039
|
-
a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
|
|
3040
|
-
b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_
|
|
3041
|
-
};
|
|
3042
|
-
}
|
|
3043
|
-
function oklabToRgb(lab) {
|
|
3044
|
-
const l_ = lab.L + 0.3963377774 * lab.a + 0.2158037573 * lab.b;
|
|
3045
|
-
const m_ = lab.L - 0.1055613458 * lab.a - 0.0638541728 * lab.b;
|
|
3046
|
-
const s_ = lab.L - 0.0894841775 * lab.a - 1.291485548 * lab.b;
|
|
3047
|
-
const l = l_ * l_ * l_;
|
|
3048
|
-
const m = m_ * m_ * m_;
|
|
3049
|
-
const s = s_ * s_ * s_;
|
|
3050
|
-
return {
|
|
3051
|
-
r: linearToSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
|
|
3052
|
-
g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
|
|
3053
|
-
b: linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)
|
|
3054
|
-
};
|
|
3055
|
-
}
|
|
3056
|
-
function oklabToOklch(lab) {
|
|
3057
|
-
return { L: lab.L, C: Math.hypot(lab.a, lab.b), h: Math.atan2(lab.b, lab.a) };
|
|
3058
|
-
}
|
|
3059
|
-
function oklchToOklab(lch) {
|
|
3060
|
-
return { L: lch.L, a: lch.C * Math.cos(lch.h), b: lch.C * Math.sin(lch.h) };
|
|
3061
|
-
}
|
|
3062
|
-
function lerp(a, b, t) {
|
|
3063
|
-
return a + (b - a) * t;
|
|
3064
|
-
}
|
|
3065
|
-
function clamp01(v) {
|
|
3066
|
-
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
3067
|
-
}
|
|
3068
|
-
function shortestHueDelta(from, to) {
|
|
3069
|
-
const TWO_PI = Math.PI * 2;
|
|
3070
|
-
let d = (to - from) % TWO_PI;
|
|
3071
|
-
if (d > Math.PI) d -= TWO_PI;
|
|
3072
|
-
if (d < -Math.PI) d += TWO_PI;
|
|
3073
|
-
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);
|
|
3074
2776
|
}
|
|
3075
2777
|
|
|
3076
2778
|
// src/expr/colorTween.ts
|
|
@@ -3098,557 +2800,1058 @@ function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, a
|
|
|
3098
2800
|
});
|
|
3099
2801
|
}
|
|
3100
2802
|
|
|
3101
|
-
// src/
|
|
3102
|
-
var
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
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;
|
|
3114
2855
|
}
|
|
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
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
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];
|
|
3157
2908
|
}
|
|
2909
|
+
this.intrinsicWidth = text.width;
|
|
2910
|
+
this.intrinsicHeight = text.height;
|
|
3158
2911
|
}
|
|
3159
|
-
this.
|
|
3160
|
-
const
|
|
3161
|
-
if (
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
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));
|
|
3165
2949
|
}
|
|
3166
2950
|
};
|
|
3167
|
-
function
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
changed = true;
|
|
3175
|
-
continue;
|
|
3176
|
-
}
|
|
3177
|
-
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;
|
|
3178
2958
|
}
|
|
3179
|
-
return changed ? out : props;
|
|
3180
2959
|
}
|
|
3181
|
-
function
|
|
2960
|
+
function stripKeys(spec, keys) {
|
|
3182
2961
|
return {
|
|
3183
|
-
...
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
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
|
|
3187
2970
|
};
|
|
3188
2971
|
}
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
const stops = g.stops ?? [];
|
|
3196
|
-
if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
|
|
3197
|
-
return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
|
|
3198
|
-
}
|
|
3199
|
-
function gradientOptions(g) {
|
|
3200
|
-
const colorStops = normalizedStops(g);
|
|
3201
|
-
const a = (g.angle ?? 90) * Math.PI / 180;
|
|
3202
|
-
const dx = Math.cos(a), dy = Math.sin(a);
|
|
3203
|
-
const h = (Math.abs(dx) + Math.abs(dy)) / 2;
|
|
3204
|
-
return {
|
|
3205
|
-
type: "linear",
|
|
3206
|
-
start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
|
|
3207
|
-
end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
|
|
3208
|
-
colorStops,
|
|
3209
|
-
textureSpace: "local"
|
|
3210
|
-
};
|
|
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;
|
|
3211
2978
|
}
|
|
3212
|
-
function
|
|
3213
|
-
|
|
3214
|
-
const
|
|
3215
|
-
|
|
3216
|
-
for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
|
|
3217
|
-
ctx.fillStyle = grad;
|
|
3218
|
-
ctx.fillRect(0, 0, size, size);
|
|
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;
|
|
3219
2983
|
}
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
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
|
+
}
|
|
3013
|
+
}
|
|
3232
3014
|
}
|
|
3233
|
-
function
|
|
3234
|
-
const
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
if (next.length === base.stops.length) out.stops = next;
|
|
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
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
const t = kf.set?.text;
|
|
3023
|
+
if (t === void 0) continue;
|
|
3024
|
+
revertibleSet(timeline, origin + resolveAt(kf.at, parentDuration), read2, write2, String(t));
|
|
3244
3025
|
}
|
|
3245
|
-
return out;
|
|
3246
3026
|
}
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
}
|
|
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;
|
|
3255
3034
|
};
|
|
3256
|
-
const
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
const
|
|
3261
|
-
const
|
|
3262
|
-
|
|
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
|
+
);
|
|
3062
|
+
}
|
|
3263
3063
|
}
|
|
3064
|
+
}
|
|
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;
|
|
3070
|
+
}
|
|
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 = [];
|
|
3264
3081
|
for (const kf of spec.keyframes ?? []) {
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
}
|
|
3274
|
-
const p = value;
|
|
3275
|
-
for (const k of Object.keys(p)) {
|
|
3276
|
-
if (PATCH_KEYS.includes(k)) continue;
|
|
3277
|
-
const guess = suggestName(k, PATCH_KEYS.filter((x) => x !== "type"));
|
|
3278
|
-
say(`fillGradient.${k} is not a gradient property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${PATCH_KEYS.join(", ")}); it is ignored`);
|
|
3279
|
-
}
|
|
3280
|
-
if (p.type !== void 0 && start && p.type !== start.type) say("fillGradient.type cannot change over time (linear <-> radial); it is ignored");
|
|
3281
|
-
if (p.stops !== void 0 && start) {
|
|
3282
|
-
const n = Array.isArray(p.stops) ? p.stops.length : 0;
|
|
3283
|
-
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`);
|
|
3284
|
-
}
|
|
3285
|
-
} else if (/^fillGradient\./i.test(key) || /^gradient/i.test(key)) {
|
|
3286
|
-
const tail = key.replace(/^fillGradient\./i, "").replace(/^gradient/i, "");
|
|
3287
|
-
const sub2 = tail ? tail[0].toLowerCase() + tail.slice(1) : "angle";
|
|
3288
|
-
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`);
|
|
3289
|
-
}
|
|
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;
|
|
3290
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];
|
|
3291
3100
|
}
|
|
3101
|
+
if (touched) timeKfs.push(t);
|
|
3102
|
+
if (!touched || left) restKfs.push(r);
|
|
3292
3103
|
}
|
|
3293
|
-
|
|
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;
|
|
3110
|
+
}
|
|
3111
|
+
return { timeKfs, restKfs: restKfs.length ? restKfs : void 0, restInitial, start, speed };
|
|
3294
3112
|
}
|
|
3295
|
-
function
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
from: fromPatch !== void 0 ? (prev) => mergeGrad(prev, fromPatch) : void 0,
|
|
3305
|
-
to: (start, prev) => toPatch !== void 0 ? mergeGrad(start, toPatch) : prev,
|
|
3306
|
-
make: (a, b) => {
|
|
3307
|
-
const colors = a.stops.map((s, i) => colorSpace === "rgb" ? interpolateColors(s.color, b.stops[i].color) : buildColorInterp(s.color, b.stops[i].color, colorSpace));
|
|
3308
|
-
return (t) => {
|
|
3309
|
-
const l = (x, y) => x + (y - x) * t;
|
|
3310
|
-
return {
|
|
3311
|
-
type: a.type,
|
|
3312
|
-
angle: l(a.angle, b.angle),
|
|
3313
|
-
center: [l(a.center[0], b.center[0]), l(a.center[1], b.center[1])],
|
|
3314
|
-
innerRadius: l(a.innerRadius, b.innerRadius),
|
|
3315
|
-
radius: l(a.radius, b.radius),
|
|
3316
|
-
stops: a.stops.map((s, i) => ({ offset: l(s.offset, b.stops[i].offset), color: colors[i](t) }))
|
|
3317
|
-
};
|
|
3318
|
-
};
|
|
3319
|
-
},
|
|
3320
|
-
duration,
|
|
3321
|
-
ease,
|
|
3322
|
-
at,
|
|
3323
|
-
repeat: loop.repeat,
|
|
3324
|
-
yoyo: loop.yoyo,
|
|
3325
|
-
repeatDelay: loop.repeatDelay
|
|
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]) };
|
|
3120
|
+
}
|
|
3121
|
+
return out;
|
|
3326
3122
|
});
|
|
3123
|
+
return { ...remap, timeKfs, start: remap.start === void 0 ? void 0 : fix(remap.start) };
|
|
3327
3124
|
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
paint(g) {
|
|
3342
|
-
const ctx = this.canvas.getContext("2d"), s = this.size;
|
|
3343
|
-
if (!ctx) return new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((x) => [x.offset, x.color]) }));
|
|
3344
|
-
ctx.clearRect(0, 0, s, s);
|
|
3345
|
-
if (g.type === "radial") {
|
|
3346
|
-
paintRadial(ctx, s, { stops: g.stops.map((x) => [x.offset, x.color]), center: g.center, innerRadius: g.innerRadius, radius: g.radius });
|
|
3347
|
-
} else {
|
|
3348
|
-
const a = g.angle * Math.PI / 180, dx = Math.cos(a), dy = Math.sin(a);
|
|
3349
|
-
const h = (Math.abs(dx) + Math.abs(dy)) / 2;
|
|
3350
|
-
const grad = ctx.createLinearGradient((0.5 - dx * h) * s, (0.5 - dy * h) * s, (0.5 + dx * h) * s, (0.5 + dy * h) * s);
|
|
3351
|
-
for (const st of g.stops) grad.addColorStop(Math.min(1, Math.max(0, st.offset)), st.color);
|
|
3352
|
-
ctx.fillStyle = grad;
|
|
3353
|
-
ctx.fillRect(0, 0, s, s);
|
|
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);
|
|
3354
3138
|
}
|
|
3355
|
-
this.tex.source.update();
|
|
3356
|
-
return { texture: this.tex, textureSpace: "local" };
|
|
3357
3139
|
}
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
function bindGradientKeyframes(timeline, holder, keyframes, duration, offset, colorSpace, onChange) {
|
|
3366
|
-
const bag = (b) => b?.fillGradient;
|
|
3367
|
-
for (const kf of keyframes) {
|
|
3368
|
-
const at = offset + resolveAt(kf.at, duration);
|
|
3369
|
-
const setV = bag(kf.set), fromV = bag(kf.from), toV = bag(kf.to);
|
|
3370
|
-
if (setV !== void 0) {
|
|
3371
|
-
timeline.tweenValue({
|
|
3372
|
-
holder,
|
|
3373
|
-
id: "grad",
|
|
3374
|
-
get: () => holder.grad,
|
|
3375
|
-
set: (g) => {
|
|
3376
|
-
holder.grad = g;
|
|
3377
|
-
onChange();
|
|
3378
|
-
},
|
|
3379
|
-
to: (start) => mergeGrad(start, setV),
|
|
3380
|
-
make: (_a, b) => () => b,
|
|
3381
|
-
duration: 0,
|
|
3382
|
-
ease: "none",
|
|
3383
|
-
at
|
|
3384
|
-
});
|
|
3385
|
-
}
|
|
3386
|
-
if (fromV !== void 0 || toV !== void 0) {
|
|
3387
|
-
tweenGradient(timeline, holder, fromV, toV, kfDuration(kf), kf.ease ?? "none", at, colorSpace, onChange, loopVars(kf));
|
|
3388
|
-
}
|
|
3140
|
+
return m;
|
|
3141
|
+
}
|
|
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;
|
|
3389
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);
|
|
3390
3151
|
}
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
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
|
+
}
|
|
3171
|
+
}
|
|
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
|
+
};
|
|
3419
3182
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
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;
|
|
3437
3218
|
}
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
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;
|
|
3442
3232
|
}
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
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);
|
|
3446
3291
|
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
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
|
+
}
|
|
3454
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();
|
|
3455
3374
|
async build() {
|
|
3456
|
-
const
|
|
3457
|
-
|
|
3458
|
-
fontSize: 36,
|
|
3459
|
-
fill: "#ffffff",
|
|
3460
|
-
align: "center"
|
|
3461
|
-
};
|
|
3462
|
-
const initialProps = this.spec.initial ?? {};
|
|
3463
|
-
this._template = String(initialProps.text ?? this.spec.text ?? "");
|
|
3464
|
-
const text = new Text({
|
|
3465
|
-
text: this._template,
|
|
3466
|
-
style: baseStyle,
|
|
3467
|
-
label: this.spec.name
|
|
3468
|
-
});
|
|
3469
|
-
text.cullable = true;
|
|
3470
|
-
this.target = text;
|
|
3471
|
-
this.intrinsicWidth = text.width;
|
|
3472
|
-
this.intrinsicHeight = text.height;
|
|
3375
|
+
const compW = this.parent?.width ?? this.root.width;
|
|
3376
|
+
this.compH = this.parent?.height ?? this.root.height;
|
|
3473
3377
|
if (this.duration === void 0) {
|
|
3474
3378
|
this.duration = this.parent?.duration ?? this.root.duration;
|
|
3475
3379
|
}
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
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)`);
|
|
3493
3410
|
}
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
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`);
|
|
3499
3429
|
}
|
|
3500
|
-
const
|
|
3501
|
-
|
|
3502
|
-
this.
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
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);
|
|
3510
3593
|
}
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
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
|
+
};
|
|
3514
3617
|
}
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars", "fillGradient"]);
|
|
3519
|
-
runSuperWithStrippedSpec(this, stripped, timeline, offset);
|
|
3520
|
-
const keyframes = this.spec.keyframes ?? [];
|
|
3521
|
-
const origin = offset + this.at;
|
|
3522
|
-
bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
|
|
3523
|
-
if (this._grad) bindGradientKeyframes(timeline, this._grad, keyframes, this.duration, origin, colorSpace, () => this._applyGradient());
|
|
3524
|
-
const refresh = () => this._refreshText();
|
|
3525
|
-
bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
|
|
3526
|
-
bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
|
|
3527
|
-
bindTextKeyframes(timeline, keyframes, this.duration, origin, () => this._template, (t) => {
|
|
3528
|
-
this._template = t;
|
|
3529
|
-
refresh();
|
|
3530
|
-
}, describeLayer(this.spec));
|
|
3618
|
+
window() {
|
|
3619
|
+
const start = this.absoluteStart ?? this.at;
|
|
3620
|
+
return { start, end: start + (this.duration ?? 0) };
|
|
3531
3621
|
}
|
|
3532
3622
|
};
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
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 [];
|
|
3540
3632
|
}
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
...kf,
|
|
3548
|
-
set: stripProps(kf.set, keys),
|
|
3549
|
-
to: stripProps(kf.to, keys),
|
|
3550
|
-
from: stripProps(kf.from, keys)
|
|
3551
|
-
})) : void 0
|
|
3552
|
-
};
|
|
3553
|
-
}
|
|
3554
|
-
function stripProps(props, keys) {
|
|
3555
|
-
if (!props) return props;
|
|
3556
|
-
if (!keys.some((k) => k in props)) return props;
|
|
3557
|
-
const out = {};
|
|
3558
|
-
for (const k of Object.keys(props)) if (!keys.includes(k)) out[k] = props[k];
|
|
3559
|
-
return out;
|
|
3560
|
-
}
|
|
3561
|
-
function resolveNumber(v, scope) {
|
|
3562
|
-
if (typeof v === "number") return v;
|
|
3563
|
-
const out = normalizeProps({ v }, scope).v;
|
|
3564
|
-
return typeof out === "number" ? out : Number(out) || 0;
|
|
3565
|
-
}
|
|
3566
|
-
function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDuration, origin, scope) {
|
|
3567
|
-
for (const kf of keyframes) {
|
|
3568
|
-
const setV = kf.set?.[key];
|
|
3569
|
-
const fromV = kf.from?.[key];
|
|
3570
|
-
const toV = kf.to?.[key];
|
|
3571
|
-
if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
|
|
3572
|
-
const at = origin + resolveAt(kf.at, parentDuration);
|
|
3573
|
-
const duration = kfDuration(kf);
|
|
3574
|
-
const ease = kf.ease ?? "none";
|
|
3575
|
-
const loop = loopVars(kf);
|
|
3576
|
-
if (setV !== void 0) {
|
|
3577
|
-
const v = resolveNumber(setV, scope);
|
|
3578
|
-
revertibleSet(timeline, at, () => state[key], (n) => {
|
|
3579
|
-
state[key] = n;
|
|
3580
|
-
refresh();
|
|
3581
|
-
}, 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;
|
|
3582
3639
|
}
|
|
3583
|
-
if (
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
{ [key]: resolveNumber(fromV, scope) },
|
|
3587
|
-
{ [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
|
|
3588
|
-
at
|
|
3589
|
-
);
|
|
3590
|
-
} else if (toV !== void 0) {
|
|
3591
|
-
timeline.to(state, { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
|
|
3592
|
-
} else if (fromV !== void 0) {
|
|
3593
|
-
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;
|
|
3594
3643
|
}
|
|
3595
|
-
|
|
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;
|
|
3596
3659
|
}
|
|
3597
|
-
function
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
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;
|
|
3602
3706
|
}
|
|
3603
3707
|
}
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
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;
|
|
3607
3713
|
}
|
|
3714
|
+
return out;
|
|
3608
3715
|
}
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
const
|
|
3614
|
-
|
|
3615
|
-
|
|
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;
|
|
3616
3736
|
};
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
if (
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
}
|
|
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;
|
|
3629
3779
|
}
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
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;
|
|
3644
3843
|
}
|
|
3844
|
+
out[k] = props[k];
|
|
3645
3845
|
}
|
|
3846
|
+
return changed ? out : props;
|
|
3646
3847
|
}
|
|
3647
|
-
function
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
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
|
+
};
|
|
3652
3855
|
}
|
|
3653
3856
|
var AudioSequence = class extends Sequence {
|
|
3654
3857
|
_audioBuffer = null;
|
|
@@ -5403,10 +5606,15 @@ function resolveDim(v, scope) {
|
|
|
5403
5606
|
var _CompositionSequence = null;
|
|
5404
5607
|
async function getCompositionSequence() {
|
|
5405
5608
|
if (!_CompositionSequence) {
|
|
5406
|
-
_CompositionSequence = (await import('./Composition-
|
|
5609
|
+
_CompositionSequence = (await import('./Composition-RF7QOEXT.js')).CompositionSequence;
|
|
5407
5610
|
}
|
|
5408
5611
|
return _CompositionSequence;
|
|
5409
5612
|
}
|
|
5613
|
+
var _SpansSequence = null;
|
|
5614
|
+
async function getSpansSequence() {
|
|
5615
|
+
if (!_SpansSequence) _SpansSequence = (await import('./Spans-SDXABOV6.js')).SpansSequence;
|
|
5616
|
+
return _SpansSequence;
|
|
5617
|
+
}
|
|
5410
5618
|
var staticTypes = {
|
|
5411
5619
|
image: ImageSequence,
|
|
5412
5620
|
text: TextSequence,
|
|
@@ -5424,8 +5632,12 @@ function registerSequenceType(type, ctor) {
|
|
|
5424
5632
|
}
|
|
5425
5633
|
async function buildSequenceTree(specs, parent, root) {
|
|
5426
5634
|
const out = [];
|
|
5427
|
-
for (
|
|
5635
|
+
for (let spec of specs) {
|
|
5428
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
|
+
}
|
|
5429
5641
|
if (!Cls && spec.type === "composition") {
|
|
5430
5642
|
Cls = await getCompositionSequence();
|
|
5431
5643
|
}
|
|
@@ -5445,6 +5657,6 @@ async function buildSequenceTree(specs, parent, root) {
|
|
|
5445
5657
|
return out;
|
|
5446
5658
|
}
|
|
5447
5659
|
|
|
5448
|
-
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, 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 };
|
|
5449
|
-
//# sourceMappingURL=chunk-
|
|
5450
|
-
//# 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
|