melonjs 20.1.0 → 20.2.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 (48) hide show
  1. package/README.md +2 -2
  2. package/build/application/settings.d.ts +3 -2
  3. package/build/application/settings.d.ts.map +1 -1
  4. package/build/index.js +1399 -229
  5. package/build/index.js.map +4 -4
  6. package/build/input/pointerevent.d.ts.map +1 -1
  7. package/build/level/gltf/GLTFModel.d.ts +17 -1
  8. package/build/level/gltf/GLTFModel.d.ts.map +1 -1
  9. package/build/loader/parsers/fontface.d.ts.map +1 -1
  10. package/build/particles/emitter.d.ts +81 -3
  11. package/build/particles/emitter.d.ts.map +1 -1
  12. package/build/particles/particle.d.ts +62 -1
  13. package/build/particles/particle.d.ts.map +1 -1
  14. package/build/particles/settings.d.ts +50 -7
  15. package/build/particles/settings.d.ts.map +1 -1
  16. package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
  17. package/build/physics/builtin/detector.d.ts.map +1 -1
  18. package/build/renderable/container.d.ts +10 -0
  19. package/build/renderable/container.d.ts.map +1 -1
  20. package/build/renderable/entity/entity.d.ts +10 -0
  21. package/build/renderable/entity/entity.d.ts.map +1 -1
  22. package/build/renderable/frameAnimation.d.ts +20 -2
  23. package/build/renderable/frameAnimation.d.ts.map +1 -1
  24. package/build/renderable/renderable.d.ts +106 -22
  25. package/build/renderable/renderable.d.ts.map +1 -1
  26. package/build/system/timer.d.ts.map +1 -1
  27. package/build/video/blendmodes.d.ts +107 -0
  28. package/build/video/blendmodes.d.ts.map +1 -0
  29. package/build/video/canvas/canvas_renderer.d.ts +28 -24
  30. package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
  31. package/build/video/effects/blendEffect.d.ts +57 -0
  32. package/build/video/effects/blendEffect.d.ts.map +1 -0
  33. package/build/video/renderer.d.ts +3 -4
  34. package/build/video/renderer.d.ts.map +1 -1
  35. package/build/video/rendertarget/canvasrendertarget.d.ts +1 -0
  36. package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
  37. package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
  38. package/build/video/webgl/glshader.d.ts.map +1 -1
  39. package/build/video/webgl/utils/program.d.ts.map +1 -1
  40. package/build/video/webgl/webgl_renderer.d.ts +116 -29
  41. package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
  42. package/build/video/webgpu/batchers/quad_batcher.d.ts +7 -0
  43. package/build/video/webgpu/batchers/quad_batcher.d.ts.map +1 -1
  44. package/build/video/webgpu/pipeline/cache.d.ts +0 -9
  45. package/build/video/webgpu/pipeline/cache.d.ts.map +1 -1
  46. package/build/video/webgpu/webgpu_renderer.d.ts +100 -6
  47. package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
  48. package/package.json +3 -3
package/build/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * melonJS Game Engine - 20.1.0
2
+ * melonJS Game Engine - 20.2.0
3
3
  * http://www.melonjs.org
4
4
  * melonjs is licensed under the MIT License.
5
5
  * http://www.opensource.org/licenses/mit-license
@@ -7770,6 +7770,20 @@ var CanvasRenderTarget = class extends RenderTarget {
7770
7770
  * @param {CanvasRenderer|WebGLRenderer} renderer - the renderer to which this canvas texture is attached
7771
7771
  */
7772
7772
  invalidate(renderer) {
7773
+ const busy = renderer._advancedBlendBusy;
7774
+ if (typeof busy === "number") {
7775
+ renderer._advancedBlendBusy = busy + 1;
7776
+ }
7777
+ try {
7778
+ this.#invalidate(renderer);
7779
+ } finally {
7780
+ if (typeof busy === "number") {
7781
+ renderer._advancedBlendBusy = busy;
7782
+ }
7783
+ }
7784
+ }
7785
+ /** @ignore */
7786
+ #invalidate(renderer) {
7773
7787
  if (renderer.type.startsWith("WebGL")) {
7774
7788
  renderer.flush();
7775
7789
  renderer.setBatcher("quad");
@@ -10807,11 +10821,10 @@ var Renderer = class _Renderer {
10807
10821
  return this.currentBlendMode;
10808
10822
  }
10809
10823
  /**
10810
- * set the current blend mode.
10811
- * Subclasses (CanvasRenderer, WebGLRenderer, WebGPURenderer once it
10812
- * lands) implement the actual backend logic.
10824
+ * set the current blend mode. Every renderer supports the same set —
10825
+ * see {@link CanvasRenderer#setBlendMode} for the list and what each does.
10813
10826
  * @param {string} [mode="normal"] - blend mode
10814
- * @param {boolean} [premultipliedAlpha=true] - whether textures use premultiplied alpha (GPU renderers only)
10827
+ * @param {boolean} [premultipliedAlpha=true] - whether textures use premultiplied alpha
10815
10828
  */
10816
10829
  setBlendMode(mode = "normal") {
10817
10830
  this.currentBlendMode = mode;
@@ -12366,7 +12379,10 @@ var Timer = class {
12366
12379
  * @ignore
12367
12380
  */
12368
12381
  updateTimers() {
12369
- for (const timer2 of this.timers) {
12382
+ for (const timer2 of [...this.timers]) {
12383
+ if (!this.timers.includes(timer2)) {
12384
+ continue;
12385
+ }
12370
12386
  if (!(timer2.pauseable && state_default.isPaused())) {
12371
12387
  timer2.elapsed += this.delta;
12372
12388
  }
@@ -17549,6 +17565,8 @@ var Body = class {
17549
17565
  };
17550
17566
 
17551
17567
  // src/renderable/renderable.js
17568
+ var _chain = [];
17569
+ var _level = new Matrix3d();
17552
17570
  var Renderable = class _Renderable extends Rect {
17553
17571
  /**
17554
17572
  * @param {number} x - position of the renderable object (accessible through inherited pos.x property)
@@ -17607,7 +17625,7 @@ var Renderable = class _Renderable extends Rect {
17607
17625
  */
17608
17626
  get parentApp() {
17609
17627
  if (!this._parentApp && this.ancestor) {
17610
- this._parentApp = this.ancestor.getRootAncestor().app;
17628
+ this._parentApp = this.ancestor.getRootAncestor()?.app;
17611
17629
  }
17612
17630
  return this._parentApp;
17613
17631
  }
@@ -17983,13 +18001,33 @@ var Renderable = class _Renderable extends Rect {
17983
18001
  }
17984
18002
  }
17985
18003
  /**
17986
- * return the renderable absolute position in the game world. The
17987
- * returned vector is a {@link Vector3d} so the z component is summed
17988
- * across the ancestor chain too — important for {@link Camera3d}'s
17989
- * frustum culling, which previously read `obj.depth` (local
17990
- * `pos.z`) and mis-culled children nested under a container with
17991
- * its own non-zero depth.
17992
- * @returns {Vector3d}
18004
+ * Where this renderable IS in the game world — its own `pos` plus every
18005
+ * ancestor's, as a {@link Vector3d} so the z component is summed across
18006
+ * the chain too (important for {@link Camera3d}'s frustum culling, which
18007
+ * previously read `obj.depth` — local `pos.z` — and mis-culled children
18008
+ * nested under a container with its own non-zero depth).
18009
+ *
18010
+ * **Reach for this** for anything positional: culling, distance checks,
18011
+ * hit tests, placing one renderable relative to another. It is cheap, and
18012
+ * it is what the engine's own culling uses.
18013
+ *
18014
+ * **Reach for {@link Renderable#getWorldTransform} instead** when a
18015
+ * position is not enough — when rotation, scale or flip along the ancestor
18016
+ * chain matters, or when you need to map an arbitrary point rather than
18017
+ * just the origin. This method sums translations only, so under a rotated
18018
+ * or scaled ancestor it reports where the renderable's *pivot* is and
18019
+ * nothing about how its content is oriented.
18020
+ *
18021
+ * Note the two also frame the question differently. This one is "where am
18022
+ * I"; `getWorldTransform()` is "what space is my content drawn in". For a
18023
+ * {@link Container} those coincide, because a container offsets its
18024
+ * children by its own position. For a leaf they differ by exactly that
18025
+ * position, which a leaf applies inside its own `draw()`.
18026
+ *
18027
+ * The returned vector is pooled and reused — copy it if you need to hold
18028
+ * onto the value across another call.
18029
+ * @returns {Vector3d} this renderable's absolute position
18030
+ * @see Renderable#getWorldTransform
17993
18031
  */
17994
18032
  getAbsolutePosition() {
17995
18033
  if (typeof this._absPos === "undefined") {
@@ -18001,6 +18039,121 @@ var Renderable = class _Renderable extends Rect {
18001
18039
  }
18002
18040
  return this._absPos;
18003
18041
  }
18042
+ /**
18043
+ * The transform this renderable interposes between its ancestor's frame
18044
+ * and the frame its own content is drawn in — a mirror of what
18045
+ * {@link Renderable#preDraw} applies to the renderer, as a matrix.
18046
+ *
18047
+ * **This is not {@link Renderable#currentTransform}.** A renderable's
18048
+ * placement is split across two members: `pos` holds where it is, and
18049
+ * `currentTransform` holds only what `rotate()` / `scale()` / `translate()`
18050
+ * accumulate — it never contains the position. `preDraw` composes the two
18051
+ * by conjugation, so a rotation pivots about the renderable's position
18052
+ * rather than the origin. On a renderable you never rotated,
18053
+ * `currentTransform` is therefore the *identity* and says nothing about
18054
+ * where its content lands, while this method returns the translation that
18055
+ * actually places it.
18056
+ *
18057
+ * {@link Container} extends this with the offset it applies to its
18058
+ * children, which a leaf renderable does not have: a leaf's own `draw()`
18059
+ * places itself from `pos`.
18060
+ * @protected
18061
+ * @param {Matrix3d} out - matrix to write into; nothing is stored on the
18062
+ * renderable itself, so callers own the lifetime
18063
+ * @returns {Matrix3d} `out`, for chaining
18064
+ * @see Renderable#getWorldTransform
18065
+ */
18066
+ getLocalTransform(out) {
18067
+ const ax = Number.isFinite(this.width) ? this.width * this.anchorPoint.x : 0;
18068
+ const ay = Number.isFinite(this.height) ? this.height * this.anchorPoint.y : 0;
18069
+ out.identity();
18070
+ if (this._flip.x || this._flip.y) {
18071
+ const dx = this._flip.x ? this.centerX - ax : 0;
18072
+ const dy = this._flip.y ? this.centerY - ay : 0;
18073
+ out.translate(dx, dy);
18074
+ out.scale(this._flip.x ? -1 : 1, this._flip.y ? -1 : 1);
18075
+ out.translate(-dx, -dy);
18076
+ }
18077
+ if (this.autoTransform === true && !this.currentTransform.isIdentity()) {
18078
+ out.translate(this.pos.x, this.pos.y);
18079
+ out.multiply(this.currentTransform);
18080
+ out.translate(-this.pos.x, -this.pos.y);
18081
+ }
18082
+ if (this.applyAnchorTransform !== false) {
18083
+ out.translate(-ax, -ay);
18084
+ }
18085
+ return out;
18086
+ }
18087
+ /**
18088
+ * The space this renderable's content is drawn IN, as a matrix — the full
18089
+ * form of {@link Renderable#getAbsolutePosition}, which sums positions up
18090
+ * the ancestor chain and therefore cannot represent the rotation, scale or
18091
+ * flip accumulated along the way.
18092
+ *
18093
+ * **Reach for `getAbsolutePosition()` instead** for ordinary positional
18094
+ * work — culling, distance checks, hit tests. It is cheaper and it is what
18095
+ * the engine culls with. **Use this** when a position is not enough:
18096
+ *
18097
+ * - an ancestor is rotated or scaled, so a translation cannot describe the
18098
+ * result
18099
+ * - you need to map an arbitrary point, not just the origin — a corner, a
18100
+ * click position, one renderable's coordinates into another's space
18101
+ * - you need to compose or invert the transform (`inv(A) · B` converts
18102
+ * between two frames, which is how `ParticleEmitter.referenceSpace`
18103
+ * measures particles against a container that is not their parent)
18104
+ *
18105
+ * The two also frame the question differently, and it shows on a leaf.
18106
+ * `getAbsolutePosition()` is "where am I"; this is "what space is my
18107
+ * content drawn in". For a {@link Container} those coincide, because a
18108
+ * container offsets its children by its own position. For a leaf they
18109
+ * differ by exactly that position, which a leaf applies inside its own
18110
+ * `draw()`. So with no rotation, scale or flip anywhere, a container's
18111
+ * translation column equals its `getAbsolutePosition()` while a leaf's
18112
+ * equals its PARENT's.
18113
+ *
18114
+ * The walk stops at a `floating` ancestor, because a floating renderable
18115
+ * draws in screen space: {@link Container#draw} resets the transform
18116
+ * outright for those, so the chain genuinely ends there rather than
18117
+ * continuing to the root.
18118
+ *
18119
+ * The camera needs no special handling — {@link Camera2d} folds its view
18120
+ * transform into the root container's `currentTransform`, so it is picked
18121
+ * up like any other level.
18122
+ * @param {Matrix3d} out - matrix to write into; nothing is stored on the
18123
+ * renderable itself, so callers own the lifetime
18124
+ * @returns {Matrix3d} `out`, for chaining
18125
+ * @see Renderable#getAbsolutePosition
18126
+ * @example
18127
+ * // map a point from one renderable's space into another's
18128
+ * const from = a.getWorldTransform(new Matrix3d());
18129
+ * const into = b.getWorldTransform(new Matrix3d()).invert();
18130
+ * const point = new Vector2d(10, 20); // in a's space
18131
+ * from.apply(point); // -> world
18132
+ * into.apply(point); // -> b's space
18133
+ * @example
18134
+ * // just need to know where something is? use the cheaper call
18135
+ * const where = renderable.getAbsolutePosition();
18136
+ */
18137
+ getWorldTransform(out) {
18138
+ _chain.length = 0;
18139
+ _chain.push(this);
18140
+ if (this.floating !== true) {
18141
+ let node = this.ancestor;
18142
+ while (typeof node !== "undefined" && node !== null) {
18143
+ _chain.push(node);
18144
+ if (node.floating === true) {
18145
+ break;
18146
+ }
18147
+ node = node.ancestor;
18148
+ }
18149
+ }
18150
+ out.identity();
18151
+ for (let i = _chain.length; i-- > 0; ) {
18152
+ out.multiply(_chain[i].getLocalTransform(_level));
18153
+ }
18154
+ _chain.length = 0;
18155
+ return out;
18156
+ }
18004
18157
  /**
18005
18158
  * Prepare the rendering context before drawing (automatically called by melonJS).
18006
18159
  * This will apply any defined transforms, anchor point, tint or blend mode and translate the context accordingly to this renderable position.
@@ -18295,6 +18448,7 @@ function compileProgram(gl, vertex, fragment2, attributes) {
18295
18448
  throw new Error(error_msg);
18296
18449
  }
18297
18450
  gl.useProgram(program);
18451
+ gl.__meCurrentProgram = program;
18298
18452
  gl.deleteShader(vertShader);
18299
18453
  gl.deleteShader(fragShader);
18300
18454
  return program;
@@ -18592,6 +18746,7 @@ var GLShader = class _GLShader {
18592
18746
  return;
18593
18747
  }
18594
18748
  this.gl.useProgram(this.program);
18749
+ this.gl.__meCurrentProgram = this.program;
18595
18750
  }
18596
18751
  /**
18597
18752
  * returns the location of an attribute variable in this shader program
@@ -21076,6 +21231,9 @@ var FrameAnimation = class {
21076
21231
  this.resetAnim = () => {
21077
21232
  if (typeof onComplete === "function") {
21078
21233
  onComplete();
21234
+ if (!this._isCurrentAnimationLive()) {
21235
+ return false;
21236
+ }
21079
21237
  }
21080
21238
  this.setCurrentAnimation(next, null, true);
21081
21239
  };
@@ -21083,6 +21241,9 @@ var FrameAnimation = class {
21083
21241
  this.resetAnim = () => {
21084
21242
  if (typeof onComplete === "function") {
21085
21243
  onComplete();
21244
+ if (!this._isCurrentAnimationLive()) {
21245
+ return false;
21246
+ }
21086
21247
  }
21087
21248
  this._animDone = true;
21088
21249
  return false;
@@ -21168,6 +21329,26 @@ var FrameAnimation = class {
21168
21329
  * @param {number} id - the frame id
21169
21330
  * @returns {object} the frame data
21170
21331
  */
21332
+ /**
21333
+ * Whether the current animation is still resolvable.
21334
+ *
21335
+ * `update()` hands control to user code in the middle of its frame loop:
21336
+ * `onended`, and the completion callback behind `resetAnim`. That code is
21337
+ * free to tear the host down — "remove me once the animation finishes" is
21338
+ * an entirely reasonable reaction, and `removeChildNow()` destroys the
21339
+ * sprite, whose `destroy()` empties `anim`. Everything after the callback
21340
+ * reads out of `anim`, so the loop has to check it is still there rather
21341
+ * than resume into a map the callback just cleared.
21342
+ *
21343
+ * A callback that switches animations with `setCurrentAnimation()` leaves
21344
+ * this true, which is the point: only a genuinely torn-down host stops the
21345
+ * loop.
21346
+ * @ignore
21347
+ * @returns {boolean} true while the current animation can still be read
21348
+ */
21349
+ _isCurrentAnimationLive() {
21350
+ return this.current !== void 0 && this.current !== null && this.anim[this.current.name] !== void 0;
21351
+ }
21171
21352
  getAnimationFrameObjectByIndex(id) {
21172
21353
  return this.anim[this.current.name].frames[id];
21173
21354
  }
@@ -21201,6 +21382,9 @@ var FrameAnimation = class {
21201
21382
  */
21202
21383
  update(dt) {
21203
21384
  let changed = false;
21385
+ if (!this._isCurrentAnimationLive()) {
21386
+ return changed;
21387
+ }
21204
21388
  if (!this.animationpause && !this._animDone && this.current.length > 1) {
21205
21389
  let duration = this.getAnimationFrameObjectByIndex(
21206
21390
  this.current.idx
@@ -21214,9 +21398,16 @@ var FrameAnimation = class {
21214
21398
  if (this.current.idx === 0) {
21215
21399
  if (typeof this.host.onended === "function") {
21216
21400
  this.host.onended();
21401
+ if (!this._isCurrentAnimationLive()) {
21402
+ break;
21403
+ }
21217
21404
  }
21218
21405
  if (typeof this.resetAnim === "function") {
21219
- if (this.resetAnim() === false) {
21406
+ const keepPlaying = this.resetAnim();
21407
+ if (!this._isCurrentAnimationLive()) {
21408
+ break;
21409
+ }
21410
+ if (keepPlaying === false) {
21220
21411
  this.setAnimationFrame(this.current.length - 1);
21221
21412
  this.dt %= duration;
21222
21413
  break;
@@ -23314,11 +23505,13 @@ function preloadBinary(data2, onload2, onerror, settings) {
23314
23505
  // src/loader/parsers/fontface.js
23315
23506
  function preloadFontFace(data2, onload2, onerror) {
23316
23507
  const fontFaceSet = typeof globalThis.document !== "undefined" ? globalThis.document.fonts : void 0;
23317
- if (!data2.src.startsWith("url(") && !data2.src.startsWith("local(")) {
23318
- data2.src = "url(" + data2.src + ")";
23508
+ let src = data2.src;
23509
+ if (!src.startsWith("url(") && !src.startsWith("local(")) {
23510
+ const escaped = src.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
23511
+ src = `url('${escaped}')`;
23319
23512
  }
23320
23513
  if (typeof fontFaceSet !== "undefined") {
23321
- const font = new FontFace(data2.name, data2.src);
23514
+ const font = new FontFace(data2.name, src);
23322
23515
  font.load().then(
23323
23516
  () => {
23324
23517
  fontList[data2.name] = font;
@@ -26711,9 +26904,12 @@ var Container = class _Container extends Renderable {
26711
26904
  let isFloating = false;
26712
26905
  const isPaused = state_default.isPaused();
26713
26906
  const children = this.getChildren();
26714
- const childrenLength = children.length;
26715
26907
  const cameras = state_default.current().cameras;
26716
- for (let i = childrenLength, obj; i--, obj = children[i]; ) {
26908
+ const pass = children.slice();
26909
+ for (let i = pass.length, obj; i--, obj = pass[i]; ) {
26910
+ if (obj.ancestor !== this) {
26911
+ continue;
26912
+ }
26717
26913
  if (isPaused && !obj.updateWhenPaused) {
26718
26914
  continue;
26719
26915
  }
@@ -26735,6 +26931,20 @@ var Container = class _Container extends Renderable {
26735
26931
  }
26736
26932
  return super.update(dt);
26737
26933
  }
26934
+ /**
26935
+ * A container additionally offsets its children by its own position — the
26936
+ * `translate()` at the top of {@link Container#draw} — which is what makes
26937
+ * a child's coordinates mean "relative to my parent" rather than naming a
26938
+ * place in the world. A leaf renderable has no such term.
26939
+ * @protected
26940
+ * @param {Matrix3d} out - matrix to write into
26941
+ * @returns {Matrix3d} `out`, for chaining
26942
+ */
26943
+ getLocalTransform(out) {
26944
+ super.getLocalTransform(out);
26945
+ out.translate(this.pos.x, this.pos.y);
26946
+ return out;
26947
+ }
26738
26948
  /**
26739
26949
  * draw this renderable (automatically called by melonJS)
26740
26950
  * @param {CanvasRenderer|WebGLRenderer} renderer - a renderer instance
@@ -27053,6 +27263,9 @@ var GLTFModel = class extends Container {
27053
27263
  this.resetAnim = () => {
27054
27264
  if (typeof onComplete === "function") {
27055
27265
  onComplete();
27266
+ if (!this._isLive()) {
27267
+ return false;
27268
+ }
27056
27269
  }
27057
27270
  this.setCurrentAnimation(next);
27058
27271
  };
@@ -27060,6 +27273,9 @@ var GLTFModel = class extends Container {
27060
27273
  this.resetAnim = () => {
27061
27274
  if (typeof onComplete === "function") {
27062
27275
  onComplete();
27276
+ if (!this._isLive()) {
27277
+ return false;
27278
+ }
27063
27279
  }
27064
27280
  this._animDone = true;
27065
27281
  return false;
@@ -27139,6 +27355,9 @@ var GLTFModel = class extends Container {
27139
27355
  if (this.current.time >= duration) {
27140
27356
  if (typeof this.onended === "function") {
27141
27357
  this.onended();
27358
+ if (!this._isLive()) {
27359
+ return super.update(dt);
27360
+ }
27142
27361
  }
27143
27362
  if (typeof this.resetAnim === "function") {
27144
27363
  if (this.resetAnim() === false) {
@@ -27150,6 +27369,9 @@ var GLTFModel = class extends Container {
27150
27369
  this.current.time %= duration;
27151
27370
  }
27152
27371
  }
27372
+ if (!this._isLive()) {
27373
+ return super.update(dt);
27374
+ }
27153
27375
  this._pose();
27154
27376
  this.isDirty = true;
27155
27377
  }
@@ -27161,6 +27383,24 @@ var GLTFModel = class extends Container {
27161
27383
  * not animate use their cached rest matrix.
27162
27384
  * @ignore
27163
27385
  */
27386
+ /**
27387
+ * Whether this model is still alive.
27388
+ *
27389
+ * `update()` hands control to user code — `onended`, and the completion
27390
+ * callback behind `resetAnim` — and then poses the hierarchy. A callback
27391
+ * that removed the model (a death animation removing its own model is the
27392
+ * obvious case) has had `Renderable.destroy()` run, which releases `pos` to
27393
+ * the pool and leaves it undefined on every part. `_pose()` writes
27394
+ * `mesh.pos`, so it must not run on a torn-down model.
27395
+ *
27396
+ * `pos` and not `ancestor`: a model that was never added to a container is
27397
+ * perfectly poseable, and several callers do exactly that.
27398
+ * @ignore
27399
+ * @returns {boolean} true while the model can still be posed
27400
+ */
27401
+ _isLive() {
27402
+ return this.pos !== void 0;
27403
+ }
27164
27404
  _pose() {
27165
27405
  const clip = this.current.name ? this.anim[this.current.name] : null;
27166
27406
  const t = this.current.time;
@@ -35686,7 +35926,7 @@ var CanvasRenderer = class extends Renderer {
35686
35926
  }
35687
35927
  /**
35688
35928
  * set the current blend mode for this renderer. <br>
35689
- * All renderers support: <br>
35929
+ * Every renderer supports the full set: <br>
35690
35930
  * - "normal" : draws new content on top of the existing content <br>
35691
35931
  * <img src="../images/normal-blendmode.png" width="180"/> <br>
35692
35932
  * - "add", "additive", or "lighter" : color values are added together <br>
@@ -35695,26 +35935,30 @@ var CanvasRenderer = class extends Renderer {
35695
35935
  * <img src="../images/multiply-blendmode.png" width="180"/> <br>
35696
35936
  * - "screen" : pixels are inverted, multiplied, and inverted again (opposite of multiply) <br>
35697
35937
  * <img src="../images/screen-blendmode.png" width="180"/> <br>
35698
- * Canvas (browser-dependent) and WebGL2: <br>
35699
35938
  * - "darken" : retains the darkest pixels of both layers <br>
35700
35939
  * <img src="../images/darken-blendmode.png" width="180"/> <br>
35701
35940
  * - "lighten" : retains the lightest pixels of both layers <br>
35702
35941
  * <img src="../images/lighten-blendmode.png" width="180"/> <br>
35703
- * Canvas only, browser-dependent (falls back to "normal" if unsupported or in WebGL): <br>
35704
- * - "overlay" <br>
35942
+ * - "overlay" : multiplies or screens, depending on the backdrop <br>
35705
35943
  * <img src="../images/overlay-blendmode.png" width="180"/> <br>
35706
- * - "color-dodge" <br>
35707
- * <img src="../images/color-dodge-blendmode.png" width="180"/> <br>
35708
- * - "color-burn" <br>
35709
- * <img src="../images/color-burn-blendmode.png" width="180"/> <br>
35710
- * - "hard-light" <br>
35944
+ * - "hard-light" : overlay with the layers swapped — a harsh spotlight <br>
35711
35945
  * <img src="../images/hard-light-blendmode.png" width="180"/> <br>
35712
- * - "soft-light" <br>
35946
+ * - "soft-light" : a diffused spotlight, gentler than hard-light <br>
35713
35947
  * <img src="../images/soft-light-blendmode.png" width="180"/> <br>
35714
- * - "difference" <br>
35948
+ * - "color-dodge" : brightens the backdrop to reflect the source <br>
35949
+ * <img src="../images/color-dodge-blendmode.png" width="180"/> <br>
35950
+ * - "color-burn" : darkens the backdrop to reflect the source <br>
35951
+ * <img src="../images/color-burn-blendmode.png" width="180"/> <br>
35952
+ * - "difference" : the absolute difference of the two layers <br>
35715
35953
  * <img src="../images/difference-blendmode.png" width="180"/> <br>
35716
- * - "exclusion" <br>
35954
+ * - "exclusion" : like difference, but lower in contrast <br>
35717
35955
  * <img src="../images/exclusion-blendmode.png" width="180"/> <br>
35956
+ * - "none" : blending disabled — the source replaces the destination
35957
+ * outright, alpha included <br>
35958
+ * A few draw types cannot honour every mode — 3D meshes, and fills using
35959
+ * a {@link Gradient} — and fall back to "normal" with a one-time console
35960
+ * warning. `setBlendMode` returns what it actually applied, so comparing
35961
+ * the result against your request detects that case.
35718
35962
  * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
35719
35963
  * @param {string} [mode="normal"] - blend mode
35720
35964
  * @returns {string} the blend mode actually applied (may differ if the requested mode is unsupported)
@@ -36587,11 +36831,11 @@ var CanvasRenderer = class extends Renderer {
36587
36831
  * Reset (overrides) the renderer transformation matrix to the
36588
36832
  * identity one, and then apply the given transformation matrix.
36589
36833
  * @param {Matrix2d|Matrix3d|number} a - a matrix to transform by, or the a component to multiply the current matrix by
36590
- * @param {number} b - the b component to multiply the current matrix by
36591
- * @param {number} c - the c component to multiply the current matrix by
36592
- * @param {number} d - the d component to multiply the current matrix by
36593
- * @param {number} e - the e component to multiply the current matrix by
36594
- * @param {number} f - the f component to multiply the current matrix by
36834
+ * @param {number} [b] - the b component to multiply the current matrix by; omitted when `a` is a matrix
36835
+ * @param {number} [c] - the c component to multiply the current matrix by; omitted when `a` is a matrix
36836
+ * @param {number} [d] - the d component to multiply the current matrix by; omitted when `a` is a matrix
36837
+ * @param {number} [e] - the e component to multiply the current matrix by; omitted when `a` is a matrix
36838
+ * @param {number} [f] - the f component to multiply the current matrix by; omitted when `a` is a matrix
36595
36839
  */
36596
36840
  setTransform(a, b, c, d, e, f) {
36597
36841
  this.resetTransform();
@@ -36601,11 +36845,11 @@ var CanvasRenderer = class extends Renderer {
36601
36845
  * Multiply given matrix into the renderer tranformation matrix
36602
36846
  * @see {@link CanvasRenderer.setTransform} which will reset the current transform matrix prior to performing the new transformation
36603
36847
  * @param {Matrix2d|Matrix3d|number} a - a matrix to transform by, or the a component to multiply the current matrix by
36604
- * @param {number} b - the b component to multiply the current matrix by
36605
- * @param {number} c - the c component to multiply the current matrix by
36606
- * @param {number} d - the d component to multiply the current matrix by
36607
- * @param {number} e - the e component to multiply the current matrix by
36608
- * @param {number} f - the f component to multiply the current matrix by
36848
+ * @param {number} [b] - the b component to multiply the current matrix by; omitted when `a` is a matrix
36849
+ * @param {number} [c] - the c component to multiply the current matrix by; omitted when `a` is a matrix
36850
+ * @param {number} [d] - the d component to multiply the current matrix by; omitted when `a` is a matrix
36851
+ * @param {number} [e] - the e component to multiply the current matrix by; omitted when `a` is a matrix
36852
+ * @param {number} [f] - the f component to multiply the current matrix by; omitted when `a` is a matrix
36609
36853
  */
36610
36854
  transform(a, b, c, d, e, f) {
36611
36855
  if (typeof a === "object") {
@@ -37651,7 +37895,7 @@ var WebGLBatcher = class extends Batcher {
37651
37895
  const gl = this.gl;
37652
37896
  this.vertexState.bind();
37653
37897
  gl.bindBuffer(gl.ARRAY_BUFFER, this.uploadBuffer);
37654
- if (this.renderer.currentProgram !== this.defaultShader.program) {
37898
+ if (this.currentShader !== this.defaultShader || this.renderer.currentProgram !== this.defaultShader.program) {
37655
37899
  this.useShader(this.defaultShader);
37656
37900
  }
37657
37901
  }
@@ -38911,6 +39155,222 @@ var QuadBatcher = class extends MaterialBatcher {
38911
39155
  }
38912
39156
  };
38913
39157
 
39158
+ // src/video/blendmodes.js
39159
+ var ADVANCED_BLEND_MODES = [
39160
+ "difference",
39161
+ "overlay",
39162
+ "hard-light",
39163
+ "color-dodge",
39164
+ "color-burn",
39165
+ "soft-light",
39166
+ // `darken` and `lighten` joined the shader path in 20.2: fixed-function
39167
+ // MIN/MAX compute `min(src, dst)` and nothing else, so there is nowhere to
39168
+ // put the `(1 - srcAlpha) * dst` term source-over contributes after the
39169
+ // blend. A translucent source lost the backdrop's share entirely — a white
39170
+ // `lighten` glow over a light backdrop rendered invisible. APPENDED, never
39171
+ // inserted: the index is the shader's branch value.
39172
+ "darken",
39173
+ "lighten"
39174
+ ];
39175
+ function isAdvancedBlendMode(mode) {
39176
+ return ADVANCED_BLEND_MODES.indexOf(mode) !== -1;
39177
+ }
39178
+ function advancedBlendModeIndex(mode) {
39179
+ return ADVANCED_BLEND_MODES.indexOf(mode);
39180
+ }
39181
+ function normalizeBlendMode(mode) {
39182
+ switch (mode) {
39183
+ case "none":
39184
+ case "multiply":
39185
+ case "screen":
39186
+ case "exclusion":
39187
+ case "normal":
39188
+ return mode;
39189
+ case "additive":
39190
+ case "add":
39191
+ case "lighter":
39192
+ return "additive";
39193
+ default:
39194
+ return "normal";
39195
+ }
39196
+ }
39197
+ function isSupportedBlendMode(mode) {
39198
+ return isAdvancedBlendMode(mode) || mode === "normal" || normalizeBlendMode(mode) !== "normal";
39199
+ }
39200
+ function reportedBlendMode(mode) {
39201
+ return isSupportedBlendMode(mode) ? mode : "normal";
39202
+ }
39203
+ function blendStateFor(mode, premultipliedAlpha) {
39204
+ const src = premultipliedAlpha ? "one" : "src-alpha";
39205
+ switch (mode) {
39206
+ case "none":
39207
+ return void 0;
39208
+ case "additive":
39209
+ return { operation: "add", srcFactor: src, dstFactor: "one" };
39210
+ case "multiply":
39211
+ return {
39212
+ operation: "add",
39213
+ srcFactor: "dst",
39214
+ dstFactor: "one-minus-src-alpha"
39215
+ };
39216
+ case "exclusion":
39217
+ return {
39218
+ operation: "add",
39219
+ srcFactor: "one-minus-dst",
39220
+ dstFactor: "one-minus-src"
39221
+ };
39222
+ case "screen":
39223
+ return {
39224
+ operation: "add",
39225
+ srcFactor: "one",
39226
+ dstFactor: "one-minus-src"
39227
+ };
39228
+ default:
39229
+ return {
39230
+ operation: "add",
39231
+ srcFactor: src,
39232
+ dstFactor: "one-minus-src-alpha"
39233
+ };
39234
+ }
39235
+ }
39236
+
39237
+ // src/video/effects/blendEffect.js
39238
+ var wgslFragment2 = `
39239
+ struct BlendUniforms {
39240
+ uBlendMode : f32,
39241
+ };
39242
+ @group(3) @binding(0) var<uniform> fx : BlendUniforms;
39243
+ @group(3) @binding(1) var backdrop : texture_2d<f32>;
39244
+ @group(3) @binding(2) var backdrop_sampler : sampler;
39245
+
39246
+ // HardLight(Cb, Cs) \u2014 also the kernel for Overlay, which the W3C spec
39247
+ // defines as HardLight with the arguments swapped
39248
+ fn ME_hardLight(b : vec3f, s : vec3f) -> vec3f {
39249
+ let lo = 2.0 * b * s;
39250
+ let hi = 1.0 - 2.0 * (1.0 - b) * (1.0 - s);
39251
+ return mix(lo, hi, step(vec3f(0.5), s));
39252
+ }
39253
+
39254
+ fn ME_softLight(b : vec3f, s : vec3f) -> vec3f {
39255
+ let d = mix(((16.0 * b - 12.0) * b + 4.0) * b, sqrt(b), step(vec3f(0.25), b));
39256
+ let lo = b - (1.0 - 2.0 * s) * b * (1.0 - b);
39257
+ let hi = b + (2.0 * s - 1.0) * (d - b);
39258
+ return mix(lo, hi, step(vec3f(0.5), s));
39259
+ }
39260
+
39261
+ // Cb == 0 wins over Cs == 1, so it is applied last
39262
+ fn ME_colorDodge(b : vec3f, s : vec3f) -> vec3f {
39263
+ var r = min(vec3f(1.0), b / max(1.0 - s, vec3f(1e-4)));
39264
+ r = mix(r, vec3f(1.0), step(vec3f(1.0), s));
39265
+ return mix(r, vec3f(0.0), step(b, vec3f(0.0)));
39266
+ }
39267
+
39268
+ // Cb == 1 wins over Cs == 0, so it is applied last
39269
+ fn ME_colorBurn(b : vec3f, s : vec3f) -> vec3f {
39270
+ var r = 1.0 - min(vec3f(1.0), (1.0 - b) / max(s, vec3f(1e-4)));
39271
+ r = mix(r, vec3f(0.0), step(s, vec3f(0.0)));
39272
+ return mix(r, vec3f(1.0), step(vec3f(1.0), b));
39273
+ }
39274
+
39275
+ fn ME_blend(b : vec3f, s : vec3f) -> vec3f {
39276
+ if (fx.uBlendMode < 0.5) { return abs(b - s); }
39277
+ if (fx.uBlendMode < 1.5) { return ME_hardLight(s, b); }
39278
+ if (fx.uBlendMode < 2.5) { return ME_hardLight(b, s); }
39279
+ if (fx.uBlendMode < 3.5) { return ME_colorDodge(b, s); }
39280
+ if (fx.uBlendMode < 4.5) { return ME_colorBurn(b, s); }
39281
+ if (fx.uBlendMode < 5.5) { return ME_softLight(b, s); }
39282
+ if (fx.uBlendMode < 6.5) { return min(b, s); }
39283
+ return max(b, s);
39284
+ }
39285
+
39286
+ fn apply(color : vec4f, uv : vec2f) -> vec4f {
39287
+ // sampled BEFORE any branch, so plain textureSampleLevel is safe here
39288
+ let Cb = textureSampleLevel(backdrop, backdrop_sampler, screen_uv, 0.0).rgb;
39289
+ let a = color.a;
39290
+ let Cs = color.rgb / max(a, 1e-4);
39291
+ let B = clamp(ME_blend(Cb, Cs), vec3f(0.0), vec3f(1.0));
39292
+ return vec4f(a * B, a);
39293
+ }
39294
+ `;
39295
+ var BlendEffect = class extends ShaderEffect {
39296
+ /**
39297
+ * @param {import("../webgl/webgl_renderer.js").default|import("../webgpu/webgpu_renderer.js").default} renderer - the renderer that owns this effect
39298
+ */
39299
+ constructor(renderer) {
39300
+ super(renderer, {
39301
+ glsl: `
39302
+ uniform sampler2D backdrop;
39303
+ uniform float uBlendMode;
39304
+
39305
+ // HardLight(Cb, Cs) \u2014 also the kernel for Overlay, which the W3C
39306
+ // spec defines as HardLight with the arguments swapped
39307
+ vec3 ME_hardLight(vec3 b, vec3 s) {
39308
+ vec3 lo = 2.0 * b * s;
39309
+ vec3 hi = 1.0 - 2.0 * (1.0 - b) * (1.0 - s);
39310
+ return mix(lo, hi, step(vec3(0.5), s));
39311
+ }
39312
+
39313
+ vec3 ME_softLight(vec3 b, vec3 s) {
39314
+ vec3 d = mix(((16.0 * b - 12.0) * b + 4.0) * b, sqrt(b), step(vec3(0.25), b));
39315
+ vec3 lo = b - (1.0 - 2.0 * s) * b * (1.0 - b);
39316
+ vec3 hi = b + (2.0 * s - 1.0) * (d - b);
39317
+ return mix(lo, hi, step(vec3(0.5), s));
39318
+ }
39319
+
39320
+ // Cb == 0 wins over Cs == 1, so it is applied last
39321
+ vec3 ME_colorDodge(vec3 b, vec3 s) {
39322
+ vec3 r = min(vec3(1.0), b / max(1.0 - s, vec3(1e-4)));
39323
+ r = mix(r, vec3(1.0), step(vec3(1.0), s));
39324
+ return mix(r, vec3(0.0), step(b, vec3(0.0)));
39325
+ }
39326
+
39327
+ // Cb == 1 wins over Cs == 0, so it is applied last
39328
+ vec3 ME_colorBurn(vec3 b, vec3 s) {
39329
+ vec3 r = 1.0 - min(vec3(1.0), (1.0 - b) / max(s, vec3(1e-4)));
39330
+ r = mix(r, vec3(0.0), step(s, vec3(0.0)));
39331
+ return mix(r, vec3(1.0), step(vec3(1.0), b));
39332
+ }
39333
+
39334
+ vec3 ME_blend(vec3 b, vec3 s) {
39335
+ if (uBlendMode < 0.5) { return abs(b - s); }
39336
+ if (uBlendMode < 1.5) { return ME_hardLight(s, b); }
39337
+ if (uBlendMode < 2.5) { return ME_hardLight(b, s); }
39338
+ if (uBlendMode < 3.5) { return ME_colorDodge(b, s); }
39339
+ if (uBlendMode < 4.5) { return ME_colorBurn(b, s); }
39340
+ if (uBlendMode < 5.5) { return ME_softLight(b, s); }
39341
+ if (uBlendMode < 6.5) { return min(b, s); }
39342
+ return max(b, s);
39343
+ }
39344
+
39345
+ vec4 apply(vec4 color, vec2 uv) {
39346
+ vec3 Cb = texture2D(backdrop, screen_uv).rgb;
39347
+ float a = color.a;
39348
+ vec3 Cs = color.rgb / max(a, 1e-4);
39349
+ vec3 B = clamp(ME_blend(Cb, Cs), 0.0, 1.0);
39350
+ return vec4(a * B, a);
39351
+ }
39352
+ `,
39353
+ wgsl: wgslFragment2
39354
+ });
39355
+ this.setUniform("uBlendMode", 0);
39356
+ }
39357
+ /**
39358
+ * Select which of the six formulas the shader evaluates.
39359
+ * @param {string} mode - one of the modes in {@link ADVANCED_BLEND_MODES}
39360
+ * @returns {boolean} false when the mode is not an advanced blend mode, in
39361
+ * which case the uniform is left untouched
39362
+ * @ignore
39363
+ */
39364
+ setBlendMode(mode) {
39365
+ const index = advancedBlendModeIndex(mode);
39366
+ if (index === -1) {
39367
+ return false;
39368
+ }
39369
+ this.setUniform("uBlendMode", index);
39370
+ return true;
39371
+ }
39372
+ };
39373
+
38914
39374
  // src/video/effects/radialGradient.js
38915
39375
  var RadialGradientEffect = class extends ShaderEffect {
38916
39376
  /**
@@ -42616,7 +43076,30 @@ var GLSamplerCache = class {
42616
43076
  var _tempMatrix = new Matrix3d();
42617
43077
  var _savedTransform = new Matrix3d();
42618
43078
  var _savedProjection = new Matrix3d();
42619
- var WebGLRenderer = class extends Renderer {
43079
+ var GL_BLEND_OP;
43080
+ var GL_BLEND_FACTOR;
43081
+ function initBlendEnums(gl) {
43082
+ GL_BLEND_OP = {
43083
+ add: gl.FUNC_ADD,
43084
+ subtract: gl.FUNC_SUBTRACT,
43085
+ "reverse-subtract": gl.FUNC_REVERSE_SUBTRACT,
43086
+ min: gl.MIN,
43087
+ max: gl.MAX
43088
+ };
43089
+ GL_BLEND_FACTOR = {
43090
+ zero: gl.ZERO,
43091
+ one: gl.ONE,
43092
+ src: gl.SRC_COLOR,
43093
+ "one-minus-src": gl.ONE_MINUS_SRC_COLOR,
43094
+ "src-alpha": gl.SRC_ALPHA,
43095
+ "one-minus-src-alpha": gl.ONE_MINUS_SRC_ALPHA,
43096
+ dst: gl.DST_COLOR,
43097
+ "one-minus-dst": gl.ONE_MINUS_DST_COLOR,
43098
+ "dst-alpha": gl.DST_ALPHA,
43099
+ "one-minus-dst-alpha": gl.ONE_MINUS_DST_ALPHA
43100
+ };
43101
+ }
43102
+ var WebGLRenderer = class _WebGLRenderer extends Renderer {
42620
43103
  /**
42621
43104
  * @param {ApplicationSettings} [options] - optional parameters for the renderer
42622
43105
  */
@@ -42633,6 +43116,14 @@ var WebGLRenderer = class extends Renderer {
42633
43116
  });
42634
43117
  this._effectProjectionStack = [];
42635
43118
  this._effectPassDepth = 0;
43119
+ this._advancedBlendEffect = void 0;
43120
+ this._advancedBlendCapture = void 0;
43121
+ this._advancedBlendTarget = void 0;
43122
+ this._advancedBlendOpen = false;
43123
+ this._advancedBlendBusy = 0;
43124
+ this._advancedBlendParent = null;
43125
+ this._advancedBlendStencil = false;
43126
+ this._advancedBlendWarned = /* @__PURE__ */ new Set();
42636
43127
  this.lineWidth = 1;
42637
43128
  this.lineJoin = "round";
42638
43129
  this.vertexBuffer = this.gl.createBuffer();
@@ -42811,6 +43302,14 @@ var WebGLRenderer = class extends Renderer {
42811
43302
  off(GAME_RESET, this.onGameReset);
42812
43303
  off(ONCONTEXT_RESTORED, this.onContextRestoredInvalidate);
42813
43304
  off(CANVAS_ONRESIZE, this.onCanvasResize);
43305
+ this._advancedBlendEffect?.destroy();
43306
+ this._advancedBlendEffect = void 0;
43307
+ this._advancedBlendCapture?.destroy();
43308
+ this._advancedBlendCapture = void 0;
43309
+ this._advancedBlendTarget?.destroy?.();
43310
+ this._advancedBlendTarget = void 0;
43311
+ this._advancedBlendOpen = false;
43312
+ this._advancedBlendBusy = 0;
42814
43313
  releaseShadowQuads(this);
42815
43314
  if (this.batchers) {
42816
43315
  this.batchers.forEach((batcher) => {
@@ -42939,6 +43438,18 @@ var WebGLRenderer = class extends Renderer {
42939
43438
  if (typeof batcher === "undefined") {
42940
43439
  throw new Error("Invalid Batcher");
42941
43440
  }
43441
+ if (this._advancedBlendBusy === 0) {
43442
+ if (this._advancedBlendOpen === true) {
43443
+ this._closeAdvancedBlend();
43444
+ }
43445
+ if (isAdvancedBlendMode(this.currentBlendMode) === true) {
43446
+ if (name === "mesh" || name === "litMesh") {
43447
+ this._warnAdvancedBlendFallback("3D meshes");
43448
+ } else {
43449
+ this._openAdvancedBlend();
43450
+ }
43451
+ }
43452
+ }
42942
43453
  const targetShader = shader != null ? shader : batcher.defaultShader;
42943
43454
  if (this.currentBatcher === batcher && batcher.currentShader === targetShader) {
42944
43455
  return this.currentBatcher;
@@ -43008,6 +43519,10 @@ var WebGLRenderer = class extends Renderer {
43008
43519
  * Flush the batcher to the frame buffer
43009
43520
  */
43010
43521
  flush() {
43522
+ if (this._advancedBlendOpen === true && this._advancedBlendBusy === 0) {
43523
+ this._closeAdvancedBlend();
43524
+ return;
43525
+ }
43011
43526
  this.currentBatcher.flush();
43012
43527
  }
43013
43528
  /**
@@ -43183,6 +43698,16 @@ var WebGLRenderer = class extends Renderer {
43183
43698
  * }
43184
43699
  */
43185
43700
  toFrameTexture(options = {}) {
43701
+ const busy = this._advancedBlendBusy;
43702
+ this._advancedBlendBusy = (busy ?? 0) + 1;
43703
+ try {
43704
+ return _WebGLRenderer.prototype._toFrameTexture.call(this, options);
43705
+ } finally {
43706
+ this._advancedBlendBusy = busy;
43707
+ }
43708
+ }
43709
+ /** @ignore */
43710
+ _toFrameTexture(options = {}) {
43186
43711
  const gl = this.gl;
43187
43712
  const canvas = this.getCanvas();
43188
43713
  let x = 0;
@@ -43418,6 +43943,125 @@ var WebGLRenderer = class extends Renderer {
43418
43943
  );
43419
43944
  this.currentBatcher.setProjection(this.projectionMatrix);
43420
43945
  }
43946
+ /**
43947
+ * Say once, per excluded path and mode, that an advanced blend mode is
43948
+ * falling back to `"normal"`. A silent data-dependent fallback is exactly
43949
+ * what the advanced-blend work exists to remove, so the exclusions are
43950
+ * loud.
43951
+ * @param {string} what - the excluded path, named for the message
43952
+ * @ignore
43953
+ */
43954
+ _warnAdvancedBlendFallback(what) {
43955
+ const key = `${what}:${this.currentBlendMode}`;
43956
+ if (this._advancedBlendWarned.has(key)) {
43957
+ return;
43958
+ }
43959
+ this._advancedBlendWarned.add(key);
43960
+ console.warn(
43961
+ `WebGLRenderer: blend mode "${this.currentBlendMode}" is not supported for ${what} and falls back to "normal"`
43962
+ );
43963
+ }
43964
+ /**
43965
+ * Close a pending advanced-blend bracket, if one is open and we are not
43966
+ * already inside the bracket machinery. The safe form to call from any
43967
+ * path that is about to disturb the destination.
43968
+ * @ignore
43969
+ */
43970
+ _drainAdvancedBlend() {
43971
+ if (this._advancedBlendOpen === true && this._advancedBlendBusy === 0) {
43972
+ this._closeAdvancedBlend();
43973
+ }
43974
+ }
43975
+ /**
43976
+ * Open an advanced-blend bracket: capture the destination, then redirect
43977
+ * this draw into an offscreen target. Closed by {@link _closeAdvancedBlend}
43978
+ * at the next draw, flush or clear.
43979
+ * @returns {boolean} false when the effect could not be realized, in which
43980
+ * case the draw proceeds unbracketed (plain source-over)
43981
+ * @ignore
43982
+ */
43983
+ _openAdvancedBlend() {
43984
+ const gl = this.gl;
43985
+ const canvas = this.getCanvas();
43986
+ const w = canvas.width;
43987
+ const h = canvas.height;
43988
+ this._advancedBlendBusy++;
43989
+ if (typeof this._advancedBlendEffect === "undefined") {
43990
+ this._advancedBlendEffect = new BlendEffect(this);
43991
+ }
43992
+ if (this._advancedBlendEffect.enabled !== true) {
43993
+ this._advancedBlendBusy--;
43994
+ this._warnAdvancedBlendFallback("this device");
43995
+ return false;
43996
+ }
43997
+ this._advancedBlendEffect.setBlendMode(this.currentBlendMode);
43998
+ this.currentBatcher.flush();
43999
+ this._advancedBlendCapture = this.toFrameTexture({
44000
+ target: this._advancedBlendCapture ?? null
44001
+ });
44002
+ this._advancedBlendEffect.setTexture(
44003
+ "backdrop",
44004
+ this._advancedBlendCapture
44005
+ );
44006
+ this._advancedBlendParent = this._effectPassDepth > 0 ? this._renderTargetPool.getCaptureTarget() : null;
44007
+ if (typeof this._advancedBlendTarget === "undefined") {
44008
+ this._advancedBlendTarget = new WebGLRenderTarget(gl, w, h, {
44009
+ samples: 0
44010
+ });
44011
+ } else {
44012
+ this._advancedBlendTarget.resize(w, h);
44013
+ }
44014
+ this.invalidateTextureUnit(0);
44015
+ this._advancedBlendTarget.bind();
44016
+ this.setViewport(0, 0, w, h);
44017
+ this.clearRenderTarget();
44018
+ this._advancedBlendStencil = gl.isEnabled(gl.STENCIL_TEST);
44019
+ if (this._advancedBlendStencil === true) {
44020
+ gl.disable(gl.STENCIL_TEST);
44021
+ }
44022
+ this._advancedBlendOpen = true;
44023
+ this._advancedBlendBusy--;
44024
+ return true;
44025
+ }
44026
+ /**
44027
+ * Close an advanced-blend bracket: drain the offscreen, return to the
44028
+ * parent target, and composite through {@link BlendEffect}.
44029
+ * @ignore
44030
+ */
44031
+ _closeAdvancedBlend() {
44032
+ const gl = this.gl;
44033
+ const canvas = this.getCanvas();
44034
+ const w = canvas.width;
44035
+ const h = canvas.height;
44036
+ this._advancedBlendBusy++;
44037
+ this._advancedBlendOpen = false;
44038
+ this.currentBatcher.flush();
44039
+ this._advancedBlendTarget.unbind();
44040
+ if (this._advancedBlendParent !== null) {
44041
+ this._advancedBlendParent.bind();
44042
+ }
44043
+ this._advancedBlendParent = null;
44044
+ this.invalidateTextureUnit(0);
44045
+ this.setViewport(0, 0, w, h);
44046
+ emit(RENDER_TARGET_CHANGED, this);
44047
+ if (this._advancedBlendStencil === true) {
44048
+ gl.enable(gl.STENCIL_TEST);
44049
+ }
44050
+ const mode = this.currentBlendMode;
44051
+ this.setBlendMode("normal", true);
44052
+ this.blitEffect(
44053
+ this._advancedBlendTarget.texture,
44054
+ 0,
44055
+ 0,
44056
+ w,
44057
+ h,
44058
+ this._advancedBlendEffect,
44059
+ true
44060
+ );
44061
+ this.currentBatcher.flush();
44062
+ this.currentBlendMode = mode;
44063
+ this._advancedBlendBusy--;
44064
+ }
43421
44065
  /**
43422
44066
  * Blit a texture to the screen through a shader effect.
43423
44067
  * Draws a screen-aligned quad using the given texture as source
@@ -43431,6 +44075,7 @@ var WebGLRenderer = class extends Renderer {
43431
44075
  * @param {boolean} [keepBlend=false] - if true, keep current blend mode (for sprite compositing)
43432
44076
  */
43433
44077
  blitEffect(source, x, y, width, height, shader, keepBlend = false) {
44078
+ this._advancedBlendBusy++;
43434
44079
  this.flush();
43435
44080
  const batcher = this.setBatcher("quad");
43436
44081
  _savedTransform.copy(this.currentTransform);
@@ -43448,6 +44093,7 @@ var WebGLRenderer = class extends Renderer {
43448
44093
  this.currentTransform.copy(_savedTransform);
43449
44094
  this.projectionMatrix.copy(_savedProjection);
43450
44095
  batcher.setProjection(this.projectionMatrix);
44096
+ this._advancedBlendBusy--;
43451
44097
  }
43452
44098
  /**
43453
44099
  * set/change the current projection matrix (WebGL only)
@@ -43534,6 +44180,7 @@ var WebGLRenderer = class extends Renderer {
43534
44180
  * Clear the frame buffer
43535
44181
  */
43536
44182
  clear() {
44183
+ this._drainAdvancedBlend();
43537
44184
  const gl = this.gl;
43538
44185
  const clearColor = this.backgroundColor.toArray();
43539
44186
  gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
@@ -43547,6 +44194,7 @@ var WebGLRenderer = class extends Renderer {
43547
44194
  * @param {boolean} [opaque=false] - Allow transparency [default] or clear the surface completely [true]
43548
44195
  */
43549
44196
  clearColor(color = "#000000", opaque = false) {
44197
+ this._drainAdvancedBlend();
43550
44198
  let glArray;
43551
44199
  const gl = this.gl;
43552
44200
  if (color instanceof Color) {
@@ -43911,12 +44559,31 @@ var WebGLRenderer = class extends Renderer {
43911
44559
  * return a reference to the system 2d Context
43912
44560
  * @returns {WebGLRenderingContext} the current WebGL context
43913
44561
  */
44562
+ /**
44563
+ * The shader program the GL context actually has bound.
44564
+ *
44565
+ * Backed by the CONTEXT rather than by a field on this renderer, because
44566
+ * `GLShader` binds programs too — writing a uniform calls `useProgram`,
44567
+ * and a freshly linked program is left bound — and it is constructed with
44568
+ * a bare `gl`, so it cannot reach a renderer-owned cache. A field here
44569
+ * would silently drift out of step with GL, and every batcher that
44570
+ * compares against it (`syncProgram`, the mesh and primitive paths) would
44571
+ * skip a rebind it actually needed, drawing a pending batch through the
44572
+ * wrong program.
44573
+ * @type {WebGLProgram|undefined}
44574
+ */
44575
+ get currentProgram() {
44576
+ return this.gl.__meCurrentProgram;
44577
+ }
44578
+ set currentProgram(value) {
44579
+ this.gl.__meCurrentProgram = value;
44580
+ }
43914
44581
  getContext() {
43915
44582
  return this.gl;
43916
44583
  }
43917
44584
  /**
43918
44585
  * set the current blend mode for this renderer. <br>
43919
- * All renderers support: <br>
44586
+ * Every renderer supports the full set: <br>
43920
44587
  * - "normal" : draws new content on top of the existing content <br>
43921
44588
  * <img src="../images/normal-blendmode.png" width="180"/> <br>
43922
44589
  * - "add", "additive", or "lighter" : color values are added together <br>
@@ -43925,71 +44592,58 @@ var WebGLRenderer = class extends Renderer {
43925
44592
  * <img src="../images/multiply-blendmode.png" width="180"/> <br>
43926
44593
  * - "screen" : pixels are inverted, multiplied, and inverted again (opposite of multiply) <br>
43927
44594
  * <img src="../images/screen-blendmode.png" width="180"/> <br>
43928
- * WebGL2 additionally supports: <br>
43929
44595
  * - "darken" : retains the darkest pixels of both layers <br>
43930
44596
  * <img src="../images/darken-blendmode.png" width="180"/> <br>
43931
44597
  * - "lighten" : retains the lightest pixels of both layers <br>
43932
44598
  * <img src="../images/lighten-blendmode.png" width="180"/> <br>
44599
+ * - "overlay" : multiplies or screens, depending on the backdrop <br>
44600
+ * <img src="../images/overlay-blendmode.png" width="180"/> <br>
44601
+ * - "hard-light" : overlay with the layers swapped — a harsh spotlight <br>
44602
+ * <img src="../images/hard-light-blendmode.png" width="180"/> <br>
44603
+ * - "soft-light" : a diffused spotlight, gentler than hard-light <br>
44604
+ * <img src="../images/soft-light-blendmode.png" width="180"/> <br>
44605
+ * - "color-dodge" : brightens the backdrop to reflect the source <br>
44606
+ * <img src="../images/color-dodge-blendmode.png" width="180"/> <br>
44607
+ * - "color-burn" : darkens the backdrop to reflect the source <br>
44608
+ * <img src="../images/color-burn-blendmode.png" width="180"/> <br>
44609
+ * - "difference" : the absolute difference of the two layers <br>
44610
+ * <img src="../images/difference-blendmode.png" width="180"/> <br>
44611
+ * - "exclusion" : like difference, but lower in contrast <br>
44612
+ * <img src="../images/exclusion-blendmode.png" width="180"/> <br>
43933
44613
  * - "none" : blending disabled — the source replaces the destination
43934
- * outright, alpha included (matches the WebGPU renderer's "none") <br>
43935
- * Other CSS blend modes ("overlay", "color-dodge", "color-burn", "hard-light", "soft-light",
43936
- * "difference", "exclusion") may be supported by the Canvas renderer (browser-dependent)
43937
- * and will always fall back to "normal" in WebGL. <br>
44614
+ * outright, alpha included <br>
44615
+ * A few draw types cannot honour every mode — 3D meshes, and fills using
44616
+ * a {@link Gradient} — and fall back to "normal" with a one-time console
44617
+ * warning. `setBlendMode` returns what it actually applied, so comparing
44618
+ * the result against your request detects that case.
43938
44619
  * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
43939
44620
  * @param {string} [mode="normal"] - blend mode
43940
- * @param {boolean} [premultipliedAlpha=true] - whether textures use premultiplied alpha (affects source blend factor)
44621
+ * @param {boolean} [premultipliedAlpha=true] - whether textures use premultiplied alpha (affects the source blend factor)
43941
44622
  * @returns {string} the blend mode actually applied (may differ if the requested mode is unsupported)
43942
44623
  */
43943
44624
  setBlendMode(mode = "normal", premultipliedAlpha = true) {
43944
44625
  if (this.currentBlendMode !== mode || this.currentPremultipliedAlpha !== premultipliedAlpha) {
43945
44626
  const gl = this.gl;
44627
+ this._drainAdvancedBlend();
43946
44628
  this.flush();
43947
- gl.enable(gl.BLEND);
43948
44629
  this.currentBlendMode = mode;
43949
44630
  this.currentPremultipliedAlpha = premultipliedAlpha;
43950
- const srcAlpha = premultipliedAlpha ? gl.ONE : gl.SRC_ALPHA;
43951
- switch (mode) {
43952
- case "screen":
43953
- gl.blendEquation(gl.FUNC_ADD);
43954
- gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_COLOR);
43955
- break;
43956
- // exclusion(a, b) = a + b - 2ab. Unlike the other CSS advanced
43957
- // modes this needs no per-pixel branch — it falls straight out
43958
- // of `src * sfactor + dst * dfactor`:
43959
- // s*(1-d) + d*(1-s) = s + d - 2sd
43960
- // Same caveat as `screen` above: the identity is exact for an
43961
- // opaque source and approximate for a translucent one, because
43962
- // the factors act on the premultiplied colour.
43963
- case "exclusion":
43964
- gl.blendEquation(gl.FUNC_ADD);
43965
- gl.blendFunc(gl.ONE_MINUS_DST_COLOR, gl.ONE_MINUS_SRC_COLOR);
43966
- break;
43967
- case "lighter":
43968
- case "additive":
43969
- case "add":
43970
- gl.blendEquation(gl.FUNC_ADD);
43971
- gl.blendFunc(srcAlpha, gl.ONE);
43972
- break;
43973
- case "multiply":
43974
- gl.blendEquation(gl.FUNC_ADD);
43975
- gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA);
43976
- break;
43977
- case "darken":
43978
- gl.blendEquation(gl.MIN);
43979
- gl.blendFunc(gl.ONE, gl.ONE);
43980
- break;
43981
- case "lighten":
43982
- gl.blendEquation(gl.MAX);
43983
- gl.blendFunc(gl.ONE, gl.ONE);
43984
- break;
43985
- case "none":
43986
- gl.disable(gl.BLEND);
43987
- break;
43988
- default:
43989
- gl.blendEquation(gl.FUNC_ADD);
43990
- gl.blendFunc(srcAlpha, gl.ONE_MINUS_SRC_ALPHA);
43991
- this.currentBlendMode = "normal";
43992
- break;
44631
+ if (typeof GL_BLEND_OP === "undefined") {
44632
+ initBlendEnums(gl);
44633
+ }
44634
+ const state3 = blendStateFor(normalizeBlendMode(mode), premultipliedAlpha);
44635
+ if (typeof state3 === "undefined") {
44636
+ gl.disable(gl.BLEND);
44637
+ } else {
44638
+ gl.enable(gl.BLEND);
44639
+ gl.blendEquation(GL_BLEND_OP[state3.operation]);
44640
+ gl.blendFunc(
44641
+ GL_BLEND_FACTOR[state3.srcFactor],
44642
+ GL_BLEND_FACTOR[state3.dstFactor]
44643
+ );
44644
+ }
44645
+ if (isSupportedBlendMode(mode) === false) {
44646
+ this.currentBlendMode = "normal";
43993
44647
  }
43994
44648
  }
43995
44649
  return this.currentBlendMode;
@@ -44630,6 +45284,12 @@ var WebGLRenderer = class extends Renderer {
44630
45284
  const grad = this._currentGradient;
44631
45285
  const hasMask = this.maskLevel > 0;
44632
45286
  this._currentGradient = null;
45287
+ const bracketed = isAdvancedBlendMode(this.currentBlendMode);
45288
+ if (bracketed === true) {
45289
+ this._warnAdvancedBlendFallback("gradient fills");
45290
+ this._drainAdvancedBlend();
45291
+ }
45292
+ this._advancedBlendBusy++;
44633
45293
  this.flush();
44634
45294
  gl.enable(gl.STENCIL_TEST);
44635
45295
  gl.colorMask(false, false, false, false);
@@ -44666,16 +45326,17 @@ var WebGLRenderer = class extends Renderer {
44666
45326
  } else {
44667
45327
  gl.disable(gl.STENCIL_TEST);
44668
45328
  }
45329
+ this._advancedBlendBusy--;
44669
45330
  }
44670
45331
  /**
44671
45332
  * Reset (overrides) the renderer transformation matrix to the
44672
45333
  * identity one, and then apply the given transformation matrix.
44673
45334
  * @param {Matrix2d|Matrix3d|number} a - a matrix to transform by, or the a component to multiply the current matrix by
44674
- * @param {number} b - the b component to multiply the current matrix by
44675
- * @param {number} c - the c component to multiply the current matrix by
44676
- * @param {number} d - the d component to multiply the current matrix by
44677
- * @param {number} e - the e component to multiply the current matrix by
44678
- * @param {number} f - the f component to multiply the current matrix by
45335
+ * @param {number} [b] - the b component to multiply the current matrix by; omitted when `a` is a matrix
45336
+ * @param {number} [c] - the c component to multiply the current matrix by; omitted when `a` is a matrix
45337
+ * @param {number} [d] - the d component to multiply the current matrix by; omitted when `a` is a matrix
45338
+ * @param {number} [e] - the e component to multiply the current matrix by; omitted when `a` is a matrix
45339
+ * @param {number} [f] - the f component to multiply the current matrix by; omitted when `a` is a matrix
44679
45340
  */
44680
45341
  setTransform(a, b, c, d, e, f) {
44681
45342
  this.resetTransform();
@@ -44685,11 +45346,11 @@ var WebGLRenderer = class extends Renderer {
44685
45346
  * Multiply given matrix into the renderer tranformation matrix
44686
45347
  * @see {@link WebGLRenderer.setTransform} which will reset the current transform matrix prior to performing the new transformation
44687
45348
  * @param {Matrix2d|Matrix3d|number} a - a matrix to transform by, or the a component to multiply the current matrix by
44688
- * @param {number} b - the b component to multiply the current matrix by
44689
- * @param {number} c - the c component to multiply the current matrix by
44690
- * @param {number} d - the d component to multiply the current matrix by
44691
- * @param {number} e - the e component to multiply the current matrix by
44692
- * @param {number} f - the f component to multiply the current matrix by
45349
+ * @param {number} [b] - the b component to multiply the current matrix by; omitted when `a` is a matrix
45350
+ * @param {number} [c] - the c component to multiply the current matrix by; omitted when `a` is a matrix
45351
+ * @param {number} [d] - the d component to multiply the current matrix by; omitted when `a` is a matrix
45352
+ * @param {number} [e] - the e component to multiply the current matrix by; omitted when `a` is a matrix
45353
+ * @param {number} [f] - the f component to multiply the current matrix by; omitted when `a` is a matrix
44693
45354
  */
44694
45355
  transform(a, b, c, d, e, f) {
44695
45356
  if (typeof a === "object") {
@@ -45678,6 +46339,9 @@ function dispatchEvent(normalizedEvents2) {
45678
46339
  }
45679
46340
  lastTimeStamp = pointer2.event.timeStamp;
45680
46341
  }
46342
+ const worldOffset = _app2.world.pos;
46343
+ const absoluteWorldX = pointer2.gameWorldX + worldOffset.x;
46344
+ const absoluteWorldY = pointer2.gameWorldY + worldOffset.y;
45681
46345
  currentPointer.pos.set(pointer2.gameWorldX, pointer2.gameWorldY);
45682
46346
  currentPointer.setSize(pointer2.width, pointer2.height);
45683
46347
  if (POINTER_MOVE.includes(pointer2.type)) {
@@ -45690,6 +46354,22 @@ function dispatchEvent(normalizedEvents2) {
45690
46354
  (a, b) => _app2.world._sortReverseZ(a, b),
45691
46355
  void 0
45692
46356
  );
46357
+ if (worldOffset.x !== 0 || worldOffset.y !== 0) {
46358
+ currentPointer.pos.set(absoluteWorldX, absoluteWorldY);
46359
+ const absoluteCandidates = _app2.world.broadphase.retrieve(
46360
+ currentPointer,
46361
+ (a, b) => _app2.world._sortReverseZ(a, b),
46362
+ void 0
46363
+ );
46364
+ for (const candidate of candidates) {
46365
+ if (candidate.isFloating === true && !absoluteCandidates.includes(candidate)) {
46366
+ absoluteCandidates.push(candidate);
46367
+ }
46368
+ }
46369
+ candidates = absoluteCandidates.sort(
46370
+ (a, b) => _app2.world._sortReverseZ(a, b)
46371
+ );
46372
+ }
45693
46373
  candidates = candidates.concat([_app2.viewport]);
45694
46374
  for (let c = candidates.length, candidate; c--, candidate = candidates[c]; ) {
45695
46375
  if (eventHandlers.has(candidate) && candidate.isKinematic !== true) {
@@ -45706,10 +46386,15 @@ function dispatchEvent(normalizedEvents2) {
45706
46386
  }
45707
46387
  if (typeof ancestor !== "undefined") {
45708
46388
  const parentBounds = ancestor.getBounds();
45709
- pointer2.gameLocalX = pointer2.gameX - parentBounds.x;
45710
- pointer2.gameLocalY = pointer2.gameY - parentBounds.y;
46389
+ if (region.isFloating === true) {
46390
+ pointer2.gameLocalX = pointer2.gameX - parentBounds.x;
46391
+ pointer2.gameLocalY = pointer2.gameY - parentBounds.y;
46392
+ } else {
46393
+ pointer2.gameLocalX = absoluteWorldX - parentBounds.x;
46394
+ pointer2.gameLocalY = absoluteWorldY - parentBounds.y;
46395
+ }
45711
46396
  }
45712
- const eventInBounds = bounds.contains(pointer2.gameX, pointer2.gameY);
46397
+ const eventInBounds = region.isFloating === true ? bounds.contains(pointer2.gameX, pointer2.gameY) : bounds.contains(absoluteWorldX, absoluteWorldY);
45713
46398
  switch (pointer2.type) {
45714
46399
  case POINTER_MOVE[0]:
45715
46400
  case POINTER_MOVE[1]:
@@ -48368,14 +49053,23 @@ var Detector = class {
48368
49053
  objB.onCollisionActive(viewB, objA);
48369
49054
  }
48370
49055
  }
49056
+ if (objA.body === void 0 || objB.body === void 0) {
49057
+ continue;
49058
+ }
48371
49059
  const eitherSensor = objA.body.isSensor === true || objB.body.isSensor === true || this.response.isTriggerContact === true;
48372
49060
  const aHasModern = typeof objA.onCollisionActive === "function";
48373
49061
  const bHasModern = typeof objB.onCollisionActive === "function";
48374
49062
  const aOptsOut = !aHasModern && typeof objA.onCollision === "function" && objA.onCollision(this.response, objB) === false;
49063
+ if (objA.body === void 0 || objB.body === void 0) {
49064
+ continue;
49065
+ }
48375
49066
  if (!aOptsOut && objA.body.isStatic === false && !eitherSensor) {
48376
49067
  objA.body.respondToCollision.call(objA.body, this.response);
48377
49068
  }
48378
49069
  const bOptsOut = !bHasModern && typeof objB.onCollision === "function" && objB.onCollision(this.response, objA) === false;
49070
+ if (objA.body === void 0 || objB.body === void 0) {
49071
+ continue;
49072
+ }
48379
49073
  if (!bOptsOut && objB.body.isStatic === false && !eitherSensor) {
48380
49074
  objB.body.respondToCollision.call(objB.body, this.response);
48381
49075
  }
@@ -48651,8 +49345,10 @@ var BuiltinAdapter = class {
48651
49345
  this.detector.collisions(ancestor);
48652
49346
  }
48653
49347
  }
48654
- body.force.set(0, 0);
48655
- body.forceZ = 0;
49348
+ if (body.force !== void 0) {
49349
+ body.force.set(0, 0);
49350
+ body.forceZ = 0;
49351
+ }
48656
49352
  }
48657
49353
  this.detector.endFrame();
48658
49354
  }
@@ -49383,6 +50079,7 @@ var Light2d = class extends Renderable {
49383
50079
  };
49384
50080
 
49385
50081
  // src/particles/particle.ts
50082
+ var _spawn = new Vector2d();
49386
50083
  var Particle = class extends Renderable {
49387
50084
  vel;
49388
50085
  image;
@@ -49394,7 +50091,6 @@ var Particle = class extends Renderable {
49394
50091
  wind;
49395
50092
  followTrajectory;
49396
50093
  onlyInViewport;
49397
- accurateBounds;
49398
50094
  _deltaInv;
49399
50095
  _halfW;
49400
50096
  _halfH;
@@ -49414,13 +50110,40 @@ var Particle = class extends Renderable {
49414
50110
  this.vel = vector2dPool.get();
49415
50111
  this.onResetEvent(emitter, true);
49416
50112
  }
50113
+ /**
50114
+ * Whether the bounds need recomputing before anyone reads them.
50115
+ *
50116
+ * A `Renderable` refreshes its bounds eagerly, from a callback fired on
50117
+ * every `pos` assignment. That is the right trade for a scene object, and
50118
+ * the wrong one for a particle: `update()` writes `pos.x` and `pos.y`
50119
+ * separately, so the callback fires TWICE per particle per frame, and both
50120
+ * runs happen before `currentTransform` is rebuilt — deriving bounds from
50121
+ * the previous frame's matrix and then throwing that away. Measured, the
50122
+ * redundant pass was about two thirds of the whole particle update loop.
50123
+ *
50124
+ * So a particle invalidates instead of recomputing, and pays once, on
50125
+ * read, for particles something actually looks at.
50126
+ *
50127
+ * Deliberately not a `#private` field: `updateBounds()` is reached from the
50128
+ * base constructor chain (`Polygon.setVertices`) before a subclass's field
50129
+ * initializers have run, and writing an undeclared private field throws.
50130
+ * @ignore
50131
+ */
50132
+ _boundsDirty = true;
49417
50133
  /**
49418
50134
  * @ignore
49419
50135
  */
49420
50136
  onResetEvent(emitter, newInstance = false) {
49421
50137
  const image = emitter.settings.image;
49422
- if (!newInstance) {
50138
+ const map = emitter._spawnMap;
50139
+ if (typeof map !== "undefined") {
50140
+ _spawn.set(emitter.getRandomPointX(), emitter.getRandomPointY());
50141
+ map.apply(_spawn);
50142
+ this.pos.set(_spawn.x, _spawn.y);
50143
+ } else {
49423
50144
  this.pos.set(emitter.getRandomPointX(), emitter.getRandomPointY());
50145
+ }
50146
+ if (!newInstance) {
49424
50147
  this.resize(image.width, image.height);
49425
50148
  this.currentTransform.identity();
49426
50149
  }
@@ -49428,7 +50151,13 @@ var Particle = class extends Renderable {
49428
50151
  this._halfW = this.width / 2;
49429
50152
  this._halfH = this.height / 2;
49430
50153
  this.alwaysUpdate = true;
50154
+ this.pos.setCallback(() => {
50155
+ this._boundsDirty = true;
50156
+ this.isDirty = true;
50157
+ });
50158
+ this._boundsDirty = true;
49431
50159
  this.anchorPoint.set(0, 0);
50160
+ this.autoTransform = false;
49432
50161
  if (typeof emitter.settings.tint === "string") {
49433
50162
  this.tint.parseCSS(emitter.settings.tint);
49434
50163
  }
@@ -49453,7 +50182,6 @@ var Particle = class extends Renderable {
49453
50182
  this.wind = emitter.settings.wind;
49454
50183
  this.followTrajectory = emitter.settings.followTrajectory;
49455
50184
  this.onlyInViewport = emitter.settings.onlyInViewport;
49456
- this.accurateBounds = emitter.settings.accurateBounds;
49457
50185
  this._deltaInv = emitter._deltaInv;
49458
50186
  if (!emitter.settings.followTrajectory) {
49459
50187
  this._angle = randomFloat(
@@ -49518,12 +50246,108 @@ var Particle = class extends Renderable {
49518
50246
  0,
49519
50247
  1
49520
50248
  );
49521
- if (this.accurateBounds) {
49522
- this.updateBounds();
49523
- }
49524
50249
  this.isDirty = this.inViewport || !this.onlyInViewport;
49525
50250
  return super.update(dt);
49526
50251
  }
50252
+ /**
50253
+ * `autoTransform` is off (see `onResetEvent`), so the base `preDraw` will
50254
+ * not apply the matrix — append it here instead, unconjugated. Appending
50255
+ * after `super` rather than splicing into it is order-equivalent for a
50256
+ * particle specifically: no flip, no mask, and the anchor is zeroed, so
50257
+ * nothing the base method emits interacts with this.
50258
+ * @ignore
50259
+ */
50260
+ preDraw(renderer) {
50261
+ super.preDraw(renderer);
50262
+ if (!this.currentTransform.isIdentity()) {
50263
+ renderer.transform(this.currentTransform);
50264
+ }
50265
+ }
50266
+ /**
50267
+ * `currentTransform` already places the particle, so the frame it
50268
+ * produces is positioned — only the ancestors' contribution is still
50269
+ * missing. The base implementation would add this particle's own `pos` on
50270
+ * top of a matrix that already contains it, counting it twice.
50271
+ * @ignore
50272
+ */
50273
+ /**
50274
+ * Bounds are recomputed here rather than when `pos` moves, so a particle
50275
+ * nothing looks at this frame pays nothing at all.
50276
+ *
50277
+ * `updateBounds()` deliberately keeps its eager contract — a caller that
50278
+ * asks for a recompute, such as {@link Container} aggregating child bounds
50279
+ * under `enableChildBoundsUpdate`, still gets fresh values back.
50280
+ * @ignore
50281
+ */
50282
+ getBounds() {
50283
+ const bounds = super.getBounds();
50284
+ if (this._boundsDirty) {
50285
+ this.updateBounds();
50286
+ }
50287
+ return bounds;
50288
+ }
50289
+ updateBounds(absolute = true) {
50290
+ this._boundsDirty = false;
50291
+ if (!this.isRenderable) {
50292
+ return super.updateBounds(absolute);
50293
+ }
50294
+ const bounds = this.getBounds();
50295
+ bounds.clear();
50296
+ bounds.addFrame(0, 0, this.width, this.height, this.currentTransform);
50297
+ if (absolute && this.ancestor) {
50298
+ const absPos = this.#frameOrigin().getAbsolutePosition();
50299
+ bounds.centerOn(
50300
+ absPos.x + bounds.x + bounds.width / 2,
50301
+ absPos.y + bounds.y + bounds.height / 2
50302
+ );
50303
+ }
50304
+ return bounds;
50305
+ }
50306
+ /**
50307
+ * The container this particle's position is measured from — its emitter
50308
+ * under the default local reference space, something else otherwise.
50309
+ * @ignore
50310
+ */
50311
+ #frameOrigin() {
50312
+ const emitter = this.ancestor;
50313
+ const space = emitter?.settings?.referenceSpace;
50314
+ if (typeof space === "undefined" || space === "local") {
50315
+ return emitter;
50316
+ }
50317
+ if (space === "world") {
50318
+ return emitter.ancestor ?? emitter;
50319
+ }
50320
+ const target = space;
50321
+ return target && typeof target.pos !== "undefined" ? target : emitter;
50322
+ }
50323
+ /**
50324
+ * A particle is positioned within its reference frame, which is not
50325
+ * necessarily its parent — so summing up the ancestor chain, as the base
50326
+ * implementation does, would measure from the wrong place.
50327
+ * @ignore
50328
+ */
50329
+ getAbsolutePosition() {
50330
+ const origin = this.#frameOrigin();
50331
+ if (origin === this.ancestor) {
50332
+ return super.getAbsolutePosition();
50333
+ }
50334
+ if (typeof this._absPos === "undefined") {
50335
+ this._absPos = vector3dPool.get();
50336
+ }
50337
+ this._absPos.set(this.pos.x, this.pos.y, this.depth);
50338
+ if (!this.floating) {
50339
+ this._absPos.add(origin.getAbsolutePosition());
50340
+ }
50341
+ return this._absPos;
50342
+ }
50343
+ /**
50344
+ * With the placement in `currentTransform` and `autoTransform` off, the
50345
+ * base composition would describe a transform this class never applies.
50346
+ * @ignore
50347
+ */
50348
+ getLocalTransform(out) {
50349
+ return out.copy(this.currentTransform);
50350
+ }
49527
50351
  /**
49528
50352
  * @ignore
49529
50353
  */
@@ -49762,6 +50586,27 @@ var Entity = class _Entity extends Renderable {
49762
50586
  );
49763
50587
  }
49764
50588
  }
50589
+ /**
50590
+ * An entity replaces {@link Renderable#preDraw} wholesale — no flip, no
50591
+ * conjugation, no anchor offset, just its position biased by the body
50592
+ * bounds — so the inherited composition would describe a transform this
50593
+ * class never applies. Mirrors the `preDraw` above instead.
50594
+ * @protected
50595
+ * @param {Matrix3d} out - matrix to write into
50596
+ * @returns {Matrix3d} `out`, for chaining
50597
+ */
50598
+ getLocalTransform(out) {
50599
+ const bounds = this.body.getBounds();
50600
+ out.identity();
50601
+ out.translate(this.pos.x + bounds.x, this.pos.y + bounds.y);
50602
+ if (this.renderable instanceof Renderable) {
50603
+ out.translate(
50604
+ this.anchorPoint.x * bounds.width,
50605
+ this.anchorPoint.y * bounds.height
50606
+ );
50607
+ }
50608
+ return out;
50609
+ }
49765
50610
  /**
49766
50611
  * draw this entity (automatically called by melonJS)
49767
50612
  * @param {CanvasRenderer|WebGLRenderer} renderer - a renderer instance
@@ -50392,7 +51237,7 @@ var Trigger = class extends Renderable {
50392
51237
  };
50393
51238
 
50394
51239
  // src/version.ts
50395
- var version = "20.1.0";
51240
+ var version = "20.2.0";
50396
51241
 
50397
51242
  // src/system/bootstrap.ts
50398
51243
  var initialized = false;
@@ -52508,72 +53353,12 @@ var FRAME_UNIFORM_SIZE = 80;
52508
53353
  var CLEAR_UNIFORM_SIZE = 16;
52509
53354
 
52510
53355
  // src/video/webgpu/pipeline/cache.js
53356
+ function gpuBlendState(mode, premultipliedAlpha) {
53357
+ const component = blendStateFor(mode, premultipliedAlpha);
53358
+ return typeof component === "undefined" ? void 0 : { color: component, alpha: component };
53359
+ }
52511
53360
  var MAX_QUAD_TEXTURES = 8;
52512
53361
  var DEPTH_STENCIL_FORMAT = "depth24plus-stencil8";
52513
- function blendStateFor(mode, premultipliedAlpha) {
52514
- const src = premultipliedAlpha ? "one" : "src-alpha";
52515
- let component;
52516
- switch (mode) {
52517
- case "none":
52518
- return void 0;
52519
- case "additive":
52520
- component = { operation: "add", srcFactor: src, dstFactor: "one" };
52521
- break;
52522
- case "multiply":
52523
- component = {
52524
- operation: "add",
52525
- srcFactor: "dst",
52526
- dstFactor: "one-minus-src-alpha"
52527
- };
52528
- break;
52529
- case "exclusion":
52530
- component = {
52531
- operation: "add",
52532
- srcFactor: "one-minus-dst",
52533
- dstFactor: "one-minus-src"
52534
- };
52535
- break;
52536
- case "screen":
52537
- component = {
52538
- operation: "add",
52539
- srcFactor: "one",
52540
- dstFactor: "one-minus-src"
52541
- };
52542
- break;
52543
- case "darken":
52544
- component = { operation: "min", srcFactor: "one", dstFactor: "one" };
52545
- break;
52546
- case "lighten":
52547
- component = { operation: "max", srcFactor: "one", dstFactor: "one" };
52548
- break;
52549
- default:
52550
- component = {
52551
- operation: "add",
52552
- srcFactor: src,
52553
- dstFactor: "one-minus-src-alpha"
52554
- };
52555
- break;
52556
- }
52557
- return { color: component, alpha: component };
52558
- }
52559
- function normalizeBlendMode(mode) {
52560
- switch (mode) {
52561
- case "none":
52562
- case "multiply":
52563
- case "screen":
52564
- case "darken":
52565
- case "lighten":
52566
- case "exclusion":
52567
- case "normal":
52568
- return mode;
52569
- case "additive":
52570
- case "add":
52571
- case "lighter":
52572
- return "additive";
52573
- default:
52574
- return "normal";
52575
- }
52576
- }
52577
53362
  var STENCIL_STATES = {
52578
53363
  none: {
52579
53364
  stencil: {
@@ -52919,7 +53704,7 @@ var WebGPUPipelineCache = class _WebGPUPipelineCache {
52919
53704
  targets: [
52920
53705
  {
52921
53706
  format: this.format,
52922
- blend: shaderKey === "clear" ? void 0 : blendStateFor(blend, pma),
53707
+ blend: shaderKey === "clear" ? void 0 : gpuBlendState(blend, pma),
52923
53708
  writeMask: stencil.colorWriteMask
52924
53709
  }
52925
53710
  ]
@@ -53000,9 +53785,16 @@ var WebGPUQuadBatcher = class extends WebGPUBatcher {
53000
53785
  },
53001
53786
  onEvict: (slot) => {
53002
53787
  this.segmentEntries[slot] = void 0;
53788
+ if (this._memoSlot === slot) {
53789
+ this._memoView = null;
53790
+ }
53003
53791
  }
53004
53792
  });
53005
53793
  this.segmentEntries = [];
53794
+ this._memoView = null;
53795
+ this._memoFilter = null;
53796
+ this._memoWrap = null;
53797
+ this._memoSlot = 0;
53006
53798
  this.segmentGroup = null;
53007
53799
  this.composedGroups = /* @__PURE__ */ new Map();
53008
53800
  this.resourceIds = /* @__PURE__ */ new WeakMap();
@@ -53101,17 +53893,24 @@ var WebGPUQuadBatcher = class extends WebGPUBatcher {
53101
53893
  const record = store.getResidentRecord(texture, { force: reupload });
53102
53894
  const wrap = texture.repeat ?? "no-repeat";
53103
53895
  const filter = typeof texture.filter === "string" ? texture.filter : renderer.getDefaultTextureFilter();
53896
+ if (record.view === this._memoView && filter === this._memoFilter && wrap === this._memoWrap) {
53897
+ return this._memoSlot;
53898
+ }
53104
53899
  const slotKey = `${this.resourceId(record.view)}|${filter}|${wrap}`;
53105
- if (this.slotTable.peek(slotKey) === void 0) {
53106
- const slot = this.slotTable.slotFor(slotKey);
53107
- this.segmentEntries[slot] = {
53900
+ let resolved = this.slotTable.peek(slotKey);
53901
+ if (resolved === void 0) {
53902
+ resolved = this.slotTable.slotFor(slotKey);
53903
+ this.segmentEntries[resolved] = {
53108
53904
  view: record.view,
53109
53905
  sampler: store.getSampler(filter, wrap)
53110
53906
  };
53111
53907
  this.segmentGroup = null;
53112
- return slot;
53113
53908
  }
53114
- return this.slotTable.peek(slotKey);
53909
+ this._memoView = record.view;
53910
+ this._memoFilter = filter;
53911
+ this._memoWrap = wrap;
53912
+ this._memoSlot = resolved;
53913
+ return resolved;
53115
53914
  }
53116
53915
  /**
53117
53916
  * a stable id for a GPU resource object (bind-group composition keys)
@@ -55151,7 +55950,7 @@ function resolveDepthOps(meshDepthActive, pendingDepthClear) {
55151
55950
  depthStoreOp: "store"
55152
55951
  };
55153
55952
  }
55154
- var WebGPURenderer = class extends Renderer {
55953
+ var WebGPURenderer = class _WebGPURenderer extends Renderer {
55155
55954
  /**
55156
55955
  * @param {ApplicationSettings} options - The renderer parameters
55157
55956
  */
@@ -55188,6 +55987,13 @@ var WebGPURenderer = class extends Renderer {
55188
55987
  this.commandEncoder = null;
55189
55988
  this.renderPass = null;
55190
55989
  this.currentRenderTarget = null;
55990
+ this._advancedBlendEffect = void 0;
55991
+ this._advancedBlendCapture = void 0;
55992
+ this._advancedBlendTarget = void 0;
55993
+ this._advancedBlendOpen = false;
55994
+ this._advancedBlendBusy = 0;
55995
+ this._advancedBlendParent = null;
55996
+ this._advancedBlendWarned = /* @__PURE__ */ new Set();
55191
55997
  this.pendingColorClear = false;
55192
55998
  this.pendingClearValue = null;
55193
55999
  this.pendingStencilClear = false;
@@ -55726,6 +56532,16 @@ var WebGPURenderer = class extends Renderer {
55726
56532
  * frame, or null when no device is available
55727
56533
  */
55728
56534
  toFrameTexture(options = {}) {
56535
+ const busy = this._advancedBlendBusy;
56536
+ this._advancedBlendBusy = (busy ?? 0) + 1;
56537
+ try {
56538
+ return _WebGPURenderer.prototype._toFrameTexture.call(this, options);
56539
+ } finally {
56540
+ this._advancedBlendBusy = busy;
56541
+ }
56542
+ }
56543
+ /** @ignore */
56544
+ _toFrameTexture(options = {}) {
55729
56545
  if (typeof this.device === "undefined") {
55730
56546
  return null;
55731
56547
  }
@@ -55941,6 +56757,7 @@ var WebGPURenderer = class extends Renderer {
55941
56757
  * @override
55942
56758
  */
55943
56759
  blitEffect(source, x, y, width, height, effect, keepBlend = false) {
56760
+ this._advancedBlendBusy++;
55944
56761
  const batcher = this.setBatcher("quad");
55945
56762
  blitSavedProjection.copy(this.projectionMatrix);
55946
56763
  this.projectionMatrix.ortho(0, width, height, 0, -1, 1);
@@ -55948,6 +56765,106 @@ var WebGPURenderer = class extends Renderer {
55948
56765
  batcher.blitTexture(source, x, y, width, height, effect, keepBlend);
55949
56766
  this.projectionMatrix.copy(blitSavedProjection);
55950
56767
  this.pushFrameGlobals();
56768
+ this._advancedBlendBusy--;
56769
+ }
56770
+ /**
56771
+ * Say once, per excluded path and mode, that an advanced blend mode is
56772
+ * falling back to `"normal"`. A silent data-dependent fallback is exactly
56773
+ * what the advanced-blend work exists to remove, so the exclusions are
56774
+ * loud.
56775
+ * @param {string} what - the excluded path, named for the message
56776
+ * @ignore
56777
+ */
56778
+ _warnAdvancedBlendFallback(what) {
56779
+ const key = `${what}:${this.currentBlendMode}`;
56780
+ if (this._advancedBlendWarned.has(key)) {
56781
+ return;
56782
+ }
56783
+ this._advancedBlendWarned.add(key);
56784
+ console.warn(
56785
+ `WebGPURenderer: blend mode "${this.currentBlendMode}" is not supported for ${what} and falls back to "normal"`
56786
+ );
56787
+ }
56788
+ /**
56789
+ * Close a pending advanced-blend bracket, if one is open and we are not
56790
+ * already inside the bracket machinery.
56791
+ * @ignore
56792
+ */
56793
+ _drainAdvancedBlend() {
56794
+ if (this._advancedBlendOpen === true && this._advancedBlendBusy === 0) {
56795
+ this._closeAdvancedBlend();
56796
+ }
56797
+ }
56798
+ /**
56799
+ * Open an advanced-blend bracket: capture the destination, then redirect
56800
+ * this draw into an offscreen target.
56801
+ * @returns {boolean} false when the effect could not be realized, in which
56802
+ * case the draw proceeds unbracketed (plain source-over)
56803
+ * @ignore
56804
+ */
56805
+ _openAdvancedBlend() {
56806
+ const canvas = this.getCanvas();
56807
+ const w = canvas.width;
56808
+ const h = canvas.height;
56809
+ this._advancedBlendBusy++;
56810
+ if (typeof this._advancedBlendEffect === "undefined") {
56811
+ this._advancedBlendEffect = new BlendEffect(this);
56812
+ }
56813
+ if (this._advancedBlendEffect.enabled !== true) {
56814
+ this._advancedBlendBusy--;
56815
+ this._warnAdvancedBlendFallback("this device");
56816
+ return false;
56817
+ }
56818
+ this._advancedBlendEffect.setBlendMode(this.currentBlendMode);
56819
+ this.currentBatcher?.flush();
56820
+ this._advancedBlendCapture = this.toFrameTexture({
56821
+ target: this._advancedBlendCapture ?? null
56822
+ });
56823
+ this._advancedBlendEffect.setTexture(
56824
+ "backdrop",
56825
+ this._advancedBlendCapture
56826
+ );
56827
+ this._advancedBlendParent = this.currentRenderTarget;
56828
+ if (typeof this._advancedBlendTarget === "undefined") {
56829
+ this._advancedBlendTarget = new WebGPURenderTarget(this, w, h, {
56830
+ sampleCount: 1
56831
+ });
56832
+ } else {
56833
+ this._advancedBlendTarget.resize(w, h);
56834
+ }
56835
+ this.setRenderTarget(this._advancedBlendTarget, { clear: true });
56836
+ this._advancedBlendOpen = true;
56837
+ this._advancedBlendBusy--;
56838
+ return true;
56839
+ }
56840
+ /**
56841
+ * Close an advanced-blend bracket: drain the offscreen, retarget to the
56842
+ * parent, and composite through {@link BlendEffect}.
56843
+ * @ignore
56844
+ */
56845
+ _closeAdvancedBlend() {
56846
+ const canvas = this.getCanvas();
56847
+ const w = canvas.width;
56848
+ const h = canvas.height;
56849
+ this._advancedBlendBusy++;
56850
+ this._advancedBlendOpen = false;
56851
+ this.currentBatcher?.flush();
56852
+ this.setRenderTarget(this._advancedBlendParent);
56853
+ this._advancedBlendParent = null;
56854
+ const mode = this.currentBlendMode;
56855
+ this.setBlendMode("normal", true);
56856
+ this.blitEffect(
56857
+ this._advancedBlendTarget,
56858
+ 0,
56859
+ 0,
56860
+ w,
56861
+ h,
56862
+ this._advancedBlendEffect,
56863
+ true
56864
+ );
56865
+ this.currentBatcher?.flush();
56866
+ this.currentBlendMode = mode;
56867
+ this._advancedBlendBusy--;
55951
56868
  }
55952
56869
  /**
55953
56870
  * a draw is about to be recorded outside the clear()/flush() bracket
@@ -55971,6 +56888,9 @@ var WebGPURenderer = class extends Renderer {
55971
56888
  * @override
55972
56889
  */
55973
56890
  flush() {
56891
+ if (this._advancedBlendOpen === true && this._advancedBlendBusy === 0) {
56892
+ this._closeAdvancedBlend();
56893
+ }
55974
56894
  this.currentBatcher?.flush();
55975
56895
  if (this.renderPass !== null) {
55976
56896
  this.renderPass.end();
@@ -56005,6 +56925,9 @@ var WebGPURenderer = class extends Renderer {
56005
56925
  this.pendingColorClear = false;
56006
56926
  this.pendingDepthClear = false;
56007
56927
  this.currentPipeline = null;
56928
+ for (const batcher of this.batchers.values()) {
56929
+ batcher.reset();
56930
+ }
56008
56931
  this.destroyRetiredTextures();
56009
56932
  }
56010
56933
  /**
@@ -56060,6 +56983,7 @@ var WebGPURenderer = class extends Renderer {
56060
56983
  if (typeof this.device === "undefined") {
56061
56984
  return;
56062
56985
  }
56986
+ this._drainAdvancedBlend();
56063
56987
  this.currentBatcher?.flush();
56064
56988
  const pass = this.ensurePass();
56065
56989
  let rgba;
@@ -56156,6 +57080,18 @@ var WebGPURenderer = class extends Renderer {
56156
57080
  if (typeof batcher === "undefined") {
56157
57081
  throw new Error("Invalid Batcher");
56158
57082
  }
57083
+ if (this._advancedBlendBusy === 0) {
57084
+ if (this._advancedBlendOpen === true) {
57085
+ this._closeAdvancedBlend();
57086
+ }
57087
+ if (isAdvancedBlendMode(this.currentBlendMode) === true) {
57088
+ if (name === "mesh" || name === "litMesh") {
57089
+ this._warnAdvancedBlendFallback("3D meshes");
57090
+ } else {
57091
+ this._openAdvancedBlend();
57092
+ }
57093
+ }
57094
+ }
56159
57095
  if (this.currentBatcher !== batcher) {
56160
57096
  if (name !== "mesh" && name !== "litMesh" && // see WebGLRenderer#_canDrainShadows: a transition raised while
56161
57097
  // a mask is being stencilled in (colour writes off) or inside a
@@ -56392,22 +57328,56 @@ var WebGPURenderer = class extends Renderer {
56392
57328
  }
56393
57329
  }
56394
57330
  /**
56395
- * Set the blend mode for subsequent draws. Under WebGPU blending is
56396
- * pipeline state: the change is recorded (after draining vertices
56397
- * queued under the previous mode) and the next batcher flush picks the
56398
- * matching pipeline variant.
56399
- * @param {string} [mode="normal"] - blend mode ("normal", "multiply", "additive"/"lighter", "screen", "darken", "lighten", "none")
57331
+ * set the current blend mode for this renderer. <br>
57332
+ * Every renderer supports the full set: <br>
57333
+ * - "normal" : draws new content on top of the existing content <br>
57334
+ * <img src="../images/normal-blendmode.png" width="180"/> <br>
57335
+ * - "add", "additive", or "lighter" : color values are added together <br>
57336
+ * <img src="../images/add-blendmode.png" width="180"/> <br>
57337
+ * - "multiply" : pixels are multiplied, resulting in a darker picture <br>
57338
+ * <img src="../images/multiply-blendmode.png" width="180"/> <br>
57339
+ * - "screen" : pixels are inverted, multiplied, and inverted again (opposite of multiply) <br>
57340
+ * <img src="../images/screen-blendmode.png" width="180"/> <br>
57341
+ * - "darken" : retains the darkest pixels of both layers <br>
57342
+ * <img src="../images/darken-blendmode.png" width="180"/> <br>
57343
+ * - "lighten" : retains the lightest pixels of both layers <br>
57344
+ * <img src="../images/lighten-blendmode.png" width="180"/> <br>
57345
+ * - "overlay" : multiplies or screens, depending on the backdrop <br>
57346
+ * <img src="../images/overlay-blendmode.png" width="180"/> <br>
57347
+ * - "hard-light" : overlay with the layers swapped — a harsh spotlight <br>
57348
+ * <img src="../images/hard-light-blendmode.png" width="180"/> <br>
57349
+ * - "soft-light" : a diffused spotlight, gentler than hard-light <br>
57350
+ * <img src="../images/soft-light-blendmode.png" width="180"/> <br>
57351
+ * - "color-dodge" : brightens the backdrop to reflect the source <br>
57352
+ * <img src="../images/color-dodge-blendmode.png" width="180"/> <br>
57353
+ * - "color-burn" : darkens the backdrop to reflect the source <br>
57354
+ * <img src="../images/color-burn-blendmode.png" width="180"/> <br>
57355
+ * - "difference" : the absolute difference of the two layers <br>
57356
+ * <img src="../images/difference-blendmode.png" width="180"/> <br>
57357
+ * - "exclusion" : like difference, but lower in contrast <br>
57358
+ * <img src="../images/exclusion-blendmode.png" width="180"/> <br>
57359
+ * - "none" : blending disabled — the source replaces the destination
57360
+ * outright, alpha included <br>
57361
+ * A few draw types cannot honour every mode — 3D meshes, and fills using
57362
+ * a {@link Gradient} — and fall back to "normal" with a one-time console
57363
+ * warning. `setBlendMode` returns what it actually applied, so comparing
57364
+ * the result against your request detects that case.
57365
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
57366
+ * @param {string} [mode="normal"] - blend mode
56400
57367
  * @param {boolean} [premultipliedAlpha=true] - whether textures use premultiplied alpha (affects the source blend factor)
56401
- * @returns {string} the blend mode actually applied
57368
+ * @returns {string} the blend mode actually applied (may differ if the requested mode is unsupported)
56402
57369
  */
56403
57370
  setBlendMode(mode = "normal", premultipliedAlpha = true) {
56404
57371
  const normalized = normalizeBlendMode(mode);
56405
- if (this.currentBlendMode !== normalized || this.premultipliedAlpha !== premultipliedAlpha) {
57372
+ const advanced = isAdvancedBlendMode(mode);
57373
+ const tracked = advanced === true ? mode : normalized;
57374
+ if (this.currentBlendMode !== tracked || this.premultipliedAlpha !== premultipliedAlpha) {
57375
+ this._drainAdvancedBlend();
56406
57376
  this.currentBatcher?.flush();
56407
- this.currentBlendMode = normalized;
57377
+ this.currentBlendMode = tracked;
56408
57378
  this.premultipliedAlpha = premultipliedAlpha;
56409
57379
  }
56410
- return normalized === "normal" && mode !== "normal" ? "normal" : mode;
57380
+ return reportedBlendMode(mode);
56411
57381
  }
56412
57382
  /**
56413
57383
  * Set the projection matrix — pushes a fresh frame-globals slot so
@@ -56895,6 +57865,11 @@ var WebGPURenderer = class extends Renderer {
56895
57865
  const grad = this.currentGradient;
56896
57866
  const hasMask = this.maskLevel > 0;
56897
57867
  this.currentGradient = null;
57868
+ if (isAdvancedBlendMode(this.currentBlendMode) === true) {
57869
+ this._warnAdvancedBlendFallback("gradient fills");
57870
+ this._drainAdvancedBlend();
57871
+ }
57872
+ this._advancedBlendBusy++;
56898
57873
  this.currentBatcher?.flush();
56899
57874
  const visibleRef = this.maskVisibleRef;
56900
57875
  let markRef = 1;
@@ -56930,6 +57905,7 @@ var WebGPURenderer = class extends Renderer {
56930
57905
  this.stencilMode = "none";
56931
57906
  }
56932
57907
  this.ensurePass().setStencilReference(this.maskVisibleRef);
57908
+ this._advancedBlendBusy--;
56933
57909
  }
56934
57910
  /**
56935
57911
  * Stroke an arc at the specified coordinates with given radius, start and end points
@@ -57587,6 +58563,14 @@ var WebGPURenderer = class extends Renderer {
57587
58563
  * @override
57588
58564
  */
57589
58565
  destroy() {
58566
+ this._advancedBlendEffect?.destroy();
58567
+ this._advancedBlendEffect = void 0;
58568
+ this._advancedBlendCapture?.destroy();
58569
+ this._advancedBlendCapture = void 0;
58570
+ this._advancedBlendTarget?.destroy?.();
58571
+ this._advancedBlendTarget = void 0;
58572
+ this._advancedBlendOpen = false;
58573
+ this._advancedBlendBusy = 0;
57590
58574
  releaseShadowQuads(this);
57591
58575
  off(GAME_RESET, this.onGameReset);
57592
58576
  off(CANVAS_ONRESIZE, this.onCanvasResize);
@@ -58467,6 +59451,7 @@ var defaultParticleEmitterSettings = {
58467
59451
  blendMode: "normal",
58468
59452
  onlyInViewport: true,
58469
59453
  floating: false,
59454
+ referenceSpace: "local",
58470
59455
  maxParticles: 10,
58471
59456
  frequency: 100,
58472
59457
  duration: Infinity,
@@ -58478,6 +59463,10 @@ var defaultParticleEmitterSettings = {
58478
59463
  var settings_default = defaultParticleEmitterSettings;
58479
59464
 
58480
59465
  // src/particles/emitter.ts
59466
+ var _m1 = new Matrix3d();
59467
+ var _m2 = new Matrix3d();
59468
+ var _correction = new Matrix3d();
59469
+ var _rebase = new Vector2d();
58481
59470
  function createDefaultParticleTexture(w = 8, h = 8) {
58482
59471
  const defaultParticleTexture = new canvasrendertarget_default(w, h, {
58483
59472
  offscreenCanvas: true
@@ -58510,6 +59499,12 @@ var ParticleEmitter = class extends Container {
58510
59499
  _dt;
58511
59500
  /** @ignore */
58512
59501
  _defaultParticle;
59502
+ /**
59503
+ * The blend mode last fanned out to the particles, so a change to
59504
+ * {@link ParticleEmitter#blendMode} can be detected per frame.
59505
+ * @ignore
59506
+ */
59507
+ #appliedBlendMode = "normal";
58513
59508
  /**
58514
59509
  * whether at least one particle has been spawned by this emitter — used as
58515
59510
  * the precondition for completion detection (a brand-new emitter with zero
@@ -58523,6 +59518,19 @@ var ParticleEmitter = class extends Container {
58523
59518
  * @ignore
58524
59519
  */
58525
59520
  _deltaInv;
59521
+ /**
59522
+ * Maps an emitter-local spawn point into the reference frame the particles
59523
+ * live in, so a particle is BORN where the emitter is even though its
59524
+ * position is thereafter measured from somewhere else. `undefined` in
59525
+ * local mode, where the two frames coincide and no mapping is needed.
59526
+ *
59527
+ * Held per emitter (not a module scratch) because particles read it during
59528
+ * their own reset, after `addParticles` has computed it. One matrix per
59529
+ * emitter that actually uses a non-local space — emitters are few, unlike
59530
+ * particles, which allocate nothing.
59531
+ * @ignore
59532
+ */
59533
+ _spawnMap;
58526
59534
  /**
58527
59535
  * @param x - x position of the particle emitter
58528
59536
  * @param y - y position of the particle emitter
@@ -58569,11 +59577,8 @@ var ParticleEmitter = class extends Container {
58569
59577
  this._deltaInv = timer_default.maxfps / 1e3;
58570
59578
  this.reset(settings);
58571
59579
  }
58572
- /**
58573
- * Reset the emitter with particle emitter settings.
58574
- * @param settings - object with emitter settings. See {@link ParticleEmitterSettings}
58575
- */
58576
59580
  reset(settings = {}) {
59581
+ const previousFrame = this.#frameOf(this.settings?.referenceSpace);
58577
59582
  Object.assign(this.settings, settings_default, settings);
58578
59583
  clampMinToMax(this.settings, "minLife", "maxLife");
58579
59584
  clampMinToMax(this.settings, "minStartScale", "maxStartScale");
@@ -58592,8 +59597,131 @@ var ParticleEmitter = class extends Container {
58592
59597
  this.settings.image = this._defaultParticle.canvas;
58593
59598
  }
58594
59599
  this.floating = this.settings.floating;
59600
+ this.blendMode = this.settings.blendMode;
59601
+ this.#appliedBlendMode = this.settings.blendMode;
59602
+ this.#rebase(previousFrame);
59603
+ this.isDirty = true;
59604
+ }
59605
+ /**
59606
+ * What a particle's position is measured against — see
59607
+ * {@link ParticleEmitterSettings.referenceSpace}.
59608
+ *
59609
+ * Assigning this re-bases every particle already alive into the new frame,
59610
+ * so nothing jumps: the cloud stays exactly where it is on screen and only
59611
+ * its subsequent motion differs. Passing it through
59612
+ * {@link ParticleEmitter#reset} does the same.
59613
+ * @default "local"
59614
+ * @example
59615
+ * emitter.referenceSpace = "world"; // start leaving a trail
59616
+ */
59617
+ get referenceSpace() {
59618
+ return this.settings.referenceSpace;
59619
+ }
59620
+ set referenceSpace(space) {
59621
+ if (space === this.settings.referenceSpace) {
59622
+ return;
59623
+ }
59624
+ const previous = this.#frameOf(this.settings.referenceSpace);
59625
+ this.settings.referenceSpace = space;
59626
+ this.#rebase(previous);
59627
+ }
59628
+ /**
59629
+ * Map the particles already alive out of the frame they were simulating
59630
+ * in and into the current one, so a change of reference space is
59631
+ * invisible at the instant it happens: the cloud stays exactly where it
59632
+ * is on screen and only its subsequent motion differs.
59633
+ *
59634
+ * Called from the accessor and from {@link ParticleEmitter#reset} alike —
59635
+ * `reset()` assigns `settings` wholesale and would otherwise leave live
59636
+ * particles holding coordinates measured against a frame that is no
59637
+ * longer theirs, teleporting the lot.
59638
+ * @ignore
59639
+ * @param previous - the frame the live particles are currently in
59640
+ */
59641
+ #rebase(previous) {
59642
+ const next = this.#frameOf(this.settings.referenceSpace);
59643
+ if (previous !== next) {
59644
+ _m1.identity();
59645
+ _m1.multiply(this.#worldFrame(next, _m2).invert());
59646
+ _m1.multiply(this.#worldFrame(previous, _m2));
59647
+ for (const particle of this.getChildren()) {
59648
+ _rebase.set(particle.pos.x, particle.pos.y);
59649
+ _m1.apply(_rebase);
59650
+ particle.pos.x = _rebase.x;
59651
+ particle.pos.y = _rebase.y;
59652
+ }
59653
+ }
59654
+ this._spawnMap = void 0;
58595
59655
  this.isDirty = true;
58596
59656
  }
59657
+ /**
59658
+ * Resolve a reference-space value to the container whose frame the
59659
+ * particles live in. Returns `this` whenever the answer is "the emitter
59660
+ * itself" — including the degenerate cases (`"world"` on an emitter with no
59661
+ * parent, or a custom target that IS the emitter), which then take the
59662
+ * local fast path with no correction at all.
59663
+ * @ignore
59664
+ */
59665
+ #frameOf(space) {
59666
+ if (space === "local") {
59667
+ return this;
59668
+ }
59669
+ if (space === "world") {
59670
+ return this.ancestor ?? this;
59671
+ }
59672
+ if (!space || typeof space.pos === "undefined") {
59673
+ return this;
59674
+ }
59675
+ return space;
59676
+ }
59677
+ /**
59678
+ * The world transform of a reference frame — i.e. of the space that
59679
+ * container's children are drawn in.
59680
+ * @ignore
59681
+ */
59682
+ #worldFrame(frame, out) {
59683
+ return frame.getWorldTransform(out);
59684
+ }
59685
+ /**
59686
+ * The transform to insert before walking the children so they are drawn in
59687
+ * the reference frame instead of the emitter's own.
59688
+ *
59689
+ * At the insertion point the renderer holds `W_ancestor · preContrib`, and
59690
+ * `Container.draw` appends `T(pos)` afterwards, so what we need is
59691
+ *
59692
+ * ```
59693
+ * K = inv(preContrib) · inv(W_ancestor) · W_target · T(−pos)
59694
+ * ```
59695
+ *
59696
+ * expressed below via `inv(preContrib) = T(pos) · inv(L)`. Note this is
59697
+ * NOT `inv(W_emitter) · W_target` — matrices do not commute, and that form
59698
+ * only coincides with this one when both chains share the same linear
59699
+ * part, which hides the difference until something upstream is rotated.
59700
+ *
59701
+ * When the target is the emitter's own parent — the `"world"` case — the
59702
+ * whole ancestor chain cancels and no walk happens at all.
59703
+ * @ignore
59704
+ * @returns the correction, or `undefined` in local mode
59705
+ */
59706
+ #correctionMatrix() {
59707
+ const target = this.#frameOf(this.settings.referenceSpace);
59708
+ if (target === this) {
59709
+ return void 0;
59710
+ }
59711
+ _correction.identity();
59712
+ _correction.translate(this.pos.x, this.pos.y);
59713
+ _correction.multiply(this.getLocalTransform(_m1).invert());
59714
+ if (target !== this.ancestor) {
59715
+ if (this.ancestor) {
59716
+ _correction.multiply(
59717
+ this.ancestor.getWorldTransform(_m1).invert()
59718
+ );
59719
+ }
59720
+ _correction.multiply(target.getWorldTransform(_m2));
59721
+ }
59722
+ _correction.translate(-this.pos.x, -this.pos.y);
59723
+ return _correction;
59724
+ }
58597
59725
  /**
58598
59726
  * returns a random point on the x axis within the bounds of this emitter
58599
59727
  * @returns a random x position within the emitter bounds
@@ -58608,10 +59736,39 @@ var ParticleEmitter = class extends Container {
58608
59736
  getRandomPointY() {
58609
59737
  return randomFloat(0, this.getBounds().height);
58610
59738
  }
59739
+ /**
59740
+ * Draw the particles in their reference frame rather than the emitter's.
59741
+ *
59742
+ * The correction goes in before `super.draw()` because that is where the
59743
+ * child walk happens; translations and the correction compose, and in
59744
+ * local mode there is no correction and this is the inherited path
59745
+ * untouched.
59746
+ * @ignore
59747
+ */
59748
+ draw(renderer, viewport) {
59749
+ const correction = this.#correctionMatrix();
59750
+ if (correction !== void 0) {
59751
+ renderer.transform(correction);
59752
+ }
59753
+ super.draw(renderer, viewport);
59754
+ }
58611
59755
  // Add count particles in the game world
58612
59756
  /** @ignore */
58613
59757
  addParticles(count) {
58614
59758
  const z = this.depth;
59759
+ const target = this.#frameOf(this.settings.referenceSpace);
59760
+ if (target === this) {
59761
+ this._spawnMap = void 0;
59762
+ } else {
59763
+ const map = this._spawnMap ?? (this._spawnMap = new Matrix3d());
59764
+ if (target === this.ancestor) {
59765
+ map.copy(this.getLocalTransform(_m1));
59766
+ } else {
59767
+ map.identity();
59768
+ map.multiply(target.getWorldTransform(_m1).invert());
59769
+ map.multiply(this.getWorldTransform(_m2));
59770
+ }
59771
+ }
58615
59772
  for (let i = 0; i < count; i++) {
58616
59773
  this.addChild(particlePool.get(this), z);
58617
59774
  }
@@ -58659,6 +59816,14 @@ var ParticleEmitter = class extends Container {
58659
59816
  * @ignore
58660
59817
  */
58661
59818
  update(dt) {
59819
+ if (this.blendMode !== this.#appliedBlendMode) {
59820
+ this.#appliedBlendMode = this.blendMode;
59821
+ this.settings.blendMode = this.blendMode;
59822
+ for (const particle of this.children ?? []) {
59823
+ particle.blendMode = this.blendMode;
59824
+ }
59825
+ this.isDirty = true;
59826
+ }
58662
59827
  if (this.settings.framesToSkip > 0) {
58663
59828
  if (++this._updateCount > this.settings.framesToSkip) {
58664
59829
  this._updateCount = 0;
@@ -58672,6 +59837,9 @@ var ParticleEmitter = class extends Container {
58672
59837
  }
58673
59838
  const childrenDirty = super.update(dt);
58674
59839
  this.isDirty = this.isDirty || childrenDirty;
59840
+ if (this.#frameOf(this.settings.referenceSpace) !== this && this.getChildren().length > 0) {
59841
+ this.inViewport = true;
59842
+ }
58675
59843
  if (this._enabled && this._stream) {
58676
59844
  if (this._durationTimer !== Infinity) {
58677
59845
  this._durationTimer -= dt;
@@ -58715,6 +59883,8 @@ var ParticleEmitter = class extends Container {
58715
59883
  this._defaultParticle = void 0;
58716
59884
  }
58717
59885
  this.settings.image = void 0;
59886
+ this.settings.referenceSpace = "local";
59887
+ this._spawnMap = void 0;
58718
59888
  }
58719
59889
  };
58720
59890
 
@@ -60298,7 +61468,7 @@ var UITextButton = class extends UIBaseElement {
60298
61468
  };
60299
61469
 
60300
61470
  // src/video/effects/blur.js
60301
- var wgslFragment2 = `
61471
+ var wgslFragment3 = `
60302
61472
  struct BlurUniforms {
60303
61473
  uBlurStrength : f32,
60304
61474
  uTextureSize : vec2f,
@@ -60349,7 +61519,7 @@ var BlurEffect = class extends ShaderEffect {
60349
61519
  return (sum / 9.0) * vColor;
60350
61520
  }
60351
61521
  `,
60352
- wgsl: wgslFragment2
61522
+ wgsl: wgslFragment3
60353
61523
  });
60354
61524
  this.strength = options.strength ?? 1;
60355
61525
  const texSize = options.textureSize ?? [256, 256];
@@ -60375,7 +61545,7 @@ var BlurEffect = class extends ShaderEffect {
60375
61545
  };
60376
61546
 
60377
61547
  // src/video/effects/chromaticAberration.js
60378
- var wgslFragment3 = `
61548
+ var wgslFragment4 = `
60379
61549
  struct AberrationUniforms {
60380
61550
  uOffset : f32,
60381
61551
  uTextureSize : vec2f,
@@ -60412,7 +61582,7 @@ var ChromaticAberrationEffect = class extends ShaderEffect {
60412
61582
  return vec4(r, g, b, a) * vColor;
60413
61583
  }
60414
61584
  `,
60415
- wgsl: wgslFragment3
61585
+ wgsl: wgslFragment4
60416
61586
  });
60417
61587
  this.offset = options.offset ?? 3;
60418
61588
  const texSize = options.textureSize ?? [256, 256];
@@ -60460,7 +61630,7 @@ var DesaturateEffect = class extends ColorMatrixEffect {
60460
61630
  };
60461
61631
 
60462
61632
  // src/video/effects/dissolve.js
60463
- var wgslFragment4 = `
61633
+ var wgslFragment5 = `
60464
61634
  struct DissolveUniforms {
60465
61635
  uDissolveProgress : f32,
60466
61636
  uEdgeColor : vec3f,
@@ -60572,7 +61742,7 @@ var DissolveEffect = class extends ShaderEffect {
60572
61742
  return color;
60573
61743
  }
60574
61744
  `,
60575
- wgsl: wgslFragment4
61745
+ wgsl: wgslFragment5
60576
61746
  });
60577
61747
  this.progress = options.progress ?? 0;
60578
61748
  this.setUniform("uDissolveProgress", this.progress);
@@ -60600,7 +61770,7 @@ var DissolveEffect = class extends ShaderEffect {
60600
61770
  };
60601
61771
 
60602
61772
  // src/video/effects/dropShadow.js
60603
- var wgslFragment5 = `
61773
+ var wgslFragment6 = `
60604
61774
  struct ShadowUniforms {
60605
61775
  uShadowOffset : vec2f,
60606
61776
  uShadowColor : vec3f,
@@ -60664,7 +61834,7 @@ var DropShadowEffect = class extends ShaderEffect {
60664
61834
  return color;
60665
61835
  }
60666
61836
  `,
60667
- wgsl: wgslFragment5
61837
+ wgsl: wgslFragment6
60668
61838
  });
60669
61839
  const texSize = options.textureSize ?? [256, 256];
60670
61840
  this.setUniform(
@@ -60698,7 +61868,7 @@ var DropShadowEffect = class extends ShaderEffect {
60698
61868
  };
60699
61869
 
60700
61870
  // src/video/effects/flash.js
60701
- var wgslFragment6 = `
61871
+ var wgslFragment7 = `
60702
61872
  struct FlashUniforms {
60703
61873
  uFlashColor : vec3f,
60704
61874
  uFlashIntensity : f32,
@@ -60725,7 +61895,7 @@ var FlashEffect = class extends ShaderEffect {
60725
61895
  return vec4(mix(color.rgb, uFlashColor * color.a, uFlashIntensity), color.a);
60726
61896
  }
60727
61897
  `,
60728
- wgsl: wgslFragment6
61898
+ wgsl: wgslFragment7
60729
61899
  });
60730
61900
  const color = options.color ?? [1, 1, 1];
60731
61901
  this.intensity = options.intensity ?? 0;
@@ -60750,7 +61920,7 @@ var FlashEffect = class extends ShaderEffect {
60750
61920
  };
60751
61921
 
60752
61922
  // src/video/effects/glow.js
60753
- var wgslFragment7 = `
61923
+ var wgslFragment8 = `
60754
61924
  struct GlowUniforms {
60755
61925
  uGlowColor : vec3f,
60756
61926
  uGlowWidth : f32,
@@ -60811,7 +61981,7 @@ var GlowEffect = class extends ShaderEffect {
60811
61981
  return color;
60812
61982
  }
60813
61983
  `,
60814
- wgsl: wgslFragment7
61984
+ wgsl: wgslFragment8
60815
61985
  });
60816
61986
  const color = options.color ?? [1, 1, 1];
60817
61987
  const width = options.width ?? 3;
@@ -60848,7 +62018,7 @@ var GlowEffect = class extends ShaderEffect {
60848
62018
  };
60849
62019
 
60850
62020
  // src/video/effects/hologram.js
60851
- var wgslFragment8 = `
62021
+ var wgslFragment9 = `
60852
62022
  struct HologramUniforms {
60853
62023
  uHoloColor : vec3f,
60854
62024
  uHoloIntensity : f32,
@@ -60891,7 +62061,7 @@ var HologramEffect = class extends ShaderEffect {
60891
62061
  return vec4(holo, color.a * flicker);
60892
62062
  }
60893
62063
  `,
60894
- wgsl: wgslFragment8
62064
+ wgsl: wgslFragment9
60895
62065
  });
60896
62066
  const color = options.color ?? [0.1, 0.7, 1];
60897
62067
  this.setUniform("uHoloColor", new Float32Array(color));
@@ -60930,7 +62100,7 @@ var InvertEffect = class extends ColorMatrixEffect {
60930
62100
  };
60931
62101
 
60932
62102
  // src/video/effects/outline.js
60933
- var wgslFragment9 = `
62103
+ var wgslFragment10 = `
60934
62104
  struct OutlineUniforms {
60935
62105
  uOutlineColor : vec3f,
60936
62106
  uOutlineWidth : f32,
@@ -60994,7 +62164,7 @@ var OutlineEffect = class extends ShaderEffect {
60994
62164
  return color;
60995
62165
  }
60996
62166
  `,
60997
- wgsl: wgslFragment9
62167
+ wgsl: wgslFragment10
60998
62168
  });
60999
62169
  const color = options.color ?? [1, 1, 1];
61000
62170
  const width = options.width ?? 1;
@@ -61028,7 +62198,7 @@ var OutlineEffect = class extends ShaderEffect {
61028
62198
  };
61029
62199
 
61030
62200
  // src/video/effects/pixelate.js
61031
- var wgslFragment10 = `
62201
+ var wgslFragment11 = `
61032
62202
  struct PixelateUniforms {
61033
62203
  uPixelSize : f32,
61034
62204
  uTextureSize : vec2f,
@@ -61059,7 +62229,7 @@ var PixelateEffect = class extends ShaderEffect {
61059
62229
  return texture2D(uSampler, snapped) * vColor;
61060
62230
  }
61061
62231
  `,
61062
- wgsl: wgslFragment10
62232
+ wgsl: wgslFragment11
61063
62233
  });
61064
62234
  this.size = options.size ?? 4;
61065
62235
  const texSize = options.textureSize ?? [256, 256];
@@ -61085,7 +62255,7 @@ var PixelateEffect = class extends ShaderEffect {
61085
62255
  };
61086
62256
 
61087
62257
  // src/video/effects/scanline.js
61088
- var wgslFragment11 = `
62258
+ var wgslFragment12 = `
61089
62259
  struct ScanlineUniforms {
61090
62260
  uScanlineOpacity : f32,
61091
62261
  uCurvature : f32,
@@ -61164,7 +62334,7 @@ var ScanlineEffect = class extends ShaderEffect {
61164
62334
  return color;
61165
62335
  }
61166
62336
  `,
61167
- wgsl: wgslFragment11
62337
+ wgsl: wgslFragment12
61168
62338
  });
61169
62339
  this.setUniform("uScanlineOpacity", options.opacity ?? 0.25);
61170
62340
  this.setUniform("uCurvature", options.curvature ?? 0);
@@ -61216,7 +62386,7 @@ var SepiaEffect = class extends ColorMatrixEffect {
61216
62386
  };
61217
62387
 
61218
62388
  // src/video/effects/shine.js
61219
- var wgslFragment12 = `
62389
+ var wgslFragment13 = `
61220
62390
  struct ShineUniforms {
61221
62391
  uShineColor : vec3f,
61222
62392
  uShineWidth : f32,
@@ -61312,7 +62482,7 @@ var ShineEffect = class extends ShaderEffect {
61312
62482
  return vec4(result, color.a);
61313
62483
  }
61314
62484
  `,
61315
- wgsl: wgslFragment12
62485
+ wgsl: wgslFragment13
61316
62486
  });
61317
62487
  this.setUniform(
61318
62488
  "uShineColor",
@@ -61359,7 +62529,7 @@ var ShineEffect = class extends ShaderEffect {
61359
62529
  };
61360
62530
 
61361
62531
  // src/video/effects/tintPulse.js
61362
- var wgslFragment13 = `
62532
+ var wgslFragment14 = `
61363
62533
  struct TintPulseUniforms {
61364
62534
  uPulseColor : vec3f,
61365
62535
  uPulseSpeed : f32,
@@ -61393,7 +62563,7 @@ var TintPulseEffect = class extends ShaderEffect {
61393
62563
  return vec4(mix(color.rgb, uPulseColor * color.a, pulse), color.a);
61394
62564
  }
61395
62565
  `,
61396
- wgsl: wgslFragment13
62566
+ wgsl: wgslFragment14
61397
62567
  });
61398
62568
  const color = options.color ?? [1, 0, 0];
61399
62569
  this.setUniform("uPulseColor", new Float32Array(color));
@@ -61427,7 +62597,7 @@ var fragment = `
61427
62597
  return vec4(color.rgb * v, color.a);
61428
62598
  }
61429
62599
  `;
61430
- var wgslFragment14 = `
62600
+ var wgslFragment15 = `
61431
62601
  struct VignetteUniforms {
61432
62602
  uStrength : f32,
61433
62603
  uSize : f32,
@@ -61448,7 +62618,7 @@ var VignetteEffect = class extends ShaderEffect {
61448
62618
  * @param {number} [options.size=25.0] - vignette spread multiplier (higher = smaller dark area)
61449
62619
  */
61450
62620
  constructor(renderer, options = {}) {
61451
- super(renderer, { glsl: fragment, wgsl: wgslFragment14 });
62621
+ super(renderer, { glsl: fragment, wgsl: wgslFragment15 });
61452
62622
  this.strength = options.strength ?? 0.15;
61453
62623
  this.size = options.size ?? 25;
61454
62624
  this.setUniform("uStrength", this.strength);
@@ -61473,7 +62643,7 @@ var VignetteEffect = class extends ShaderEffect {
61473
62643
  };
61474
62644
 
61475
62645
  // src/video/effects/wave.js
61476
- var wgslFragment15 = `
62646
+ var wgslFragment16 = `
61477
62647
  struct WaveUniforms {
61478
62648
  uAmplitude : f32,
61479
62649
  uFrequency : f32,
@@ -61509,7 +62679,7 @@ var WaveEffect = class extends ShaderEffect {
61509
62679
  return texture2D(uSampler, distorted) * vColor;
61510
62680
  }
61511
62681
  `,
61512
- wgsl: wgslFragment15
62682
+ wgsl: wgslFragment16
61513
62683
  });
61514
62684
  this.setUniform("uAmplitude", options.amplitude ?? 0.01);
61515
62685
  this.setUniform("uFrequency", options.frequency ?? 10);