pixi-effects 0.9.0 → 0.11.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 +36 -0
- package/README.md +6 -6
- package/ai/SKILL.md +4 -3
- package/ai/reference/cheatsheet.md +17 -6
- package/ai/reference/pitfalls.md +3 -3
- package/ai/reference/recipes.md +141 -11
- package/ai/template.html +11 -8
- package/ai/tools/render.mjs +14 -5
- package/dist/{Base-CSIulyIq.d.cts → Base-CwUxTfja.d.cts} +1 -1
- package/dist/{Base-DVgwyIUj.d.ts → Base-CyvSLoCP.d.ts} +1 -1
- package/dist/Composition-7TEKHZNQ.cjs +14 -0
- package/dist/{Composition-CM62XJKT.cjs.map → Composition-7TEKHZNQ.cjs.map} +1 -1
- package/dist/Composition-HIISJAX2.js +5 -0
- package/dist/{Composition-RJDSQYTV.js.map → Composition-HIISJAX2.js.map} +1 -1
- package/dist/Controller.cjs +34 -0
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +12 -2
- package/dist/Controller.d.ts +12 -2
- package/dist/Controller.js +34 -0
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-qGHp15c_.d.cts → Movie-DSSNOF3T.d.cts} +61 -2
- package/dist/{Movie-QMlFyHkf.d.ts → Movie-nzmWsxWX.d.ts} +61 -2
- package/dist/{chunk-O4AJYUPK.js → chunk-3IPDNC3E.js} +90 -14
- package/dist/chunk-3IPDNC3E.js.map +1 -0
- package/dist/{chunk-AGMNSJJH.cjs → chunk-6NWJLVCA.cjs} +196 -12
- package/dist/chunk-6NWJLVCA.cjs.map +1 -0
- package/dist/{chunk-FNKSXBKB.js → chunk-C6QHKYC5.js} +193 -14
- package/dist/chunk-C6QHKYC5.js.map +1 -0
- package/dist/{chunk-PDBDDGG5.cjs → chunk-F7NCHS6Q.cjs} +108 -32
- package/dist/chunk-F7NCHS6Q.cjs.map +1 -0
- package/dist/index.cjs +542 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +303 -5
- package/dist/index.d.ts +303 -5
- package/dist/index.js +511 -27
- package/dist/index.js.map +1 -1
- package/dist/loader.css +110 -0
- package/dist/three.cjs +5 -5
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +1 -1
- package/dist/{types-oJijbn-K.d.ts → types-BXu28nJj.d.cts} +26 -2
- package/dist/{types-oJijbn-K.d.cts → types-BXu28nJj.d.ts} +26 -2
- package/docs/api.md +16 -1
- package/docs/dsl.md +137 -2
- package/llms-full.txt +318 -26
- package/package.json +5 -2
- package/dist/Composition-CM62XJKT.cjs +0 -14
- package/dist/Composition-RJDSQYTV.js +0 -5
- package/dist/chunk-AGMNSJJH.cjs.map +0 -1
- package/dist/chunk-FNKSXBKB.js.map +0 -1
- package/dist/chunk-O4AJYUPK.js.map +0 -1
- package/dist/chunk-PDBDDGG5.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkF7NCHS6Q_cjs = require('./chunk-F7NCHS6Q.cjs');
|
|
4
|
+
var chunk6NWJLVCA_cjs = require('./chunk-6NWJLVCA.cjs');
|
|
5
5
|
var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
|
|
6
6
|
var PIXI = require('pixi.js');
|
|
7
7
|
var gsap = require('gsap');
|
|
@@ -296,6 +296,24 @@ function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
|
|
|
296
296
|
issues
|
|
297
297
|
};
|
|
298
298
|
}
|
|
299
|
+
|
|
300
|
+
// src/core/motionBlur.ts
|
|
301
|
+
var DEFAULTS = { samples: 8, shutter: 0.5 };
|
|
302
|
+
function resolveMotionBlur(spec, where) {
|
|
303
|
+
if (spec === void 0 || spec === null || spec === false) return null;
|
|
304
|
+
const o = spec === true ? {} : typeof spec === "number" ? { samples: spec } : spec;
|
|
305
|
+
if (typeof spec === "object") chunkUQBGW5DO_cjs.warnUnknownOptions(`${where} motionBlur`, o, ["samples", "shutter"]);
|
|
306
|
+
const samples = o.samples ?? DEFAULTS.samples, shutter = o.shutter ?? DEFAULTS.shutter;
|
|
307
|
+
if (!(Number.isInteger(samples) && samples >= 2 && samples <= 64)) throw new Error(`pixi-effects: ${where}: motionBlur samples must be a whole number from 2 to 64, got ${samples}`);
|
|
308
|
+
if (!(Number.isFinite(shutter) && shutter > 0 && shutter <= 1)) throw new Error(`pixi-effects: ${where}: motionBlur shutter must be a fraction of the frame time above 0 and up to 1 (0.5 is a 180\xB0 shutter), got ${shutter}`);
|
|
309
|
+
return { samples, shutter };
|
|
310
|
+
}
|
|
311
|
+
function blurTimes(frame, frameRate, mb, duration) {
|
|
312
|
+
const t = frame / frameRate, width = mb.shutter / frameRate;
|
|
313
|
+
return Array.from({ length: mb.samples }, (_, k) => Math.min(Math.max(t + ((k + 0.5) / mb.samples - 0.5) * width, 0), duration));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/core/Renderer.ts
|
|
299
317
|
var VIDEO_CODEC_BY_FORMAT = {
|
|
300
318
|
mp4: "avc",
|
|
301
319
|
mov: "avc",
|
|
@@ -368,7 +386,10 @@ async function exportFrames(movie, options = {}) {
|
|
|
368
386
|
format: container,
|
|
369
387
|
target: new mediabunny.BufferTarget()
|
|
370
388
|
});
|
|
371
|
-
const
|
|
389
|
+
const mb = options.motionBlur !== void 0 ? resolveMotionBlur(options.motionBlur, "movie.render()") : movie.motionBlur ?? null;
|
|
390
|
+
const stage = movie.app.canvas;
|
|
391
|
+
const blurCanvas = mb ? Object.assign(document.createElement("canvas"), { width: stage.width, height: stage.height }) : null;
|
|
392
|
+
const canvasSource = new mediabunny.CanvasSource(blurCanvas ?? stage, {
|
|
372
393
|
codec: opts.video.codec,
|
|
373
394
|
bitrate: opts.video.bitrate
|
|
374
395
|
});
|
|
@@ -389,7 +410,8 @@ async function exportFrames(movie, options = {}) {
|
|
|
389
410
|
const keyframeIntervalFrames = Math.max(1, Math.round(2 * movie.frameRate));
|
|
390
411
|
try {
|
|
391
412
|
for (let frame = 0; frame <= movie.totalFrames; frame++) {
|
|
392
|
-
await movie.
|
|
413
|
+
if (mb && blurCanvas) await movie._exposeFrame(frame, mb, blurCanvas, false);
|
|
414
|
+
else await movie.gotoFrame(frame, true);
|
|
393
415
|
const isKey = frame === 0 || frame % keyframeIntervalFrames === 0;
|
|
394
416
|
const addOpts = isKey ? { keyFrame: true } : void 0;
|
|
395
417
|
await canvasSource.add(frame / movie.frameRate, 1 / movie.frameRate, addOpts);
|
|
@@ -398,6 +420,7 @@ async function exportFrames(movie, options = {}) {
|
|
|
398
420
|
}
|
|
399
421
|
await canvasSource.close();
|
|
400
422
|
await output.finalize();
|
|
423
|
+
if (mb) await movie.gotoFrame(movie.totalFrames, true);
|
|
401
424
|
return new Blob([output.target.buffer], { type: output.format.mimeType });
|
|
402
425
|
} finally {
|
|
403
426
|
movie.app.ticker.start();
|
|
@@ -843,16 +866,16 @@ function expandTransitions(spec) {
|
|
|
843
866
|
if (toUses > 1) {
|
|
844
867
|
throw new Error(`pixi-effects: ${tag} sequence "${t.to}" is used as \`to\` in more than one transition`);
|
|
845
868
|
}
|
|
846
|
-
const tStart =
|
|
869
|
+
const tStart = chunk6NWJLVCA_cjs.resolveAt(t.at, parentDuration);
|
|
847
870
|
const tEnd = tStart + t.duration;
|
|
848
871
|
const fromSeq = fromEntry.seq;
|
|
849
|
-
const fromStart =
|
|
872
|
+
const fromStart = chunk6NWJLVCA_cjs.resolveAt(fromSeq.at, parentDuration);
|
|
850
873
|
const fromEnd = fromStart + (fromSeq.duration ?? parentDuration);
|
|
851
874
|
if (tStart < fromStart || tEnd > fromEnd) {
|
|
852
875
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`from\` "${t.from}" (lives [${fromStart}, ${fromEnd}], so it ends at ${fromEnd})`);
|
|
853
876
|
}
|
|
854
877
|
const toSeq = toEntry.seq;
|
|
855
|
-
const toStart =
|
|
878
|
+
const toStart = chunk6NWJLVCA_cjs.resolveAt(toSeq.at, parentDuration);
|
|
856
879
|
const toEnd = toStart + (toSeq.duration ?? parentDuration);
|
|
857
880
|
if (tStart < toStart || tEnd > toEnd) {
|
|
858
881
|
throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`to\` "${t.to}" (lives [${toStart}, ${toEnd}], so it starts at ${toStart})`);
|
|
@@ -1173,12 +1196,12 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1173
1196
|
blending.add(w.to);
|
|
1174
1197
|
}
|
|
1175
1198
|
}
|
|
1176
|
-
comp.layers().forEach(({ seq, display, threeD }, i) => {
|
|
1199
|
+
comp.layers().forEach(({ seq, display, threeD, carriers }, i) => {
|
|
1177
1200
|
const t = seq.target;
|
|
1178
1201
|
const name = seq.spec.name;
|
|
1179
1202
|
const label = name ?? `${seq.spec.type}#${i}`;
|
|
1180
|
-
const alpha = t?.alpha ?? 1;
|
|
1181
|
-
const alive = threeD ? display.visible : t?.renderable ?? true;
|
|
1203
|
+
const alpha = (t?.alpha ?? 1) * carriers.reduce((a, c) => a * (c.target?.alpha ?? 1), 1);
|
|
1204
|
+
const alive = (threeD ? display.visible : t?.renderable ?? true) && carriers.every((c) => c.target?.renderable ?? true);
|
|
1182
1205
|
const visible = parentVisible && alive && alpha > 0;
|
|
1183
1206
|
let bounds = null;
|
|
1184
1207
|
if (!insideThreeD) {
|
|
@@ -1192,10 +1215,17 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1192
1215
|
onCanvas = inter === 0 && area > 0 ? "none" : inter >= area - 1e-6 ? "full" : "partial";
|
|
1193
1216
|
if (area === 0) onCanvas = "none";
|
|
1194
1217
|
}
|
|
1195
|
-
const keys =
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1218
|
+
const keys = chunk6NWJLVCA_cjs.collectPropKeys(seq.spec);
|
|
1219
|
+
const carried = carriers.some((c) => {
|
|
1220
|
+
const k = chunk6NWJLVCA_cjs.collectPropKeys(c.spec);
|
|
1221
|
+
return (c.spec.keyframes ?? []).length > 0 && (k.has("x") || k.has("y") || k.has("rotation") || k.has("scale"));
|
|
1222
|
+
});
|
|
1223
|
+
const moving = parentMoving || carried || (seq.spec.keyframes ?? []).length > 0 && (keys.has("x") || keys.has("y"));
|
|
1224
|
+
const zeroScale = (o) => {
|
|
1225
|
+
const s = o?.scale;
|
|
1226
|
+
return !!s && (s.x === 0 || s.y === 0);
|
|
1227
|
+
};
|
|
1228
|
+
const zero = parentZero || zeroScale(t) || carriers.some((c) => zeroScale(c.target));
|
|
1199
1229
|
const info = { path: prefix + label, name, type: seq.spec.type, threeD, visible, alpha, moving, bounds, onCanvas };
|
|
1200
1230
|
layers.push(info);
|
|
1201
1231
|
let shown = bounds ? clip(bounds, view) : null;
|
|
@@ -1208,7 +1238,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
|
|
|
1208
1238
|
const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
|
|
1209
1239
|
if (group) scene.set(info, group);
|
|
1210
1240
|
if (zero || seq.showsNothingYet) scaledToNothing.add(info);
|
|
1211
|
-
if (seq instanceof
|
|
1241
|
+
if (seq instanceof chunkF7NCHS6Q_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
|
|
1212
1242
|
});
|
|
1213
1243
|
};
|
|
1214
1244
|
walk2(root, "", false, true);
|
|
@@ -1268,10 +1298,10 @@ function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions) {
|
|
|
1268
1298
|
const start = parentStart + (seq.at ?? 0);
|
|
1269
1299
|
const end = Math.min(parentEnd, seq.duration !== void 0 ? start + seq.duration : parentEnd);
|
|
1270
1300
|
const span = end - start;
|
|
1271
|
-
const keys = (seq.spec.keyframes ?? []).map((kf) => round(start +
|
|
1301
|
+
const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunk6NWJLVCA_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
|
|
1272
1302
|
const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq) };
|
|
1273
1303
|
const inner = [];
|
|
1274
|
-
const isComp = seq instanceof
|
|
1304
|
+
const isComp = seq instanceof chunkF7NCHS6Q_cjs.CompositionSequence;
|
|
1275
1305
|
if (isComp) walk(seq, start, end, depth + 1, prefix + name + "/", inner, transitions);
|
|
1276
1306
|
entries.push({ row, inner, groupable: !isComp });
|
|
1277
1307
|
});
|
|
@@ -1328,7 +1358,8 @@ var COLORS = {
|
|
|
1328
1358
|
audio: "#ff7aa8",
|
|
1329
1359
|
composition: "#8ea4c8",
|
|
1330
1360
|
three: "#ff9f68",
|
|
1331
|
-
camera: "#b0b0b0"
|
|
1361
|
+
camera: "#b0b0b0",
|
|
1362
|
+
null: "#b0b0b0"
|
|
1332
1363
|
};
|
|
1333
1364
|
var esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1334
1365
|
var num = (n) => String(Math.round(n * 100) / 100);
|
|
@@ -1464,6 +1495,41 @@ function normalizePoster(poster, duration, frameRate) {
|
|
|
1464
1495
|
return { seconds, frame: Math.round(seconds * frameRate) };
|
|
1465
1496
|
}
|
|
1466
1497
|
|
|
1498
|
+
// src/core/loader.ts
|
|
1499
|
+
var FADE_FALLBACK_MS = 900;
|
|
1500
|
+
function resolveLoader(option, canvas) {
|
|
1501
|
+
if (option === false) return null;
|
|
1502
|
+
if (option instanceof HTMLElement) return option;
|
|
1503
|
+
if (typeof option === "string") return document.querySelector(option);
|
|
1504
|
+
let box = canvas?.parentElement ?? null;
|
|
1505
|
+
for (let level = 0; box && level < 4; level++, box = box.parentElement) {
|
|
1506
|
+
if (level > 0 && (box === document.body || box === document.documentElement)) break;
|
|
1507
|
+
const found = box.querySelector(".pe-loader");
|
|
1508
|
+
if (found) return found;
|
|
1509
|
+
}
|
|
1510
|
+
return null;
|
|
1511
|
+
}
|
|
1512
|
+
function dismissLoader(loader) {
|
|
1513
|
+
if (!loader || loader.classList.contains("pe-loader--done")) return;
|
|
1514
|
+
loader.classList.add("pe-loader--done");
|
|
1515
|
+
loader.setAttribute("aria-hidden", "true");
|
|
1516
|
+
let removed = false;
|
|
1517
|
+
const remove = () => {
|
|
1518
|
+
if (!removed) {
|
|
1519
|
+
removed = true;
|
|
1520
|
+
loader.remove();
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
loader.addEventListener("transitionend", remove, { once: true });
|
|
1524
|
+
setTimeout(remove, FADE_FALLBACK_MS);
|
|
1525
|
+
}
|
|
1526
|
+
function failLoader(loader, message) {
|
|
1527
|
+
if (!loader) return;
|
|
1528
|
+
loader.setAttribute("data-state", "error");
|
|
1529
|
+
if (loader.hasAttribute("data-label")) loader.setAttribute("data-label", message);
|
|
1530
|
+
loader.setAttribute("aria-busy", "false");
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1467
1533
|
// src/core/Movie.ts
|
|
1468
1534
|
PIXI.extensions.add(PIXI.CullerPlugin);
|
|
1469
1535
|
gsap.gsap.registerPlugin(PixiPlugin.PixiPlugin);
|
|
@@ -1479,6 +1545,8 @@ var Movie = class {
|
|
|
1479
1545
|
_cancelAudioStart = null;
|
|
1480
1546
|
/** The poster time in seconds (`movie.init({ poster })`), or null. */
|
|
1481
1547
|
poster = null;
|
|
1548
|
+
/** The motion blur `render()`, `snapshot()` and `contactSheet()` apply unless told otherwise (`movie.init({ motionBlur })`), or null. */
|
|
1549
|
+
motionBlur = null;
|
|
1482
1550
|
_posterFrame = null;
|
|
1483
1551
|
/** The canvas shows the poster picture while the playhead is still at frame 0 (like `<video poster>`): the first seek or play replaces it. */
|
|
1484
1552
|
_atPoster = false;
|
|
@@ -1535,7 +1603,7 @@ var Movie = class {
|
|
|
1535
1603
|
const back = this.currentFrame;
|
|
1536
1604
|
this.currentFrame = this._posterFrame;
|
|
1537
1605
|
try {
|
|
1538
|
-
this.timeline.time(this.
|
|
1606
|
+
this.timeline.time(this._timeOf(this._posterFrame));
|
|
1539
1607
|
await this._awaitVideoFrames();
|
|
1540
1608
|
this._updateSpace();
|
|
1541
1609
|
this._renderNow();
|
|
@@ -1563,7 +1631,7 @@ var Movie = class {
|
|
|
1563
1631
|
return this._audioContext;
|
|
1564
1632
|
}
|
|
1565
1633
|
async init(options = {}) {
|
|
1566
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.init()", options, ["width", "height", "duration", "frameRate", "background", "canvas", "assets", "composition", "poster"]);
|
|
1634
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.init()", options, ["width", "height", "duration", "frameRate", "background", "canvas", "assets", "composition", "poster", "motionBlur", "loader"]);
|
|
1567
1635
|
this._initState = "pending";
|
|
1568
1636
|
try {
|
|
1569
1637
|
this.width = options.width ?? 1920;
|
|
@@ -1573,6 +1641,7 @@ var Movie = class {
|
|
|
1573
1641
|
this.totalFrames = Math.round(this.duration * this.frameRate);
|
|
1574
1642
|
const poster = normalizePoster(options.poster, this.duration, this.frameRate);
|
|
1575
1643
|
this.poster = poster.seconds;
|
|
1644
|
+
this.motionBlur = resolveMotionBlur(options.motionBlur, "movie.init()");
|
|
1576
1645
|
this._posterFrame = poster.frame;
|
|
1577
1646
|
this.background = options.background ?? "#000000";
|
|
1578
1647
|
this.timeline = gsap.gsap.timeline({ paused: true, defaults: { ease: "none" } });
|
|
@@ -1610,7 +1679,7 @@ var Movie = class {
|
|
|
1610
1679
|
height: options.composition.height ?? this.height,
|
|
1611
1680
|
duration: options.composition.duration ?? this.duration
|
|
1612
1681
|
} : void 0;
|
|
1613
|
-
await
|
|
1682
|
+
await chunk6NWJLVCA_cjs.ensureFilterLibrary(seededComposition);
|
|
1614
1683
|
const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
|
|
1615
1684
|
const rootSeqSpec = {
|
|
1616
1685
|
type: "composition",
|
|
@@ -1620,7 +1689,7 @@ var Movie = class {
|
|
|
1620
1689
|
...userComposition
|
|
1621
1690
|
};
|
|
1622
1691
|
if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
|
|
1623
|
-
const composition = new
|
|
1692
|
+
const composition = new chunkF7NCHS6Q_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
|
|
1624
1693
|
await composition.build();
|
|
1625
1694
|
this._rootSequence = composition;
|
|
1626
1695
|
if (composition.target) root.addChild(composition.target);
|
|
@@ -1639,7 +1708,9 @@ var Movie = class {
|
|
|
1639
1708
|
this._initState = "ready";
|
|
1640
1709
|
if (this._posterFrame !== null) await this._showPoster();
|
|
1641
1710
|
this.emit("ready");
|
|
1711
|
+
dismissLoader(resolveLoader(options.loader, options.canvas ?? this.app?.canvas ?? null));
|
|
1642
1712
|
} catch (err) {
|
|
1713
|
+
failLoader(resolveLoader(options.loader, options.canvas ?? null), "COULD NOT LOAD");
|
|
1643
1714
|
try {
|
|
1644
1715
|
await this.destroy();
|
|
1645
1716
|
} catch (cleanupErr) {
|
|
@@ -1673,17 +1744,24 @@ var Movie = class {
|
|
|
1673
1744
|
async _goto(frame) {
|
|
1674
1745
|
this._atPoster = false;
|
|
1675
1746
|
this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames));
|
|
1676
|
-
this.timeline.time(this.
|
|
1747
|
+
this.timeline.time(this._timeOf(this.currentFrame));
|
|
1677
1748
|
await this._awaitVideoFrames();
|
|
1678
1749
|
this._updateSpace();
|
|
1679
1750
|
this._renderNow();
|
|
1680
1751
|
this.emit("frame", { frame: this.currentFrame, totalFrames: this.totalFrames });
|
|
1681
1752
|
}
|
|
1682
|
-
|
|
1753
|
+
/**
|
|
1754
|
+
* The moment frame `frame` shows. The very last frame (`totalFrames`) is at the end of the movie, where every layer's lifespan
|
|
1755
|
+
* has just closed: it is drawn a hair before, so a layer that lasts to the end is still on screen in the final frame
|
|
1756
|
+
* (it used to be an empty one, the last frame of every export).
|
|
1757
|
+
*/
|
|
1758
|
+
_timeOf(frame) {
|
|
1759
|
+
return Math.min(frame / this.frameRate, Math.max(0, this.duration - END_MARGIN));
|
|
1760
|
+
}
|
|
1761
|
+
async _awaitVideoFrames(t = this._timeOf(this.currentFrame)) {
|
|
1683
1762
|
if (!this._rootSequence) return;
|
|
1684
1763
|
const collected = [];
|
|
1685
1764
|
collectVideoSequences(this._rootSequence, collected);
|
|
1686
|
-
const t = this.currentFrame / this.frameRate;
|
|
1687
1765
|
await Promise.all(collected.map((v) => {
|
|
1688
1766
|
const local = t - (v.absoluteStart ?? v.at);
|
|
1689
1767
|
if (local < 0 || local > (v.duration ?? 0)) return Promise.resolve();
|
|
@@ -1691,19 +1769,27 @@ var Movie = class {
|
|
|
1691
1769
|
}));
|
|
1692
1770
|
}
|
|
1693
1771
|
async snapshot(frame = this.currentFrame, opts = {}) {
|
|
1694
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as"]);
|
|
1772
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as", "motionBlur"]);
|
|
1695
1773
|
this._requireReady("snapshot");
|
|
1696
|
-
|
|
1774
|
+
const mb = this._blurFor(opts.motionBlur, "movie.snapshot()");
|
|
1775
|
+
const f = clampFrame(frame, this.totalFrames);
|
|
1776
|
+
let source = this.app.canvas;
|
|
1777
|
+
if (mb) {
|
|
1778
|
+
source = this._blurCanvas();
|
|
1779
|
+
await this._quietly(() => this._exposeFrame(f, mb, source, true));
|
|
1780
|
+
} else await this._quietly(() => this.gotoFrame(f, true));
|
|
1697
1781
|
const scale = opts.scale ?? 1;
|
|
1698
1782
|
const out = document.createElement("canvas");
|
|
1699
1783
|
out.width = Math.max(1, Math.round(this.width * scale));
|
|
1700
1784
|
out.height = Math.max(1, Math.round(this.height * scale));
|
|
1701
|
-
out.getContext("2d").drawImage(
|
|
1785
|
+
out.getContext("2d").drawImage(source, 0, 0, out.width, out.height);
|
|
1702
1786
|
return encodeCanvas(out, opts.type ?? "image/png", opts.as ?? "blob");
|
|
1703
1787
|
}
|
|
1704
1788
|
async contactSheet(opts = {}) {
|
|
1705
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.contactSheet()", opts, ["frames", "times", "count", "columns", "cellWidth", "as"]);
|
|
1789
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.contactSheet()", opts, ["frames", "times", "count", "columns", "cellWidth", "as", "motionBlur"]);
|
|
1706
1790
|
this._requireReady("contactSheet");
|
|
1791
|
+
const mb = this._blurFor(opts.motionBlur, "movie.contactSheet()");
|
|
1792
|
+
const blurred = mb ? this._blurCanvas() : null;
|
|
1707
1793
|
const frames = (opts.frames ?? opts.times?.map((t) => Math.round(t * this.frameRate)) ?? pickFrames(this.totalFrames, opts.count ?? 6)).map((f) => clampFrame(f, this.totalFrames));
|
|
1708
1794
|
const LABEL_H = 26;
|
|
1709
1795
|
const L = sheetLayout(frames.length, opts.columns ?? 3, opts.cellWidth ?? 480, this.width, this.height, LABEL_H);
|
|
@@ -1717,9 +1803,10 @@ var Movie = class {
|
|
|
1717
1803
|
try {
|
|
1718
1804
|
for (let i = 0; i < frames.length; i++) {
|
|
1719
1805
|
const f = frames[i];
|
|
1720
|
-
await this._quietly(() => this.
|
|
1806
|
+
if (mb && blurred) await this._quietly(() => this._exposeFrame(f, mb, blurred, true));
|
|
1807
|
+
else await this._quietly(() => this.gotoFrame(f, true));
|
|
1721
1808
|
const { x, y } = L.positions[i];
|
|
1722
|
-
g.drawImage(this.app.canvas, x, y + LABEL_H, L.cellW, L.cellH);
|
|
1809
|
+
g.drawImage(blurred ?? this.app.canvas, x, y + LABEL_H, L.cellW, L.cellH);
|
|
1723
1810
|
g.fillStyle = "#e8ecf8";
|
|
1724
1811
|
g.font = "600 15px system-ui, sans-serif";
|
|
1725
1812
|
g.textBaseline = "middle";
|
|
@@ -1789,9 +1876,46 @@ var Movie = class {
|
|
|
1789
1876
|
}
|
|
1790
1877
|
}
|
|
1791
1878
|
/** Projects every `threeD` layer for the current frame (see src/space). */
|
|
1792
|
-
_updateSpace() {
|
|
1879
|
+
_updateSpace(t = this._timeOf(this.currentFrame)) {
|
|
1793
1880
|
if (!this._rootSequence || !this.app) return;
|
|
1794
|
-
this._rootSequence.updateSpace(
|
|
1881
|
+
this._rootSequence.updateSpace(t, this.app.renderer);
|
|
1882
|
+
}
|
|
1883
|
+
/** The motion blur an operation uses: its own option when given (`false` is off), otherwise the movie's. */
|
|
1884
|
+
_blurFor(option, where) {
|
|
1885
|
+
return option === void 0 ? this.motionBlur : resolveMotionBlur(option, where);
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* @internal Draw `frame` with motion blur into `target` (a canvas the size of the stage): the average of `mb.samples` renders at moments
|
|
1889
|
+
* spread over the shutter interval around the frame time. With `settle` the playhead and the stage are left exactly on the frame.
|
|
1890
|
+
*/
|
|
1891
|
+
async _exposeFrame(frame, mb, target, settle) {
|
|
1892
|
+
const g = target.getContext("2d");
|
|
1893
|
+
const stage = this.app.canvas;
|
|
1894
|
+
this._atPoster = false;
|
|
1895
|
+
const times = blurTimes(frame, this.frameRate, mb, Math.max(0, this.duration - END_MARGIN));
|
|
1896
|
+
for (let k = 0; k < times.length; k++) {
|
|
1897
|
+
const t = times[k];
|
|
1898
|
+
this.timeline.time(t);
|
|
1899
|
+
await this._awaitVideoFrames(t);
|
|
1900
|
+
this._updateSpace(t);
|
|
1901
|
+
this._renderNow();
|
|
1902
|
+
if (k === 0) g.clearRect(0, 0, target.width, target.height);
|
|
1903
|
+
g.globalAlpha = 1 / (k + 1);
|
|
1904
|
+
g.drawImage(stage, 0, 0, target.width, target.height);
|
|
1905
|
+
}
|
|
1906
|
+
g.globalAlpha = 1;
|
|
1907
|
+
if (settle) await this._goto(frame);
|
|
1908
|
+
else {
|
|
1909
|
+
this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames));
|
|
1910
|
+
this.emit("frame", { frame: this.currentFrame, totalFrames: this.totalFrames });
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
_blurCanvas() {
|
|
1914
|
+
const stage = this.app.canvas;
|
|
1915
|
+
const c = document.createElement("canvas");
|
|
1916
|
+
c.width = stage.width;
|
|
1917
|
+
c.height = stage.height;
|
|
1918
|
+
return c;
|
|
1795
1919
|
}
|
|
1796
1920
|
play() {
|
|
1797
1921
|
if (this._initState !== "ready") return;
|
|
@@ -1890,7 +2014,7 @@ var Movie = class {
|
|
|
1890
2014
|
return this.muted;
|
|
1891
2015
|
}
|
|
1892
2016
|
async render(options) {
|
|
1893
|
-
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.render()", options, ["format", "video", "audio"]);
|
|
2017
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("movie.render()", options, ["format", "video", "audio", "motionBlur"]);
|
|
1894
2018
|
try {
|
|
1895
2019
|
return await this._quietly(() => exportFrames(this, options));
|
|
1896
2020
|
} catch (err) {
|
|
@@ -1915,6 +2039,7 @@ var Movie = class {
|
|
|
1915
2039
|
this._initState = "destroyed";
|
|
1916
2040
|
}
|
|
1917
2041
|
};
|
|
2042
|
+
var END_MARGIN = 1e-4;
|
|
1918
2043
|
var clampFrame = (f, last) => Math.max(0, Math.min(Math.round(f), last));
|
|
1919
2044
|
function encodeCanvas(canvas, type, as) {
|
|
1920
2045
|
if (as === "dataURL") return Promise.resolve(canvas.toDataURL(type));
|
|
@@ -2119,17 +2244,17 @@ function orbit(opts) {
|
|
|
2119
2244
|
throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
|
|
2120
2245
|
}
|
|
2121
2246
|
const [cx, cy] = opts.center ?? [width / 2, height / 2];
|
|
2122
|
-
const fixedR = opts.radius ??
|
|
2247
|
+
const fixedR = opts.radius ?? chunk6NWJLVCA_cjs.homeDistance(height, fov ?? chunk6NWJLVCA_cjs.DEFAULT_FOV);
|
|
2123
2248
|
const start = opts.start ?? -degrees / 2;
|
|
2124
2249
|
const steps = Math.max(1, Math.round(duration * stepsPerSecond));
|
|
2125
2250
|
const easeFn = parseEase(ease);
|
|
2126
2251
|
const dollyEase = dolly ? parseEase(dolly.ease ?? ease) : null;
|
|
2127
2252
|
const pos = (i) => {
|
|
2128
2253
|
const p = i / steps;
|
|
2129
|
-
const a = (start + degrees * easeFn(p)) *
|
|
2254
|
+
const a = (start + degrees * easeFn(p)) * chunk6NWJLVCA_cjs.DEG;
|
|
2130
2255
|
if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
|
|
2131
2256
|
const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
|
|
2132
|
-
const R =
|
|
2257
|
+
const R = chunk6NWJLVCA_cjs.homeDistance(height, f);
|
|
2133
2258
|
return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
|
|
2134
2259
|
};
|
|
2135
2260
|
const keyframes = [];
|
|
@@ -2156,6 +2281,365 @@ function orbit(opts) {
|
|
|
2156
2281
|
return spec;
|
|
2157
2282
|
}
|
|
2158
2283
|
|
|
2284
|
+
// src/presets/particles.ts
|
|
2285
|
+
var KEYS = ["count", "at", "emit", "life", "area", "angle", "speed", "gravity", "wind", "drag", "sway", "size", "scale", "fade", "colors", "shape", "spin", "seed", "name", "blendMode", "template", "sampleRate"];
|
|
2286
|
+
var SHAPES = ["circle", "rect", "star"];
|
|
2287
|
+
var MAX_KEYFRAMES = 3e4;
|
|
2288
|
+
function particles(opts) {
|
|
2289
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("particles()", opts, KEYS);
|
|
2290
|
+
const { count, at = 0, emit = 0, gravity = 0, wind = 0, drag = 0, sway, fade, colors = ["#ffffff"], shape = "circle", seed = 0, name = "particle", template, sampleRate = 20 } = opts;
|
|
2291
|
+
if (!(Number.isInteger(count) && count > 0)) throw new Error(`particles(): count must be a whole number of particles (at least 1), got ${count}`);
|
|
2292
|
+
const life = range("life", opts.life, 1e-6);
|
|
2293
|
+
if (!opts.area || !Number.isFinite(opts.area.x) || !Number.isFinite(opts.area.y)) throw new Error("particles(): area is required: { x, y } (the centre) and optionally width / height to spread over");
|
|
2294
|
+
if (!(Number.isFinite(emit) && emit >= 0)) throw new Error(`particles(): emit must be a number of seconds >= 0, got ${emit}`);
|
|
2295
|
+
if (!(Number.isFinite(sampleRate) && sampleRate > 0)) throw new Error(`particles(): sampleRate must be a positive number (samples per second), got ${sampleRate}`);
|
|
2296
|
+
if (!SHAPES.includes(shape)) {
|
|
2297
|
+
const g = chunkUQBGW5DO_cjs.suggestName(String(shape), SHAPES);
|
|
2298
|
+
throw new Error(`particles(): shape "${shape}" is not one of ${SHAPES.map((s) => `"${s}"`).join(", ")}${g ? `; did you mean "${g}"?` : ""}`);
|
|
2299
|
+
}
|
|
2300
|
+
const angle = range("angle", opts.angle ?? [0, 360], -Infinity);
|
|
2301
|
+
const speed = range("speed", opts.speed ?? [100, 300], 0);
|
|
2302
|
+
const size = range("size", opts.size ?? [3, 6], 0);
|
|
2303
|
+
const spin = opts.spin === void 0 ? null : range("spin", opts.spin, -Infinity);
|
|
2304
|
+
const curved = gravity !== 0 || wind !== 0 || drag !== 0 || !!sway;
|
|
2305
|
+
const worst = curved ? Math.max(1, Math.ceil(life[1] * sampleRate)) : 1;
|
|
2306
|
+
if (count * worst > MAX_KEYFRAMES) {
|
|
2307
|
+
throw new Error(`particles(): too many keyframes (${count} particles \xD7 up to ${worst} samples each): lower count or life, or lower sampleRate (now ${sampleRate})`);
|
|
2308
|
+
}
|
|
2309
|
+
const round2 = (v) => Math.round(v * 1e6) / 1e6;
|
|
2310
|
+
const out = [];
|
|
2311
|
+
for (let i = 0; i < count; i++) {
|
|
2312
|
+
const r = chunk6NWJLVCA_cjs.random(seed * 1000003 + i * 7919 + 17);
|
|
2313
|
+
const pick = ([a2, b]) => a2 + r() * (b - a2);
|
|
2314
|
+
const born = at + r() * emit;
|
|
2315
|
+
const lifetime = pick(life);
|
|
2316
|
+
const ax = (r() - 0.5) * (opts.area.width ?? 0), ay = (r() - 0.5) * (opts.area.height ?? 0);
|
|
2317
|
+
const x0 = opts.area.x + ax, y0 = opts.area.y + ay;
|
|
2318
|
+
const a = pick(angle) * Math.PI / 180, v = pick(speed);
|
|
2319
|
+
const vx = v * Math.cos(a), vy = v * Math.sin(a);
|
|
2320
|
+
const s = pick(size);
|
|
2321
|
+
const color = colors[Math.min(colors.length - 1, Math.floor(r() * colors.length))];
|
|
2322
|
+
const turn = spin ? pick(spin) : 0;
|
|
2323
|
+
const startAngle = spin ? r() * 360 : 0;
|
|
2324
|
+
const swayAmp = sway?.amp ?? 0, swayFreq = sway?.freq ?? 1;
|
|
2325
|
+
const n0 = sway ? chunk6NWJLVCA_cjs.noise(0, seed * 1000003 + i) : 0;
|
|
2326
|
+
const at_ = (t) => {
|
|
2327
|
+
const axis = (p0, v0, acc) => {
|
|
2328
|
+
if (drag > 0) {
|
|
2329
|
+
const vInf = acc / drag;
|
|
2330
|
+
return p0 + vInf * t + (v0 - vInf) * (1 - Math.exp(-drag * t)) / drag;
|
|
2331
|
+
}
|
|
2332
|
+
return p0 + v0 * t + 0.5 * acc * t * t;
|
|
2333
|
+
};
|
|
2334
|
+
const sw = sway ? swayAmp * (chunk6NWJLVCA_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
|
|
2335
|
+
return { x: axis(x0, vx, wind) + sw, y: axis(y0, vy, gravity) };
|
|
2336
|
+
};
|
|
2337
|
+
const kfs = [];
|
|
2338
|
+
const steps = curved ? Math.max(1, Math.ceil(lifetime * sampleRate)) : 1;
|
|
2339
|
+
const step = lifetime / steps;
|
|
2340
|
+
for (let k = 1; k <= steps; k++) {
|
|
2341
|
+
const p = at_(k * step);
|
|
2342
|
+
kfs.push({ at: round2((k - 1) * step), to: { x: round2(p.x), y: round2(p.y) }, duration: round2(step), ease: "none" });
|
|
2343
|
+
}
|
|
2344
|
+
if (opts.scale) kfs.push({ at: 0, to: { scale: opts.scale[1] }, duration: round2(lifetime), ease: "none" });
|
|
2345
|
+
if (turn !== 0) kfs.push({ at: 0, to: { rotation: round2(startAngle + turn * lifetime) }, duration: round2(lifetime), ease: "none" });
|
|
2346
|
+
if (fade?.in) kfs.push({ at: 0, to: { alpha: 1 }, duration: fade.in, ease: "none" });
|
|
2347
|
+
if (fade?.out) kfs.push({ at: -fade.out, from: { alpha: 1 }, to: { alpha: 0 }, duration: fade.out, ease: "none" });
|
|
2348
|
+
const initial = { x: round2(x0), y: round2(y0) };
|
|
2349
|
+
if (opts.scale) initial.scale = opts.scale[0];
|
|
2350
|
+
if (fade?.in) initial.alpha = 0;
|
|
2351
|
+
if (spin) initial.rotation = round2(startAngle);
|
|
2352
|
+
const common = { name: `${name}-${i}`, at: round2(born), duration: round2(lifetime), ...opts.blendMode ? { blendMode: opts.blendMode } : {} };
|
|
2353
|
+
if (template) {
|
|
2354
|
+
const t = template;
|
|
2355
|
+
out.push({ ...t, ...common, initial: { ...t.initial, ...initial }, keyframes: [...t.keyframes ?? [], ...kfs] });
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2358
|
+
const style = { ...initial, fillColor: color };
|
|
2359
|
+
if (shape === "circle") out.push({ type: "shape", shape: "circle", radius: round2(s), ...common, initial: style, keyframes: kfs });
|
|
2360
|
+
else if (shape === "rect") out.push({ type: "shape", shape: "rect", width: round2(s * 2), height: round2(s * 2), ...common, initial: style, keyframes: kfs });
|
|
2361
|
+
else {
|
|
2362
|
+
const points = [];
|
|
2363
|
+
for (let p = 0; p < 10; p++) {
|
|
2364
|
+
const rad = p % 2 === 0 ? s : s * 0.45, ang = -Math.PI / 2 + p * Math.PI / 5;
|
|
2365
|
+
points.push([round2(rad * Math.cos(ang)), round2(rad * Math.sin(ang))]);
|
|
2366
|
+
}
|
|
2367
|
+
out.push({ type: "shape", shape: "polygon", points, ...common, initial: style, keyframes: kfs });
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return out;
|
|
2371
|
+
}
|
|
2372
|
+
function range(name, v, min) {
|
|
2373
|
+
const [a, b] = typeof v === "number" ? [v, v] : v;
|
|
2374
|
+
if (!(Number.isFinite(a) && Number.isFinite(b)) || a > b || a < min || min > 0 && a <= 0) {
|
|
2375
|
+
throw new Error(`particles(): ${name} must be a number or [min, max] with min <= max${min > -Infinity ? ` and min >= ${min}` : ""}, got ${JSON.stringify(v)}`);
|
|
2376
|
+
}
|
|
2377
|
+
return [a, b];
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
// src/presets/followPath.ts
|
|
2381
|
+
var KEYS2 = ["d", "at", "duration", "ease", "from", "to", "orient", "rotate", "frameRate"];
|
|
2382
|
+
var MAX_STEPS = 2e3;
|
|
2383
|
+
function followPath(opts) {
|
|
2384
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("followPath()", opts, KEYS2);
|
|
2385
|
+
const { d, at = 0, duration, ease = "none", from = 0, to = 1, orient = false, rotate = 0, frameRate = 30 } = opts;
|
|
2386
|
+
if (typeof d !== "string" || d.trim() === "") throw new Error('followPath(): d must be SVG path data, e.g. "M 100 600 C 300 100 900 100 1180 600"');
|
|
2387
|
+
if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`followPath(): duration must be a positive number of seconds, got ${duration}`);
|
|
2388
|
+
if (!(Number.isFinite(frameRate) && frameRate > 0)) throw new Error(`followPath(): frameRate must be a positive number (samples per second), got ${frameRate}`);
|
|
2389
|
+
for (const [n, v] of [["from", from], ["to", to]]) {
|
|
2390
|
+
if (!(Number.isFinite(v) && v >= 0 && v <= 1)) throw new Error(`followPath(): ${n} must be a fraction of the path between 0 and 1, got ${v}`);
|
|
2391
|
+
}
|
|
2392
|
+
const steps = Math.max(1, Math.round(duration * frameRate));
|
|
2393
|
+
if (steps > MAX_STEPS) throw new Error(`followPath(): ${steps} samples is too many (max ${MAX_STEPS}); lower frameRate or duration`);
|
|
2394
|
+
const segments = [];
|
|
2395
|
+
let total = 0;
|
|
2396
|
+
for (const line of chunk6NWJLVCA_cjs.flattenSvgPath(d)) {
|
|
2397
|
+
const p = line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
|
|
2398
|
+
for (let i = 2; i < p.length; i += 2) {
|
|
2399
|
+
const len = Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
|
|
2400
|
+
if (len === 0) continue;
|
|
2401
|
+
segments.push({ x0: p[i - 2], y0: p[i - 1], x1: p[i], y1: p[i + 1], start: total, len });
|
|
2402
|
+
total += len;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
if (!(total > 0)) throw new Error(`followPath(): the path "${d}" has no length (it needs at least two different points)`);
|
|
2406
|
+
const find = (s) => {
|
|
2407
|
+
let lo = 0, hi = segments.length - 1;
|
|
2408
|
+
while (lo < hi) {
|
|
2409
|
+
const mid = lo + hi >> 1;
|
|
2410
|
+
if (segments[mid].start + segments[mid].len < s) lo = mid + 1;
|
|
2411
|
+
else hi = mid;
|
|
2412
|
+
}
|
|
2413
|
+
return segments[lo];
|
|
2414
|
+
};
|
|
2415
|
+
const at_ = (fraction) => {
|
|
2416
|
+
const s = Math.min(Math.max(fraction, 0), 1) * total;
|
|
2417
|
+
const g = find(s);
|
|
2418
|
+
const t = Math.min(Math.max((s - g.start) / g.len, 0), 1);
|
|
2419
|
+
return { x: g.x0 + (g.x1 - g.x0) * t, y: g.y0 + (g.y1 - g.y0) * t, heading: Math.atan2(g.y1 - g.y0, g.x1 - g.x0) * 180 / Math.PI };
|
|
2420
|
+
};
|
|
2421
|
+
const shape = parseEase(ease);
|
|
2422
|
+
const round2 = (v) => Math.round(v * 1e6) / 1e6;
|
|
2423
|
+
let turn = 0;
|
|
2424
|
+
const sample = (k) => {
|
|
2425
|
+
const p = from + (to - from) * shape(k / steps);
|
|
2426
|
+
const pt = at_(p);
|
|
2427
|
+
const out2 = { x: round2(pt.x), y: round2(pt.y) };
|
|
2428
|
+
if (orient) {
|
|
2429
|
+
let h = pt.heading + rotate;
|
|
2430
|
+
if (k === 0) turn = h;
|
|
2431
|
+
else {
|
|
2432
|
+
let delta = h - turn;
|
|
2433
|
+
delta -= 360 * Math.round(delta / 360);
|
|
2434
|
+
turn += delta;
|
|
2435
|
+
}
|
|
2436
|
+
out2.rotation = round2(turn);
|
|
2437
|
+
}
|
|
2438
|
+
return out2;
|
|
2439
|
+
};
|
|
2440
|
+
const out = [{ at, set: sample(0) }];
|
|
2441
|
+
const step = duration / steps;
|
|
2442
|
+
for (let k = 1; k <= steps; k++) out.push({ at: round2(at + (k - 1) * step), to: sample(k), duration: round2(step), ease: "none" });
|
|
2443
|
+
return out;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// src/presets/stagger.ts
|
|
2447
|
+
var STAGGER_KEYS = ["each", "amount", "from", "grid", "ease", "seed"];
|
|
2448
|
+
var FROM_NAMES = ["start", "end", "center", "edges", "random"];
|
|
2449
|
+
function stagger(items, options = {}) {
|
|
2450
|
+
const isCount = typeof items === "number";
|
|
2451
|
+
const n = isCount ? items : items.length;
|
|
2452
|
+
if (!Number.isInteger(n) || n < 0) throw new Error(`stagger(): count must be a whole number of items, got ${n}`);
|
|
2453
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("stagger()", options, STAGGER_KEYS);
|
|
2454
|
+
const { each, amount, from = "start", grid, ease = "none", seed = 0 } = options;
|
|
2455
|
+
if (each !== void 0 && amount !== void 0) throw new Error("stagger(): give `each` (seconds between neighbours) or `amount` (seconds in total), not both");
|
|
2456
|
+
if (each !== void 0 && !(Number.isFinite(each) && each >= 0)) throw new Error(`stagger(): each must be a number of seconds >= 0, got ${each}`);
|
|
2457
|
+
if (amount !== void 0 && !(Number.isFinite(amount) && amount >= 0)) throw new Error(`stagger(): amount must be a number of seconds >= 0, got ${amount}`);
|
|
2458
|
+
if (typeof from === "string" && !FROM_NAMES.includes(from)) {
|
|
2459
|
+
throw new Error(`stagger(): from "${from}" is not one of ${FROM_NAMES.map((f) => `"${f}"`).join(", ")} or an item index`);
|
|
2460
|
+
}
|
|
2461
|
+
if (typeof from === "number" && !(Number.isInteger(from) && from >= 0 && from < Math.max(n, 1))) throw new Error(`stagger(): from ${from} is not an index of the ${n} items`);
|
|
2462
|
+
if (grid && !(Array.isArray(grid) && grid.length === 2 && grid.every((g) => Number.isInteger(g) && g > 0) && grid[0] * grid[1] >= n)) {
|
|
2463
|
+
throw new Error(`stagger(): grid must be [columns, rows] and hold all ${n} items, got ${JSON.stringify(grid)}`);
|
|
2464
|
+
}
|
|
2465
|
+
const dist = distances(n, from, grid, seed);
|
|
2466
|
+
const max = dist.reduce((m, d) => Math.max(m, d), 0);
|
|
2467
|
+
const total = amount !== void 0 ? amount : (each ?? 0.1) * max;
|
|
2468
|
+
const shape = parseEase(ease);
|
|
2469
|
+
const delays = dist.map((d) => max === 0 ? 0 : Math.round(total * shape(d / max) * 1e6) / 1e6);
|
|
2470
|
+
if (isCount) return delays;
|
|
2471
|
+
return items.map((layer, i) => ({ ...layer, at: Math.round(((layer.at ?? 0) + delays[i]) * 1e6) / 1e6 }));
|
|
2472
|
+
}
|
|
2473
|
+
function distances(n, from, grid, seed) {
|
|
2474
|
+
if (n === 0) return [];
|
|
2475
|
+
if (from === "random") {
|
|
2476
|
+
const order = Array.from({ length: n }, (_, i) => i);
|
|
2477
|
+
const r = chunk6NWJLVCA_cjs.random(seed);
|
|
2478
|
+
for (let i = n - 1; i > 0; i--) {
|
|
2479
|
+
const j = Math.floor(r() * (i + 1));
|
|
2480
|
+
[order[i], order[j]] = [order[j], order[i]];
|
|
2481
|
+
}
|
|
2482
|
+
return order;
|
|
2483
|
+
}
|
|
2484
|
+
if (!grid) {
|
|
2485
|
+
const mid = (n - 1) / 2;
|
|
2486
|
+
return Array.from({ length: n }, (_, i) => {
|
|
2487
|
+
switch (from) {
|
|
2488
|
+
case "start":
|
|
2489
|
+
return i;
|
|
2490
|
+
case "end":
|
|
2491
|
+
return n - 1 - i;
|
|
2492
|
+
case "center":
|
|
2493
|
+
return Math.abs(i - mid);
|
|
2494
|
+
case "edges":
|
|
2495
|
+
return mid - Math.abs(i - mid);
|
|
2496
|
+
default:
|
|
2497
|
+
return Math.abs(i - from);
|
|
2498
|
+
}
|
|
2499
|
+
});
|
|
2500
|
+
}
|
|
2501
|
+
const [cols, rows] = grid;
|
|
2502
|
+
const at = (i) => ({ x: i % cols, y: Math.floor(i / cols) });
|
|
2503
|
+
const origin = from === "start" ? { x: 0, y: 0 } : from === "end" ? { x: cols - 1, y: rows - 1 } : from === "center" || from === "edges" ? { x: (cols - 1) / 2, y: (rows - 1) / 2 } : at(from);
|
|
2504
|
+
const d = Array.from({ length: n }, (_, i) => Math.hypot(at(i).x - origin.x, at(i).y - origin.y));
|
|
2505
|
+
if (from !== "edges") return d;
|
|
2506
|
+
const far = Math.max(...d);
|
|
2507
|
+
return d.map((v) => far - v);
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
// src/presets/wiggle.ts
|
|
2511
|
+
var WIGGLE_KEYS = ["at", "duration", "freq", "seed", "ease", "props"];
|
|
2512
|
+
var MAX_STEPS2 = 2e3;
|
|
2513
|
+
function wiggle(opts) {
|
|
2514
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("wiggle()", opts, WIGGLE_KEYS);
|
|
2515
|
+
const { at = 0, duration, freq = 3, seed = 0, ease = "sine.inOut", props } = opts;
|
|
2516
|
+
if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`wiggle(): duration must be a positive number of seconds, got ${duration}`);
|
|
2517
|
+
if (!(Number.isFinite(freq) && freq > 0)) throw new Error(`wiggle(): freq must be a positive number (steps per second), got ${freq}`);
|
|
2518
|
+
const names = Object.keys(props ?? {});
|
|
2519
|
+
if (names.length === 0) throw new Error("wiggle(): props is empty; name at least one property, e.g. props: { x: { around: 100, amp: 8 } }");
|
|
2520
|
+
for (const n of names) {
|
|
2521
|
+
const p = props[n];
|
|
2522
|
+
if (!Number.isFinite(p.amp)) throw new Error(`wiggle(): props.${n}.amp must be a number, got ${p.amp}`);
|
|
2523
|
+
if (p.around === void 0 || p.around === null || p.around === "") throw new Error(`wiggle(): props.${n}.around is missing`);
|
|
2524
|
+
}
|
|
2525
|
+
const steps = Math.ceil(duration * freq - 1e-9);
|
|
2526
|
+
if (steps > MAX_STEPS2) throw new Error(`wiggle(): ${steps} steps is too many (max ${MAX_STEPS2}); lower freq or duration`);
|
|
2527
|
+
const set = {};
|
|
2528
|
+
for (const n of names) set[n] = props[n].around;
|
|
2529
|
+
const out = [{ at, set }];
|
|
2530
|
+
const stepLen = duration / steps;
|
|
2531
|
+
for (let s = 0; s < steps; s++) {
|
|
2532
|
+
const to = {};
|
|
2533
|
+
names.forEach((n, pi) => {
|
|
2534
|
+
const p = props[n];
|
|
2535
|
+
if (s === steps - 1) {
|
|
2536
|
+
to[n] = p.around;
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
const off = (chunk6NWJLVCA_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
|
|
2540
|
+
to[n] = typeof p.around === "number" ? p.around + off : `(${p.around}) ${off < 0 ? "-" : "+"} ${Math.abs(off)}`;
|
|
2541
|
+
});
|
|
2542
|
+
out.push({ at: at + s * stepLen, to, duration: stepLen, ease });
|
|
2543
|
+
}
|
|
2544
|
+
return out;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
// src/presets/animateText.ts
|
|
2548
|
+
var KEYS3 = ["by", "x", "y", "align", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
|
|
2549
|
+
var PRESETS = {
|
|
2550
|
+
rise: { from: { y: 40, alpha: 0 }, duration: 0.5, ease: "back.out(2)" },
|
|
2551
|
+
drop: { from: { y: -50, alpha: 0 }, duration: 0.7, ease: "bounce.out" },
|
|
2552
|
+
fade: { from: { alpha: 0 }, duration: 0.5, ease: "power1.out" },
|
|
2553
|
+
pop: { from: { scale: 0, alpha: 0 }, duration: 0.45, ease: "back.out(2.4)" },
|
|
2554
|
+
zoom: { from: { scale: 2.5, alpha: 0 }, duration: 0.6, ease: "power3.out" },
|
|
2555
|
+
slide: { from: { x: -60, alpha: 0 }, duration: 0.5, ease: "power3.out" },
|
|
2556
|
+
spin: { from: { rotation: -90, scale: 0, alpha: 0 }, duration: 0.6, ease: "back.out(1.7)" }
|
|
2557
|
+
};
|
|
2558
|
+
var PRESET_NAMES = Object.keys(PRESETS);
|
|
2559
|
+
function animateText(text, style, options) {
|
|
2560
|
+
chunkUQBGW5DO_cjs.warnUnknownOptions("animateText()", options, KEYS3);
|
|
2561
|
+
const { by = "chars", x = 0, y = 0, align = "left", name = "text", at = 0, duration, idle, styleFor } = options;
|
|
2562
|
+
if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`animateText(): duration must be a positive number of seconds (how long until the last piece is gone), got ${duration}`);
|
|
2563
|
+
const pieces = splitText(text, style, { by, x, y, align });
|
|
2564
|
+
if (pieces.length === 0) return [];
|
|
2565
|
+
const lineHeight = measureText("M", style).height;
|
|
2566
|
+
const inTween = options.in === false ? null : resolveTween(options.in ?? "rise", "in");
|
|
2567
|
+
const outTween = options.out === void 0 || options.out === false ? null : resolveTween(options.out, "out");
|
|
2568
|
+
const delays = stagger(pieces.length, options.stagger ?? { each: 0.04 });
|
|
2569
|
+
const wave = Math.max(...delays);
|
|
2570
|
+
const need = (inTween?.duration ?? 0) + (outTween?.duration ?? 0) + wave;
|
|
2571
|
+
if (duration < need - 1e-9) {
|
|
2572
|
+
throw new Error(`animateText(): duration ${duration} is too short for this wave: the entrance${outTween ? ", the exit" : ""} and the stagger need at least ${+need.toFixed(3)} s`);
|
|
2573
|
+
}
|
|
2574
|
+
const end = at + duration;
|
|
2575
|
+
return pieces.map((p, i) => {
|
|
2576
|
+
const cx = p.x + p.width / 2, cy = p.y + lineHeight / 2;
|
|
2577
|
+
const rest = { x: cx, y: cy, alpha: 1, scale: 1, scaleX: 1, scaleY: 1, rotation: 0 };
|
|
2578
|
+
const start = at + delays[i];
|
|
2579
|
+
const len = end - start;
|
|
2580
|
+
const keyframes = [];
|
|
2581
|
+
const place = (key, v) => key === "x" || key === "y" ? rest[key] + v : v;
|
|
2582
|
+
if (inTween) {
|
|
2583
|
+
keyframes.push({
|
|
2584
|
+
at: 0,
|
|
2585
|
+
duration: inTween.duration,
|
|
2586
|
+
ease: inTween.ease,
|
|
2587
|
+
from: Object.fromEntries(Object.entries(inTween.from).map(([k, v]) => [k, place(k, v)])),
|
|
2588
|
+
to: Object.fromEntries(Object.keys(inTween.from).map((k) => [k, inTween.to[k] ?? rest[k]]))
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
let idleEnd = len;
|
|
2592
|
+
if (outTween) {
|
|
2593
|
+
const begin = outTween.duration + (wave - delays[i]);
|
|
2594
|
+
idleEnd = len - begin;
|
|
2595
|
+
keyframes.push({
|
|
2596
|
+
at: -begin,
|
|
2597
|
+
duration: outTween.duration,
|
|
2598
|
+
ease: outTween.ease,
|
|
2599
|
+
from: Object.fromEntries(Object.keys(outTween.from).map((k) => [k, outTween.to[k] ?? rest[k]])),
|
|
2600
|
+
to: Object.fromEntries(Object.entries(outTween.from).map(([k, v]) => [k, place(k, v)]))
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2603
|
+
if (idle) {
|
|
2604
|
+
const props = {};
|
|
2605
|
+
for (const k of ["x", "y", "rotation", "scale"]) if (idle[k] !== void 0) props[k] = { around: rest[k], amp: idle[k] };
|
|
2606
|
+
const freq = idle.freq ?? 1;
|
|
2607
|
+
const from = inTween?.duration ?? 0;
|
|
2608
|
+
if (Object.keys(props).length > 0 && idleEnd - from >= 1 / freq) {
|
|
2609
|
+
keyframes.push(...wiggle({ at: from, duration: idleEnd - from, freq, seed: (idle.seed ?? 0) * 1e3 + i, props }));
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
return {
|
|
2613
|
+
type: "text",
|
|
2614
|
+
name: `${name}-${i}`,
|
|
2615
|
+
text: p.text,
|
|
2616
|
+
style: styleFor ? { ...style, ...styleFor(p) } : style,
|
|
2617
|
+
at: start,
|
|
2618
|
+
duration: len,
|
|
2619
|
+
initial: { x: cx, y: cy, anchorX: 0.5, anchorY: 0.5 },
|
|
2620
|
+
keyframes
|
|
2621
|
+
};
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
function resolveTween(spec, which) {
|
|
2625
|
+
const t = typeof spec === "string" ? { preset: spec } : spec;
|
|
2626
|
+
if (t.preset !== void 0 && !(t.preset in PRESETS)) {
|
|
2627
|
+
const guess = chunkUQBGW5DO_cjs.suggestName(String(t.preset), PRESET_NAMES);
|
|
2628
|
+
throw new Error(`animateText(): ${which} "${t.preset}" is not a preset${guess ? `; did you mean "${guess}"?` : ""} (presets: ${PRESET_NAMES.map((n) => `"${n}"`).join(", ")})`);
|
|
2629
|
+
}
|
|
2630
|
+
const base = t.preset ? PRESETS[t.preset] : void 0;
|
|
2631
|
+
const from = { ...base?.from ?? {}, ...t.from ?? {} };
|
|
2632
|
+
const to = t.to ?? {};
|
|
2633
|
+
const known = /* @__PURE__ */ new Set(["x", "y", "alpha", "scale", "scaleX", "scaleY", "rotation"]);
|
|
2634
|
+
for (const k of Object.keys(from)) {
|
|
2635
|
+
if (!known.has(k) && to[k] === void 0) throw new Error(`animateText(): ${which}.from has "${k}", which has no resting value; give ${which}.to: { ${k}: \u2026 }`);
|
|
2636
|
+
}
|
|
2637
|
+
if (Object.keys(from).length === 0) throw new Error(`animateText(): ${which} needs a preset or a from`);
|
|
2638
|
+
let ease = t.ease ?? base?.ease ?? "power2.out";
|
|
2639
|
+
if (which === "out" && t.ease === void 0) ease = ease.replace(".out", ".in");
|
|
2640
|
+
return { from, to: { ...to }, duration: t.duration ?? base?.duration ?? 0.5, ease };
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2159
2643
|
// src/transforms/withFade.ts
|
|
2160
2644
|
function withFade(spec, opts) {
|
|
2161
2645
|
chunkUQBGW5DO_cjs.warnUnknownOptions("withFade()", opts, ["in", "out"]);
|
|
@@ -2176,15 +2660,35 @@ function withFade(spec, opts) {
|
|
|
2176
2660
|
return spec;
|
|
2177
2661
|
}
|
|
2178
2662
|
|
|
2663
|
+
Object.defineProperty(exports, "noise", {
|
|
2664
|
+
enumerable: true,
|
|
2665
|
+
get: function () { return chunk6NWJLVCA_cjs.noise; }
|
|
2666
|
+
});
|
|
2667
|
+
Object.defineProperty(exports, "rand", {
|
|
2668
|
+
enumerable: true,
|
|
2669
|
+
get: function () { return chunk6NWJLVCA_cjs.rand; }
|
|
2670
|
+
});
|
|
2671
|
+
Object.defineProperty(exports, "random", {
|
|
2672
|
+
enumerable: true,
|
|
2673
|
+
get: function () { return chunk6NWJLVCA_cjs.random; }
|
|
2674
|
+
});
|
|
2179
2675
|
Object.defineProperty(exports, "registerSequenceType", {
|
|
2180
2676
|
enumerable: true,
|
|
2181
|
-
get: function () { return
|
|
2677
|
+
get: function () { return chunk6NWJLVCA_cjs.registerSequenceType; }
|
|
2182
2678
|
});
|
|
2183
2679
|
exports.Movie = Movie;
|
|
2680
|
+
exports.animateText = animateText;
|
|
2681
|
+
exports.dismissLoader = dismissLoader;
|
|
2682
|
+
exports.failLoader = failLoader;
|
|
2683
|
+
exports.followPath = followPath;
|
|
2184
2684
|
exports.kenBurns = kenBurns;
|
|
2185
2685
|
exports.measureText = measureText;
|
|
2186
2686
|
exports.orbit = orbit;
|
|
2687
|
+
exports.particles = particles;
|
|
2688
|
+
exports.resolveLoader = resolveLoader;
|
|
2187
2689
|
exports.splitText = splitText;
|
|
2690
|
+
exports.stagger = stagger;
|
|
2691
|
+
exports.wiggle = wiggle;
|
|
2188
2692
|
exports.withFade = withFade;
|
|
2189
2693
|
//# sourceMappingURL=index.cjs.map
|
|
2190
2694
|
//# sourceMappingURL=index.cjs.map
|