pixi-effects 0.41.0 → 0.43.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/dist/{Base-Jgkud1Aw.d.ts → Base-DKFQtqzW.d.ts} +1 -1
  5. package/dist/{Base-DQWIJ6bJ.d.cts → Base-lb5Uhaqu.d.cts} +1 -1
  6. package/dist/Controller.d.cts +2 -2
  7. package/dist/Controller.d.ts +2 -2
  8. package/dist/{Movie-CwqRLOzE.d.ts → Movie-B_qQIYV6.d.ts} +47 -4
  9. package/dist/{Movie-DWtxRiJ2.d.cts → Movie-BdUm2Mc9.d.cts} +47 -4
  10. package/dist/{chunk-Y2N6U3X5.js → chunk-6N5ZBUOU.js} +169 -8
  11. package/dist/chunk-6N5ZBUOU.js.map +1 -0
  12. package/dist/{chunk-RN5FWGGM.cjs → chunk-BEGOKE7Q.cjs} +169 -8
  13. package/dist/chunk-BEGOKE7Q.cjs.map +1 -0
  14. package/dist/index.cjs +25 -25
  15. package/dist/index.d.cts +4 -4
  16. package/dist/index.d.ts +4 -4
  17. package/dist/index.js +1 -1
  18. package/dist/loader.css +1 -1
  19. package/dist/present.cjs +1 -1
  20. package/dist/present.d.cts +2 -2
  21. package/dist/present.d.ts +2 -2
  22. package/dist/present.js +1 -1
  23. package/dist/three.cjs +115 -24
  24. package/dist/three.cjs.map +1 -1
  25. package/dist/three.d.cts +28 -2
  26. package/dist/three.d.ts +28 -2
  27. package/dist/three.js +117 -26
  28. package/dist/three.js.map +1 -1
  29. package/dist/{types-DIh93lFb.d.cts → types-DiNHtdrS.d.cts} +19 -0
  30. package/dist/{types-DIh93lFb.d.ts → types-DiNHtdrS.d.ts} +19 -0
  31. package/docs/api.md +23 -3
  32. package/docs/dsl.md +3 -2
  33. package/llms-full.txt +147 -17
  34. package/package.json +2 -1
  35. package/skills/pixi-effects/SKILL.md +2 -1
  36. package/skills/pixi-effects/reference/cheatsheet.md +2 -0
  37. package/skills/pixi-effects/reference/recipes.md +117 -11
  38. package/skills/pixi-effects/template.html +6 -6
  39. package/dist/chunk-RN5FWGGM.cjs.map +0 -1
  40. package/dist/chunk-Y2N6U3X5.js.map +0 -1
@@ -9,6 +9,21 @@ var chunkTWZNOUM7_cjs = require('./chunk-TWZNOUM7.cjs');
9
9
  var pixi_js = require('pixi.js');
10
10
  var mediabunny = require('mediabunny');
11
11
 
12
+ // src/core/gpuShare.ts
13
+ function sharedGpuOf(renderer) {
14
+ const r = renderer;
15
+ if (!r || r.name !== "webgpu" || !r.gpu?.device) return void 0;
16
+ const uid = r.uid;
17
+ return {
18
+ device: r.gpu.device,
19
+ adopt(source, texture) {
20
+ const data = source._gpuData;
21
+ if (!data || typeof data !== "object") throw new Error("this PixiJS keeps no _gpuData on its texture sources");
22
+ data[uid] = { textureView: null, textureViews: /* @__PURE__ */ Object.create(null), gpuTexture: texture, destroy() {
23
+ } };
24
+ }
25
+ };
26
+ }
12
27
  var registered = false;
13
28
  function ensureLoadersRegistered(audioContext) {
14
29
  if (registered) return;
@@ -1636,6 +1651,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1636
1651
  if (typeof drawn.renderer === "string") {
1637
1652
  info.renderer = drawn.renderer;
1638
1653
  if (typeof drawn.backend === "string") info.backend = drawn.backend;
1654
+ if (info.renderer === "webgpu") info.sharedGpu = drawn.sharedGpu === true;
1639
1655
  }
1640
1656
  layers.push(info);
1641
1657
  chainAlpha.set(info, parentAlpha * alpha);
@@ -1732,6 +1748,23 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1732
1748
  }
1733
1749
 
1734
1750
  // src/core/pick.ts
1751
+ var scratch;
1752
+ var saidBlind = /* @__PURE__ */ new Set();
1753
+ var readResourceAlpha = (resource, x, y) => {
1754
+ try {
1755
+ if (scratch === void 0) {
1756
+ const c = document.createElement("canvas");
1757
+ c.width = c.height = 1;
1758
+ scratch = c.getContext("2d", { willReadFrequently: true });
1759
+ }
1760
+ if (!scratch) return null;
1761
+ scratch.clearRect(0, 0, 1, 1);
1762
+ scratch.drawImage(resource, x, y, 1, 1, 0, 0, 1, 1);
1763
+ return scratch.getImageData(0, 0, 1, 1).data[3] / 255;
1764
+ } catch {
1765
+ return null;
1766
+ }
1767
+ };
1735
1768
  function pointInQuad(q, x, y) {
1736
1769
  let pos = 0, neg = 0;
1737
1770
  for (let i = 0; i < 4; i++) {
@@ -1756,11 +1789,14 @@ var above = (a, b) => {
1756
1789
  }
1757
1790
  return b.length - a.length;
1758
1791
  };
1759
- function pickScene(root, x, y, opts = {}) {
1792
+ function pickScene(root, x, y, opts = {}, deps = {}) {
1793
+ if (opts.alpha !== void 0 && !(opts.alpha >= 0 && opts.alpha <= 1)) throw new Error(`pixi-effects: pick: alpha must be a number from 0 to 1 (a pixel at or below it is not a hit), got ${opts.alpha}`);
1794
+ const readAlpha = deps.readAlpha ?? readResourceAlpha;
1760
1795
  const found = [];
1796
+ const names = (list, name, path) => list.some((e) => e === path || name !== void 0 && e === name || path.startsWith(e + "/") || path.split("/").slice(0, -1).includes(e));
1761
1797
  const wanted = (name, path) => {
1762
- if (opts.ignore && (opts.ignore.includes(path) || name !== void 0 && opts.ignore.includes(name))) return false;
1763
- if (opts.only && !(opts.only.includes(path) || name !== void 0 && opts.only.includes(name))) return false;
1798
+ if (opts.ignore && names(opts.ignore, name, path)) return false;
1799
+ if (opts.only && !names(opts.only, name, path)) return false;
1764
1800
  return true;
1765
1801
  };
1766
1802
  const inside = (b, p) => p.x >= b.x && p.x <= b.x + b.width && p.y >= b.y && p.y <= b.y + b.height;
@@ -1790,8 +1826,34 @@ function pickScene(root, x, y, opts = {}) {
1790
1826
  const lb = d.getLocalBounds();
1791
1827
  if (inside(lb, local)) {
1792
1828
  const three = typeof seq.pickAt === "function" ? seq.pickAt((local.x - lb.x) / lb.width, (local.y - lb.y) / lb.height) : void 0;
1793
- if (three) hit = { name, path, type: seq.spec.type, kind: "three", point, three };
1794
- else if (three === void 0 && (typeof d.containsPoint !== "function" || d.containsPoint(local))) hit = { name, path, type: seq.spec.type, kind: "flat", point, local: { x: local.x - lb.x, y: local.y - lb.y } };
1829
+ if (three) {
1830
+ const planeOf = seq.dragPlane;
1831
+ if (planeOf) {
1832
+ three.grab = (plane = "screen") => {
1833
+ const along = planeOf.call(seq, three.point, plane);
1834
+ return { move: (cx, cy) => {
1835
+ const lp = d.toLocal({ x: cx, y: cy });
1836
+ const b = d.getLocalBounds();
1837
+ return along((lp.x - b.x) / b.width, (lp.y - b.y) / b.height);
1838
+ } };
1839
+ };
1840
+ }
1841
+ hit = { name, path, type: seq.spec.type, kind: "three", point, three };
1842
+ } else if (three === void 0 && (typeof d.containsPoint !== "function" || d.containsPoint(local))) {
1843
+ let opaque = true;
1844
+ const tex = opts.alpha !== void 0 && seq.spec.type !== "shader" ? d.texture : void 0;
1845
+ if (tex?.source?.resource) {
1846
+ const res = tex.source.resolution ?? 1;
1847
+ const a = readAlpha(tex.source.resource, (tex.frame.x + (local.x - lb.x) / lb.width * tex.frame.width) * res, (tex.frame.y + (local.y - lb.y) / lb.height * tex.frame.height) * res);
1848
+ if (a === null) {
1849
+ if (!saidBlind.has(path)) {
1850
+ saidBlind.add(path);
1851
+ console.warn(`pixi-effects: pick: the picture of layer "${path}" cannot be read for { alpha } (a picture from another origin: load it with crossorigin="anonymous" or from the same site), so the box counts as the picture`);
1852
+ }
1853
+ } else opaque = a > opts.alpha;
1854
+ }
1855
+ if (opaque) hit = { name, path, type: seq.spec.type, kind: "flat", point, local: { x: local.x - lb.x, y: local.y - lb.y } };
1856
+ }
1795
1857
  }
1796
1858
  }
1797
1859
  if (!hit) return;
@@ -1805,6 +1867,82 @@ function pickScene(root, x, y, opts = {}) {
1805
1867
  return found.sort((a, b) => above(a.order, b.order)).map((f) => f.hit);
1806
1868
  }
1807
1869
 
1870
+ // src/core/hover.ts
1871
+ var keyOf = (h) => h ? `${h.path}${h.three ? `#${h.three.object}` : ""}` : null;
1872
+ function startHover(host, opts = {}) {
1873
+ const { canvas } = host;
1874
+ const request = host.requestFrame ?? ((fn) => requestAnimationFrame(fn));
1875
+ const cancel = host.cancelFrame ?? ((id) => cancelAnimationFrame(id));
1876
+ const cursor = opts.cursor === true ? "pointer" : opts.cursor || "";
1877
+ const pickOpts = {};
1878
+ if (opts.only) pickOpts.only = opts.only;
1879
+ if (opts.ignore) pickOpts.ignore = opts.ignore;
1880
+ let current = null;
1881
+ let last = null;
1882
+ let loop = null;
1883
+ let said = false;
1884
+ const call = (fn, hit) => {
1885
+ if (!fn) return;
1886
+ try {
1887
+ fn(hit);
1888
+ } catch (err) {
1889
+ if (!said) {
1890
+ said = true;
1891
+ console.warn("pixi-effects: movie.hover: a handler threw (said once) \u2014", err);
1892
+ }
1893
+ }
1894
+ };
1895
+ const setCursor = (on) => {
1896
+ if (cursor) canvas.style.cursor = on ? cursor : "";
1897
+ };
1898
+ const evaluate = () => {
1899
+ if (!last) return;
1900
+ const [x, y] = host.point(last);
1901
+ const hit = host.pick(x, y, pickOpts);
1902
+ if (keyOf(hit) === keyOf(current)) {
1903
+ current = hit ?? current;
1904
+ return;
1905
+ }
1906
+ const was = current;
1907
+ current = hit;
1908
+ if (was) call(opts.onLeave, was);
1909
+ if (hit) call(opts.onEnter, hit);
1910
+ setCursor(!!hit);
1911
+ };
1912
+ const tick = () => {
1913
+ loop = null;
1914
+ if (last) {
1915
+ evaluate();
1916
+ loop = request(tick);
1917
+ }
1918
+ };
1919
+ const onMove = (event) => {
1920
+ last = event;
1921
+ evaluate();
1922
+ if (loop === null) loop = request(tick);
1923
+ };
1924
+ const onLeave = () => {
1925
+ last = null;
1926
+ if (loop !== null) {
1927
+ cancel(loop);
1928
+ loop = null;
1929
+ }
1930
+ const was = current;
1931
+ current = null;
1932
+ if (was) call(opts.onLeave, was);
1933
+ setCursor(false);
1934
+ };
1935
+ canvas.addEventListener("pointermove", onMove);
1936
+ canvas.addEventListener("pointerdown", onMove);
1937
+ canvas.addEventListener("pointerleave", onLeave);
1938
+ return () => {
1939
+ canvas.removeEventListener("pointermove", onMove);
1940
+ canvas.removeEventListener("pointerdown", onMove);
1941
+ canvas.removeEventListener("pointerleave", onLeave);
1942
+ onLeave();
1943
+ };
1944
+ }
1945
+
1808
1946
  // src/core/timelineChart.ts
1809
1947
  var GROUP_MIN = 4;
1810
1948
  var MAX_KEYS = 200;
@@ -2607,12 +2745,15 @@ var Movie = class {
2607
2745
  */
2608
2746
  async _construct(composition, vars, width, height, lp, sizeWins = this._resized) {
2609
2747
  const spec = applyVars(composition, vars);
2748
+ const gpu = sharedGpuOf(this.app?.renderer);
2749
+ const gpuField = gpu ? { gpu } : {};
2610
2750
  const rootShape = {
2611
2751
  width,
2612
2752
  height,
2613
2753
  duration: this.duration,
2614
2754
  frameRate: this.frameRate,
2615
2755
  vars: numericVars(vars),
2756
+ ...gpuField,
2616
2757
  ...lp ? { onLayerBuilt: async () => {
2617
2758
  lp.tick();
2618
2759
  await lp.yieldIfDue();
@@ -2859,6 +3000,14 @@ var Movie = class {
2859
3000
  await this._goto(frame);
2860
3001
  this.emit("seeked", { frame: this.currentFrame, totalFrames: this.totalFrames });
2861
3002
  }
3003
+ /**
3004
+ * Draw the frame on screen again, quietly (no `seeking` / `seeked`). For a paused movie when something outside it that a layer reads has changed: a `three` layer whose `update` reads the page's own
3005
+ * state (a dragged object's place) shows it after `await movie.redraw()`. A playing movie needs no call: the next frame draws it.
3006
+ */
3007
+ async redraw() {
3008
+ this._requireReady("redraw");
3009
+ await this._quietly(() => this.gotoFrame(this.currentFrame, true));
3010
+ }
2862
3011
  /** Run `fn` with `seeking` / `seeked` silenced: the movie is moving its own playhead, not answering a seek. */
2863
3012
  async _quietly(fn) {
2864
3013
  this._quiet++;
@@ -3072,11 +3221,23 @@ var Movie = class {
3072
3221
  }
3073
3222
  /** Every layer under the canvas point, the one on top first (see `pick`). */
3074
3223
  pickAll(x, y, opts = {}) {
3075
- chunkTWZNOUM7_cjs.warnUnknownOptions("movie.pick()", opts, ["only", "ignore"]);
3224
+ chunkTWZNOUM7_cjs.warnUnknownOptions("movie.pick()", opts, ["only", "ignore", "alpha"]);
3076
3225
  this._requireReady("pick");
3077
3226
  if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error(`pixi-effects: movie.pick(x, y) needs two numbers (canvas pixels), got ${x}, ${y}. For a pointer event: movie.pick(...movie.canvasPoint(event))`);
3078
3227
  return pickScene(this._rootSequence, x, y, opts);
3079
3228
  }
3229
+ /**
3230
+ * Which layer the pointer is over, as callbacks: `onEnter(hit)` when the layer (or, in a `three` layer, the object) under the pointer changes to another, `onLeave(hit)` for the one it
3231
+ * left (also when the pointer leaves the picture). While the pointer rests inside, it is asked again on every frame, so a picture that moves under a still pointer is followed.
3232
+ * `{ only, ignore }` as for `pick`; `cursor: true` (or a CSS cursor) shows it while something is hovered. Returns the function that stops it. A page-side tool: an exported video has no pointer.
3233
+ */
3234
+ hover(opts = {}) {
3235
+ chunkTWZNOUM7_cjs.warnUnknownOptions("movie.hover()", opts, ["onEnter", "onLeave", "cursor", "only", "ignore"]);
3236
+ this._requireReady("hover");
3237
+ const canvas = this.app?.canvas;
3238
+ if (!canvas) throw new Error("pixi-effects: movie.hover needs the movie's canvas");
3239
+ return startHover({ canvas, pick: (x, y, o) => this.pick(x, y, o), point: (e) => this.canvasPoint(e) }, opts);
3240
+ }
3080
3241
  /** A pointer event's place in canvas pixels (the canvas may be shown larger or smaller than its own size): `movie.pick(...movie.canvasPoint(event))`. */
3081
3242
  canvasPoint(event) {
3082
3243
  this._requireReady("canvasPoint");
@@ -4607,5 +4768,5 @@ exports.splitText = splitText;
4607
4768
  exports.stagger = stagger;
4608
4769
  exports.wiggle = wiggle;
4609
4770
  exports.withFade = withFade;
4610
- //# sourceMappingURL=chunk-RN5FWGGM.cjs.map
4611
- //# sourceMappingURL=chunk-RN5FWGGM.cjs.map
4771
+ //# sourceMappingURL=chunk-BEGOKE7Q.cjs.map
4772
+ //# sourceMappingURL=chunk-BEGOKE7Q.cjs.map