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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.43.0
4
+
5
+ A `three` layer on WebGPU shares PixiJS's GPU device, and the tests stop depending on the gallery. Nothing breaks.
6
+
7
+ - **Changed (tests, nothing for users): no library test uses a gallery piece as its subject.** The tests whose subject is the gallery or the site are in `tests/site/` (`npm run test:site`); the pages made for the tests, formerly `examples/_checks/`, are in `tests/films/`; the player and the timeline viewer have films of their own; `noFalseWarnings` runs every test film.
8
+ - **New: a `three` layer with `renderer: 'webgpu'` draws on PixiJS's own GPU device (`shareGpu`, default `true`).** In a movie PixiJS draws on WebGPU, the layer renders into a texture that PixiJS samples directly: no canvas of its own, no copy. The picture is the same (0 of 255 different in every channel of nine pictures, with antialias, fog and tone mapping), it is faster (spike: 0.7 against 1.1 ms for one layer, 1.6 against 5.7 ms for ten), and the browser's limit of about 32 WebGPU canvases no longer applies (a 60-layer film draws every layer; such a layer is not counted by the "too many three layers" warning). `shareGpu: false` keeps a canvas of its own; PixiJS on WebGL or any failure of the shared route does the same (one warning, before `setup`). `movie.inspect()` shows `sharedGpu`. The default `renderer` stays `'webgl'`. `tests/films/three-renderers`, `three-budget`, `tests/core/gpuShare.test.ts`.
9
+
10
+ ## 0.42.0
11
+
12
+ Interaction on top of `pick`: hover, dragging, a hit that looks at the picture, and a fix for a false warning that every rebuilt page with a `three` layer showed since 0.40.0. Nothing breaks.
13
+
14
+ - **Fixed (found by an AI trial of the interactive features): a rebuilt `three` layer said the browser had taken its WebGL context.** `setVars`, `resize` and `rebuild` destroy the old layers; a three layer lets its context go with `forceContextLoss()`, and the loss event that follows was reported by the "the browser took this layer's WebGL context away" warning added in 0.40.0, though it was the layer's own doing. A page whose three layer changed a variable on a click showed a false warning. The layer now knows it is closing (`tests/three/budget.test.ts`; `tests/films/three-budget` rebuilds in Chrome and fails without the fix).
15
+ - **New: `movie.hover({ onEnter, onLeave })`.** Which layer the pointer is over (in a `three` layer, which object), as callbacks on top of `pick`: `onEnter(hit)` when the layer under the pointer changes to another, `onLeave(hit)` for the one it left (also when the pointer leaves the picture or the returned stop function is called). While the pointer rests inside it is asked again on every frame, so a picture that moves under a still pointer is followed (a runner leaves the pointer with no pointer event in between: tested in real Chrome). `{ only, ignore }`, `cursor: true` (or a CSS cursor). A page-side tool: an exported video has no pointer.
16
+ - **New: `pick` looks at the picture (`{ alpha }`), and an object of a `three` layer can be dragged (`hit.three.grab`, `movie.redraw()`).** `movie.pick(x, y, { alpha: 0.1 })` leaves out a pixel of an image, a video or a `canvas` layer whose alpha is at or below it (a transparent corner of a logo, the clear part of a drawn circle); off by default, nothing is read without it; shapes are hit by their outline, text and `shader` layers by their box (a shader's WebGL picture cannot be read back safely); a picture that cannot be read (another origin) stays a hit with one warning. `hit.three.grab('screen' | 'floor' | 'wall' | [nx, ny, nz])` fixes a plane through the point taken and `grab.move(x, y)` returns where the pointer's ray meets it and the `delta` from the point taken (null where it never does): the object follows the pointer exactly (checked in real Chrome on a wall and on a floor seen in perspective). `movie.redraw()` draws the paused frame again, quietly, for a layer that reads the page's own state. `pick`'s `only` / `ignore` take a group's name for everything in it (a card and its label: `only: ['card']`), as well as layer names and paths.
17
+ - **Recipes and docs, from three fresh-AI trials of `pick`, `hover` and dragging** (a fox that stops when clicked, a card dragged onto a zone that lights, a logo clicked by its picture and a cube dragged along a floor; each page checked in a real browser): the recipes `pick-click`, `drag-drop` (a flat layer, a zone that lights by a variable alpha, the grab and grabbing cursors, `touch-action: none`) and `drag-3d` (with a floor variant) are complete pages whose code is tested (`tests/films/drag-recipe`, `drag3d`, `interact`, `pick`); SKILL.md says how to test an interactive page with pointer events (`check` does not click); the `vars` doc says a rebuild runs a three layer's `setup` again, so a 3D drag keeps the object's place in the page and calls `redraw()`.
18
+
3
19
  ## 0.41.0
4
20
 
5
21
  `movie.pick` (what is under a point of the canvas) and a better fit of the light falloff in a `three` layer. Nothing breaks.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pixi-effects
2
2
 
3
- > **Status**: experimental — current release `0.41.0`. The API may still change between minor versions; see the [CHANGELOG](./CHANGELOG.md) for what each release changed.
3
+ > **Status**: experimental — current release `0.43.0`. The API may still change between minor versions; see the [CHANGELOG](./CHANGELOG.md) for what each release changed.
4
4
 
5
5
  **[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 43 pieces written as plain data by AI models · **[For AI →](https://yjmtmtk.github.io/pixi-effects/for-ai.html)** · what an AI reads and runs · **[Playground →](https://yjmtmtk.github.io/pixi-effects/examples/playground.html)** in the browser.
6
6
 
@@ -49,8 +49,8 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
49
49
  "imports": {
50
50
  "pixi.js": "https://esm.sh/pixi.js@8.22.0?bundle-deps",
51
51
  "mediabunny": "https://esm.sh/mediabunny",
52
- "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.41.0/dist/index.js",
53
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.41.0/dist/Controller.js"
52
+ "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.43.0/dist/index.js",
53
+ "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.43.0/dist/Controller.js"
54
54
  }
55
55
  }
56
56
  </script>
@@ -61,7 +61,7 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
61
61
  </script>
62
62
  ```
63
63
 
64
- Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.41.0/dist/index.js`) rather than a CDN-rebundled build such as `esm.sh/pixi-effects` or jsDelivr's `+esm`: the entries (`pixi-effects`, `…/controller`, `…/three`) share internal chunks, which only works when each file is served untouched.
64
+ Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.43.0/dist/index.js`) rather than a CDN-rebundled build such as `esm.sh/pixi-effects` or jsDelivr's `+esm`: the entries (`pixi-effects`, `…/controller`, `…/three`) share internal chunks, which only works when each file is served untouched.
65
65
 
66
66
  > **Using three.js?** Add two more entries (`three` and `pixi-effects/three`) to this importmap — see [Adding three.js](#adding-threejs-optional) below.
67
67
  >
@@ -78,7 +78,7 @@ Add two more entries to the importmap above: three.js itself, and the `pixi-effe
78
78
  "...": "(everything from the importmap above)",
79
79
  "three": "https://esm.sh/three@0.178.0",
80
80
  "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.178.0/examples/jsm/",
81
- "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.41.0/dist/three.js"
81
+ "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.43.0/dist/three.js"
82
82
  }
83
83
  }
84
84
  </script>
@@ -59,9 +59,9 @@
59
59
  "imports": {
60
60
  "pixi.js": "https://cdn.jsdelivr.net/npm/pixi.js@8.22.0/dist/pixi.min.mjs",
61
61
  "mediabunny": "https://cdn.jsdelivr.net/npm/mediabunny@1.61.3/+esm",
62
- "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.41.0/dist/index.js",
63
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.41.0/dist/Controller.js",
64
- "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.41.0/dist/present.js"
62
+ "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.43.0/dist/index.js",
63
+ "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.43.0/dist/Controller.js",
64
+ "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.43.0/dist/present.js"
65
65
  }
66
66
  }
67
67
  </script>
@@ -1,5 +1,5 @@
1
1
  import { Container } from 'pixi.js';
2
- import { S as SequenceSpec, C as CompositionShape } from './types-DIh93lFb.js';
2
+ import { S as SequenceSpec, C as CompositionShape } from './types-DiNHtdrS.js';
3
3
 
4
4
  /**
5
5
  * Resolves a routed keyframe path to a concrete tween target.
@@ -1,5 +1,5 @@
1
1
  import { Container } from 'pixi.js';
2
- import { S as SequenceSpec, C as CompositionShape } from './types-DIh93lFb.cjs';
2
+ import { S as SequenceSpec, C as CompositionShape } from './types-DiNHtdrS.cjs';
3
3
 
4
4
  /**
5
5
  * Resolves a routed keyframe path to a concrete tween target.
@@ -1,7 +1,7 @@
1
- import { M as Movie } from './Movie-DWtxRiJ2.cjs';
1
+ import { M as Movie } from './Movie-BdUm2Mc9.cjs';
2
2
  import 'pixi.js';
3
3
  import './soundEntry-DT13DNt8.cjs';
4
- import './types-DIh93lFb.cjs';
4
+ import './types-DiNHtdrS.cjs';
5
5
 
6
6
  /**
7
7
  * The look of the bar. Each key sets a CSS custom property on the bar (so you can also set `--mc-accent` … in your own CSS, on the
@@ -1,7 +1,7 @@
1
- import { M as Movie } from './Movie-CwqRLOzE.js';
1
+ import { M as Movie } from './Movie-B_qQIYV6.js';
2
2
  import 'pixi.js';
3
3
  import './soundEntry-DT13DNt8.js';
4
- import './types-DIh93lFb.js';
4
+ import './types-DiNHtdrS.js';
5
5
 
6
6
  /**
7
7
  * The look of the bar. Each key sets a CSS custom property on the bar (so you can also set `--mc-accent` … in your own CSS, on the
@@ -1,6 +1,6 @@
1
1
  import { Application } from 'pixi.js';
2
2
  import { A as AudioReport, a as AudioInspectOptions } from './soundEntry-DT13DNt8.js';
3
- import { b as Stop, A as AssetSpec, a as CompositionSpec } from './types-DIh93lFb.js';
3
+ import { b as Stop, A as AssetSpec, a as CompositionSpec } from './types-DiNHtdrS.js';
4
4
 
5
5
  type EaseFn = (p: number) => number;
6
6
 
@@ -213,6 +213,8 @@ interface LayerInfo {
213
213
  /** A `three` layer: the renderer it draws with (`'webgl'` / `'webgpu'`) and the backend under it (`'webgl2'`: three's WebGPURenderer found no WebGPU and used its own WebGL2 backend: the same picture). */
214
214
  renderer?: 'webgl' | 'webgpu';
215
215
  backend?: 'webgl' | 'webgpu' | 'webgl2';
216
+ /** A `three` layer with `renderer: 'webgpu'`: true when it draws on PixiJS's own GPU device (no canvas of its own); false when it has a canvas of its own (`shareGpu: false`, or PixiJS is on WebGL). */
217
+ sharedGpu?: boolean;
216
218
  }
217
219
  interface InspectReport {
218
220
  frame: number;
@@ -259,15 +261,45 @@ interface PickHit {
259
261
  point: [number, number, number];
260
262
  distance: number;
261
263
  object3d?: unknown;
264
+ /**
265
+ * Drag what the ray met: `grab(plane)` fixes a plane through the hit point (`'screen'` facing the camera, the default; `'floor'`; `'wall'`; or a normal `[x, y, z]`), and `move(x, y)` takes the pointer
266
+ * in canvas pixels and gives where its ray meets that plane (`point`) and how far that is from the hit point (`delta`): move the object by it. null where the ray never meets the plane.
267
+ */
268
+ grab?: (plane?: 'screen' | 'floor' | 'wall' | [number, number, number]) => {
269
+ move: (x: number, y: number) => {
270
+ point: [number, number, number];
271
+ delta: [number, number, number];
272
+ } | null;
273
+ };
262
274
  };
263
275
  }
264
276
  interface PickOptions {
265
- /** Only these layers can be hit (by `name` or by `path`). */
277
+ /**
278
+ * Look at the picture, not the box: a pixel of an image, a video or a `canvas` layer whose alpha is at or below this (0..1; `0.1` leaves out what is nearly clear) is not a hit.
279
+ * Off by default (the box is the picture, and nothing is read). Shapes are always hit by their outline, text and `shader` layers by their box; a picture that cannot be read (from another origin) stays a hit.
280
+ */
281
+ alpha?: number;
282
+ /** Only these layers can be hit (by `name` or by `path`; a group's name stands for everything in it). */
266
283
  only?: string[];
267
- /** These layers cannot be hit (by `name` or by `path`). */
284
+ /** These layers cannot be hit (by `name` or by `path`; a group's name stands for everything in it). */
268
285
  ignore?: string[];
269
286
  }
270
287
 
288
+ /**
289
+ * `movie.hover({ onEnter, onLeave })`: which layer (or which object of a three layer) the pointer is over, as two callbacks. It is a page-side tool on top of `movie.pick`: it listens to the
290
+ * canvas, asks `pick` where the pointer is, and calls `onEnter(hit)` when the layer under it changes to another and `onLeave(hit)` for the one it left. While the pointer rests inside the
291
+ * picture it asks again on every frame, so a layer that runs away from a still pointer is left. Nothing here is part of the video: an exported file has no pointer.
292
+ */
293
+
294
+ interface HoverOptions extends PickOptions {
295
+ /** The layer (or, in a `three` layer, the object) the pointer has just come onto. */
296
+ onEnter?: (hit: PickHit) => void;
297
+ /** The one it has just left (also when the pointer leaves the picture, or `stop()` is called). */
298
+ onLeave?: (hit: PickHit) => void;
299
+ /** A CSS cursor to show while something is hovered (`true` is `'pointer'`); it goes back when nothing is. */
300
+ cursor?: string | boolean;
301
+ }
302
+
271
303
  /** One layer (or a run of similar layers) on the timeline. Times are in seconds, absolute (from the start of the movie). */
272
304
  interface TimelineRow {
273
305
  /** `parent/child` for a layer inside a composition. */
@@ -645,6 +677,11 @@ declare class Movie {
645
677
  private _runRebuild;
646
678
  private _doRebuild;
647
679
  gotoFrame(frame: number, force?: boolean): Promise<void>;
680
+ /**
681
+ * 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
682
+ * state (a dragged object's place) shows it after `await movie.redraw()`. A playing movie needs no call: the next frame draws it.
683
+ */
684
+ redraw(): Promise<void>;
648
685
  /** Run `fn` with `seeking` / `seeked` silenced: the movie is moving its own playhead, not answering a seek. */
649
686
  private _quietly;
650
687
  private _emitError;
@@ -719,6 +756,12 @@ declare class Movie {
719
756
  pick(x: number, y: number, opts?: PickOptions): PickHit | null;
720
757
  /** Every layer under the canvas point, the one on top first (see `pick`). */
721
758
  pickAll(x: number, y: number, opts?: PickOptions): PickHit[];
759
+ /**
760
+ * 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
761
+ * 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.
762
+ * `{ 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.
763
+ */
764
+ hover(opts?: HoverOptions): () => void;
722
765
  /** 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))`. */
723
766
  canvasPoint(event: {
724
767
  clientX: number;
@@ -814,4 +857,4 @@ declare class Movie {
814
857
  destroy(): Promise<void>;
815
858
  }
816
859
 
817
- export { type ContactSheetOptions as C, type FontReport as F, type InspectOptions as I, type LayerInfo as L, Movie as M, type OnionSkinOptions as O, type PickHit as P, type RebuildOptions as R, type SnapshotOptions as S, type ThumbnailOptions as T, type VolumeEvent as V, type MotionBlurSpec as a, type FrameEvent as b, type InspectReport as c, type IssueGroup as d, type LoadProgressState as e, type LoadStage as f, type LoaderOption as g, type LoopEvent as h, type MotionBlurOptions as i, type MovieErrorEvent as j, type MovieOptions as k, type PickOptions as l, type ProgressEvent as m, type RebuildReason as n, type RebuildReport as o, type RenderOptions as p, type ReviewOptions as q, type ReviewReport as r, type TimelineData as s, type TimelineHtmlOptions as t, type TimelineRow as u, type TimelineSvgOptions as v, type TimelineTransition as w, dismissLoader as x, failLoader as y, resolveLoader as z };
860
+ export { type ContactSheetOptions as C, type FontReport as F, type HoverOptions as H, type InspectOptions as I, type LayerInfo as L, Movie as M, type OnionSkinOptions as O, type PickHit as P, type RebuildOptions as R, type SnapshotOptions as S, type ThumbnailOptions as T, type VolumeEvent as V, type MotionBlurSpec as a, type FrameEvent as b, type InspectReport as c, type IssueGroup as d, type LoadProgressState as e, type LoadStage as f, type LoaderOption as g, type LoopEvent as h, type MotionBlurOptions as i, type MovieErrorEvent as j, type MovieOptions as k, type PickOptions as l, type ProgressEvent as m, type RebuildReason as n, type RebuildReport as o, type RenderOptions as p, type ReviewOptions as q, type ReviewReport as r, type TimelineData as s, type TimelineHtmlOptions as t, type TimelineRow as u, type TimelineSvgOptions as v, type TimelineTransition as w, dismissLoader as x, failLoader as y, resolveLoader as z };
@@ -1,6 +1,6 @@
1
1
  import { Application } from 'pixi.js';
2
2
  import { A as AudioReport, a as AudioInspectOptions } from './soundEntry-DT13DNt8.cjs';
3
- import { b as Stop, A as AssetSpec, a as CompositionSpec } from './types-DIh93lFb.cjs';
3
+ import { b as Stop, A as AssetSpec, a as CompositionSpec } from './types-DiNHtdrS.cjs';
4
4
 
5
5
  type EaseFn = (p: number) => number;
6
6
 
@@ -213,6 +213,8 @@ interface LayerInfo {
213
213
  /** A `three` layer: the renderer it draws with (`'webgl'` / `'webgpu'`) and the backend under it (`'webgl2'`: three's WebGPURenderer found no WebGPU and used its own WebGL2 backend: the same picture). */
214
214
  renderer?: 'webgl' | 'webgpu';
215
215
  backend?: 'webgl' | 'webgpu' | 'webgl2';
216
+ /** A `three` layer with `renderer: 'webgpu'`: true when it draws on PixiJS's own GPU device (no canvas of its own); false when it has a canvas of its own (`shareGpu: false`, or PixiJS is on WebGL). */
217
+ sharedGpu?: boolean;
216
218
  }
217
219
  interface InspectReport {
218
220
  frame: number;
@@ -259,15 +261,45 @@ interface PickHit {
259
261
  point: [number, number, number];
260
262
  distance: number;
261
263
  object3d?: unknown;
264
+ /**
265
+ * Drag what the ray met: `grab(plane)` fixes a plane through the hit point (`'screen'` facing the camera, the default; `'floor'`; `'wall'`; or a normal `[x, y, z]`), and `move(x, y)` takes the pointer
266
+ * in canvas pixels and gives where its ray meets that plane (`point`) and how far that is from the hit point (`delta`): move the object by it. null where the ray never meets the plane.
267
+ */
268
+ grab?: (plane?: 'screen' | 'floor' | 'wall' | [number, number, number]) => {
269
+ move: (x: number, y: number) => {
270
+ point: [number, number, number];
271
+ delta: [number, number, number];
272
+ } | null;
273
+ };
262
274
  };
263
275
  }
264
276
  interface PickOptions {
265
- /** Only these layers can be hit (by `name` or by `path`). */
277
+ /**
278
+ * Look at the picture, not the box: a pixel of an image, a video or a `canvas` layer whose alpha is at or below this (0..1; `0.1` leaves out what is nearly clear) is not a hit.
279
+ * Off by default (the box is the picture, and nothing is read). Shapes are always hit by their outline, text and `shader` layers by their box; a picture that cannot be read (from another origin) stays a hit.
280
+ */
281
+ alpha?: number;
282
+ /** Only these layers can be hit (by `name` or by `path`; a group's name stands for everything in it). */
266
283
  only?: string[];
267
- /** These layers cannot be hit (by `name` or by `path`). */
284
+ /** These layers cannot be hit (by `name` or by `path`; a group's name stands for everything in it). */
268
285
  ignore?: string[];
269
286
  }
270
287
 
288
+ /**
289
+ * `movie.hover({ onEnter, onLeave })`: which layer (or which object of a three layer) the pointer is over, as two callbacks. It is a page-side tool on top of `movie.pick`: it listens to the
290
+ * canvas, asks `pick` where the pointer is, and calls `onEnter(hit)` when the layer under it changes to another and `onLeave(hit)` for the one it left. While the pointer rests inside the
291
+ * picture it asks again on every frame, so a layer that runs away from a still pointer is left. Nothing here is part of the video: an exported file has no pointer.
292
+ */
293
+
294
+ interface HoverOptions extends PickOptions {
295
+ /** The layer (or, in a `three` layer, the object) the pointer has just come onto. */
296
+ onEnter?: (hit: PickHit) => void;
297
+ /** The one it has just left (also when the pointer leaves the picture, or `stop()` is called). */
298
+ onLeave?: (hit: PickHit) => void;
299
+ /** A CSS cursor to show while something is hovered (`true` is `'pointer'`); it goes back when nothing is. */
300
+ cursor?: string | boolean;
301
+ }
302
+
271
303
  /** One layer (or a run of similar layers) on the timeline. Times are in seconds, absolute (from the start of the movie). */
272
304
  interface TimelineRow {
273
305
  /** `parent/child` for a layer inside a composition. */
@@ -645,6 +677,11 @@ declare class Movie {
645
677
  private _runRebuild;
646
678
  private _doRebuild;
647
679
  gotoFrame(frame: number, force?: boolean): Promise<void>;
680
+ /**
681
+ * 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
682
+ * state (a dragged object's place) shows it after `await movie.redraw()`. A playing movie needs no call: the next frame draws it.
683
+ */
684
+ redraw(): Promise<void>;
648
685
  /** Run `fn` with `seeking` / `seeked` silenced: the movie is moving its own playhead, not answering a seek. */
649
686
  private _quietly;
650
687
  private _emitError;
@@ -719,6 +756,12 @@ declare class Movie {
719
756
  pick(x: number, y: number, opts?: PickOptions): PickHit | null;
720
757
  /** Every layer under the canvas point, the one on top first (see `pick`). */
721
758
  pickAll(x: number, y: number, opts?: PickOptions): PickHit[];
759
+ /**
760
+ * 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
761
+ * 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.
762
+ * `{ 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.
763
+ */
764
+ hover(opts?: HoverOptions): () => void;
722
765
  /** 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))`. */
723
766
  canvasPoint(event: {
724
767
  clientX: number;
@@ -814,4 +857,4 @@ declare class Movie {
814
857
  destroy(): Promise<void>;
815
858
  }
816
859
 
817
- export { type ContactSheetOptions as C, type FontReport as F, type InspectOptions as I, type LayerInfo as L, Movie as M, type OnionSkinOptions as O, type PickHit as P, type RebuildOptions as R, type SnapshotOptions as S, type ThumbnailOptions as T, type VolumeEvent as V, type MotionBlurSpec as a, type FrameEvent as b, type InspectReport as c, type IssueGroup as d, type LoadProgressState as e, type LoadStage as f, type LoaderOption as g, type LoopEvent as h, type MotionBlurOptions as i, type MovieErrorEvent as j, type MovieOptions as k, type PickOptions as l, type ProgressEvent as m, type RebuildReason as n, type RebuildReport as o, type RenderOptions as p, type ReviewOptions as q, type ReviewReport as r, type TimelineData as s, type TimelineHtmlOptions as t, type TimelineRow as u, type TimelineSvgOptions as v, type TimelineTransition as w, dismissLoader as x, failLoader as y, resolveLoader as z };
860
+ export { type ContactSheetOptions as C, type FontReport as F, type HoverOptions as H, type InspectOptions as I, type LayerInfo as L, Movie as M, type OnionSkinOptions as O, type PickHit as P, type RebuildOptions as R, type SnapshotOptions as S, type ThumbnailOptions as T, type VolumeEvent as V, type MotionBlurSpec as a, type FrameEvent as b, type InspectReport as c, type IssueGroup as d, type LoadProgressState as e, type LoadStage as f, type LoaderOption as g, type LoopEvent as h, type MotionBlurOptions as i, type MovieErrorEvent as j, type MovieOptions as k, type PickOptions as l, type ProgressEvent as m, type RebuildReason as n, type RebuildReport as o, type RenderOptions as p, type ReviewOptions as q, type ReviewReport as r, type TimelineData as s, type TimelineHtmlOptions as t, type TimelineRow as u, type TimelineSvgOptions as v, type TimelineTransition as w, dismissLoader as x, failLoader as y, resolveLoader as z };
@@ -7,6 +7,21 @@ import { warnUnknownOptions, suggestName } from './chunk-BXOCEZ4U.js';
7
7
  import { extensions, CullerPlugin, Application, Container, Rectangle, Culler, CanvasTextMetrics, TextStyle, Assets, ExtensionType, Filter, Texture, UniformGroup, GpuProgram, GlProgram, defaultFilterVert } from 'pixi.js';
8
8
  import { Output, BufferTarget, CanvasSource, AudioBufferSource, Input, ALL_FORMATS, BlobSource, WavOutputFormat, Conversion, VideoSampleSink, OggOutputFormat, QUALITY_VERY_HIGH, QUALITY_HIGH, QUALITY_MEDIUM, QUALITY_LOW, QUALITY_VERY_LOW, MkvOutputFormat, WebMOutputFormat, MovOutputFormat, Mp4OutputFormat, canEncodeVideo, getFirstEncodableAudioCodec } from 'mediabunny';
9
9
 
10
+ // src/core/gpuShare.ts
11
+ function sharedGpuOf(renderer) {
12
+ const r = renderer;
13
+ if (!r || r.name !== "webgpu" || !r.gpu?.device) return void 0;
14
+ const uid = r.uid;
15
+ return {
16
+ device: r.gpu.device,
17
+ adopt(source, texture) {
18
+ const data = source._gpuData;
19
+ if (!data || typeof data !== "object") throw new Error("this PixiJS keeps no _gpuData on its texture sources");
20
+ data[uid] = { textureView: null, textureViews: /* @__PURE__ */ Object.create(null), gpuTexture: texture, destroy() {
21
+ } };
22
+ }
23
+ };
24
+ }
10
25
  var registered = false;
11
26
  function ensureLoadersRegistered(audioContext) {
12
27
  if (registered) return;
@@ -1634,6 +1649,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1634
1649
  if (typeof drawn.renderer === "string") {
1635
1650
  info.renderer = drawn.renderer;
1636
1651
  if (typeof drawn.backend === "string") info.backend = drawn.backend;
1652
+ if (info.renderer === "webgpu") info.sharedGpu = drawn.sharedGpu === true;
1637
1653
  }
1638
1654
  layers.push(info);
1639
1655
  chainAlpha.set(info, parentAlpha * alpha);
@@ -1730,6 +1746,23 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1730
1746
  }
1731
1747
 
1732
1748
  // src/core/pick.ts
1749
+ var scratch;
1750
+ var saidBlind = /* @__PURE__ */ new Set();
1751
+ var readResourceAlpha = (resource, x, y) => {
1752
+ try {
1753
+ if (scratch === void 0) {
1754
+ const c = document.createElement("canvas");
1755
+ c.width = c.height = 1;
1756
+ scratch = c.getContext("2d", { willReadFrequently: true });
1757
+ }
1758
+ if (!scratch) return null;
1759
+ scratch.clearRect(0, 0, 1, 1);
1760
+ scratch.drawImage(resource, x, y, 1, 1, 0, 0, 1, 1);
1761
+ return scratch.getImageData(0, 0, 1, 1).data[3] / 255;
1762
+ } catch {
1763
+ return null;
1764
+ }
1765
+ };
1733
1766
  function pointInQuad(q, x, y) {
1734
1767
  let pos = 0, neg = 0;
1735
1768
  for (let i = 0; i < 4; i++) {
@@ -1754,11 +1787,14 @@ var above = (a, b) => {
1754
1787
  }
1755
1788
  return b.length - a.length;
1756
1789
  };
1757
- function pickScene(root, x, y, opts = {}) {
1790
+ function pickScene(root, x, y, opts = {}, deps = {}) {
1791
+ 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}`);
1792
+ const readAlpha = deps.readAlpha ?? readResourceAlpha;
1758
1793
  const found = [];
1794
+ 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));
1759
1795
  const wanted = (name, path) => {
1760
- if (opts.ignore && (opts.ignore.includes(path) || name !== void 0 && opts.ignore.includes(name))) return false;
1761
- if (opts.only && !(opts.only.includes(path) || name !== void 0 && opts.only.includes(name))) return false;
1796
+ if (opts.ignore && names(opts.ignore, name, path)) return false;
1797
+ if (opts.only && !names(opts.only, name, path)) return false;
1762
1798
  return true;
1763
1799
  };
1764
1800
  const inside = (b, p) => p.x >= b.x && p.x <= b.x + b.width && p.y >= b.y && p.y <= b.y + b.height;
@@ -1788,8 +1824,34 @@ function pickScene(root, x, y, opts = {}) {
1788
1824
  const lb = d.getLocalBounds();
1789
1825
  if (inside(lb, local)) {
1790
1826
  const three = typeof seq.pickAt === "function" ? seq.pickAt((local.x - lb.x) / lb.width, (local.y - lb.y) / lb.height) : void 0;
1791
- if (three) hit = { name, path, type: seq.spec.type, kind: "three", point, three };
1792
- 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 } };
1827
+ if (three) {
1828
+ const planeOf = seq.dragPlane;
1829
+ if (planeOf) {
1830
+ three.grab = (plane = "screen") => {
1831
+ const along = planeOf.call(seq, three.point, plane);
1832
+ return { move: (cx, cy) => {
1833
+ const lp = d.toLocal({ x: cx, y: cy });
1834
+ const b = d.getLocalBounds();
1835
+ return along((lp.x - b.x) / b.width, (lp.y - b.y) / b.height);
1836
+ } };
1837
+ };
1838
+ }
1839
+ hit = { name, path, type: seq.spec.type, kind: "three", point, three };
1840
+ } else if (three === void 0 && (typeof d.containsPoint !== "function" || d.containsPoint(local))) {
1841
+ let opaque = true;
1842
+ const tex = opts.alpha !== void 0 && seq.spec.type !== "shader" ? d.texture : void 0;
1843
+ if (tex?.source?.resource) {
1844
+ const res = tex.source.resolution ?? 1;
1845
+ 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);
1846
+ if (a === null) {
1847
+ if (!saidBlind.has(path)) {
1848
+ saidBlind.add(path);
1849
+ 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`);
1850
+ }
1851
+ } else opaque = a > opts.alpha;
1852
+ }
1853
+ if (opaque) hit = { name, path, type: seq.spec.type, kind: "flat", point, local: { x: local.x - lb.x, y: local.y - lb.y } };
1854
+ }
1793
1855
  }
1794
1856
  }
1795
1857
  if (!hit) return;
@@ -1803,6 +1865,82 @@ function pickScene(root, x, y, opts = {}) {
1803
1865
  return found.sort((a, b) => above(a.order, b.order)).map((f) => f.hit);
1804
1866
  }
1805
1867
 
1868
+ // src/core/hover.ts
1869
+ var keyOf = (h) => h ? `${h.path}${h.three ? `#${h.three.object}` : ""}` : null;
1870
+ function startHover(host, opts = {}) {
1871
+ const { canvas } = host;
1872
+ const request = host.requestFrame ?? ((fn) => requestAnimationFrame(fn));
1873
+ const cancel = host.cancelFrame ?? ((id) => cancelAnimationFrame(id));
1874
+ const cursor = opts.cursor === true ? "pointer" : opts.cursor || "";
1875
+ const pickOpts = {};
1876
+ if (opts.only) pickOpts.only = opts.only;
1877
+ if (opts.ignore) pickOpts.ignore = opts.ignore;
1878
+ let current = null;
1879
+ let last = null;
1880
+ let loop = null;
1881
+ let said = false;
1882
+ const call = (fn, hit) => {
1883
+ if (!fn) return;
1884
+ try {
1885
+ fn(hit);
1886
+ } catch (err) {
1887
+ if (!said) {
1888
+ said = true;
1889
+ console.warn("pixi-effects: movie.hover: a handler threw (said once) \u2014", err);
1890
+ }
1891
+ }
1892
+ };
1893
+ const setCursor = (on) => {
1894
+ if (cursor) canvas.style.cursor = on ? cursor : "";
1895
+ };
1896
+ const evaluate = () => {
1897
+ if (!last) return;
1898
+ const [x, y] = host.point(last);
1899
+ const hit = host.pick(x, y, pickOpts);
1900
+ if (keyOf(hit) === keyOf(current)) {
1901
+ current = hit ?? current;
1902
+ return;
1903
+ }
1904
+ const was = current;
1905
+ current = hit;
1906
+ if (was) call(opts.onLeave, was);
1907
+ if (hit) call(opts.onEnter, hit);
1908
+ setCursor(!!hit);
1909
+ };
1910
+ const tick = () => {
1911
+ loop = null;
1912
+ if (last) {
1913
+ evaluate();
1914
+ loop = request(tick);
1915
+ }
1916
+ };
1917
+ const onMove = (event) => {
1918
+ last = event;
1919
+ evaluate();
1920
+ if (loop === null) loop = request(tick);
1921
+ };
1922
+ const onLeave = () => {
1923
+ last = null;
1924
+ if (loop !== null) {
1925
+ cancel(loop);
1926
+ loop = null;
1927
+ }
1928
+ const was = current;
1929
+ current = null;
1930
+ if (was) call(opts.onLeave, was);
1931
+ setCursor(false);
1932
+ };
1933
+ canvas.addEventListener("pointermove", onMove);
1934
+ canvas.addEventListener("pointerdown", onMove);
1935
+ canvas.addEventListener("pointerleave", onLeave);
1936
+ return () => {
1937
+ canvas.removeEventListener("pointermove", onMove);
1938
+ canvas.removeEventListener("pointerdown", onMove);
1939
+ canvas.removeEventListener("pointerleave", onLeave);
1940
+ onLeave();
1941
+ };
1942
+ }
1943
+
1806
1944
  // src/core/timelineChart.ts
1807
1945
  var GROUP_MIN = 4;
1808
1946
  var MAX_KEYS = 200;
@@ -2605,12 +2743,15 @@ var Movie = class {
2605
2743
  */
2606
2744
  async _construct(composition, vars, width, height, lp, sizeWins = this._resized) {
2607
2745
  const spec = applyVars(composition, vars);
2746
+ const gpu = sharedGpuOf(this.app?.renderer);
2747
+ const gpuField = gpu ? { gpu } : {};
2608
2748
  const rootShape = {
2609
2749
  width,
2610
2750
  height,
2611
2751
  duration: this.duration,
2612
2752
  frameRate: this.frameRate,
2613
2753
  vars: numericVars(vars),
2754
+ ...gpuField,
2614
2755
  ...lp ? { onLayerBuilt: async () => {
2615
2756
  lp.tick();
2616
2757
  await lp.yieldIfDue();
@@ -2857,6 +2998,14 @@ var Movie = class {
2857
2998
  await this._goto(frame);
2858
2999
  this.emit("seeked", { frame: this.currentFrame, totalFrames: this.totalFrames });
2859
3000
  }
3001
+ /**
3002
+ * 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
3003
+ * state (a dragged object's place) shows it after `await movie.redraw()`. A playing movie needs no call: the next frame draws it.
3004
+ */
3005
+ async redraw() {
3006
+ this._requireReady("redraw");
3007
+ await this._quietly(() => this.gotoFrame(this.currentFrame, true));
3008
+ }
2860
3009
  /** Run `fn` with `seeking` / `seeked` silenced: the movie is moving its own playhead, not answering a seek. */
2861
3010
  async _quietly(fn) {
2862
3011
  this._quiet++;
@@ -3070,11 +3219,23 @@ var Movie = class {
3070
3219
  }
3071
3220
  /** Every layer under the canvas point, the one on top first (see `pick`). */
3072
3221
  pickAll(x, y, opts = {}) {
3073
- warnUnknownOptions("movie.pick()", opts, ["only", "ignore"]);
3222
+ warnUnknownOptions("movie.pick()", opts, ["only", "ignore", "alpha"]);
3074
3223
  this._requireReady("pick");
3075
3224
  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))`);
3076
3225
  return pickScene(this._rootSequence, x, y, opts);
3077
3226
  }
3227
+ /**
3228
+ * 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
3229
+ * 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.
3230
+ * `{ 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.
3231
+ */
3232
+ hover(opts = {}) {
3233
+ warnUnknownOptions("movie.hover()", opts, ["onEnter", "onLeave", "cursor", "only", "ignore"]);
3234
+ this._requireReady("hover");
3235
+ const canvas = this.app?.canvas;
3236
+ if (!canvas) throw new Error("pixi-effects: movie.hover needs the movie's canvas");
3237
+ return startHover({ canvas, pick: (x, y, o) => this.pick(x, y, o), point: (e) => this.canvasPoint(e) }, opts);
3238
+ }
3078
3239
  /** 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))`. */
3079
3240
  canvasPoint(event) {
3080
3241
  this._requireReady("canvasPoint");
@@ -4582,5 +4743,5 @@ function withFade(spec, opts) {
4582
4743
  }
4583
4744
 
4584
4745
  export { Movie, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, dismissLoader, failLoader, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, musicEvents, orbit, parseEase2 as parseEase, particles, react, resolveLoader, samplePath, splitText, stagger, wiggle, withFade };
4585
- //# sourceMappingURL=chunk-Y2N6U3X5.js.map
4586
- //# sourceMappingURL=chunk-Y2N6U3X5.js.map
4746
+ //# sourceMappingURL=chunk-6N5ZBUOU.js.map
4747
+ //# sourceMappingURL=chunk-6N5ZBUOU.js.map