melonjs 19.0.0 → 19.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 (94) hide show
  1. package/README.md +7 -5
  2. package/build/application/application.d.ts +33 -0
  3. package/build/application/application.d.ts.map +1 -1
  4. package/build/application/defaultApplicationSettings.d.ts +1 -0
  5. package/build/application/defaultApplicationSettings.d.ts.map +1 -1
  6. package/build/application/settings.d.ts +47 -1
  7. package/build/application/settings.d.ts.map +1 -1
  8. package/build/camera/camera2d.d.ts +45 -22
  9. package/build/camera/camera2d.d.ts.map +1 -1
  10. package/build/camera/effects/camera_effect.d.ts +45 -0
  11. package/build/camera/effects/camera_effect.d.ts.map +1 -0
  12. package/build/camera/effects/fade_effect.d.ts +60 -0
  13. package/build/camera/effects/fade_effect.d.ts.map +1 -0
  14. package/build/camera/effects/mask_effect.d.ts +88 -0
  15. package/build/camera/effects/mask_effect.d.ts.map +1 -0
  16. package/build/camera/effects/shake_effect.d.ts +47 -0
  17. package/build/camera/effects/shake_effect.d.ts.map +1 -0
  18. package/build/index.d.ts +10 -2
  19. package/build/index.d.ts.map +1 -1
  20. package/build/index.js +2928 -935
  21. package/build/index.js.map +4 -4
  22. package/build/level/tiled/TMXObjectFactory.d.ts.map +1 -1
  23. package/build/math/color_matrix.d.ts +51 -0
  24. package/build/math/color_matrix.d.ts.map +1 -0
  25. package/build/math/matrix2d.d.ts +6 -6
  26. package/build/math/matrix3d.d.ts +17 -0
  27. package/build/math/matrix3d.d.ts.map +1 -1
  28. package/build/particles/emitter.d.ts +19 -5
  29. package/build/particles/emitter.d.ts.map +1 -1
  30. package/build/particles/particle.d.ts +4 -1
  31. package/build/particles/particle.d.ts.map +1 -1
  32. package/build/particles/settings.d.ts +200 -31
  33. package/build/particles/settings.d.ts.map +1 -1
  34. package/build/physics/quadtree.d.ts.map +1 -1
  35. package/build/renderable/imagelayer.d.ts +13 -2
  36. package/build/renderable/imagelayer.d.ts.map +1 -1
  37. package/build/renderable/renderable.d.ts +54 -35
  38. package/build/renderable/renderable.d.ts.map +1 -1
  39. package/build/renderable/text/bitmaptext.d.ts +33 -5
  40. package/build/renderable/text/bitmaptext.d.ts.map +1 -1
  41. package/build/renderable/text/text.d.ts +27 -1
  42. package/build/renderable/text/text.d.ts.map +1 -1
  43. package/build/renderable/trigger.d.ts +44 -19
  44. package/build/renderable/trigger.d.ts.map +1 -1
  45. package/build/state/state.d.ts +44 -2
  46. package/build/state/state.d.ts.map +1 -1
  47. package/build/tweens/tween.d.ts +9 -0
  48. package/build/tweens/tween.d.ts.map +1 -1
  49. package/build/video/buffer/index.d.ts +40 -0
  50. package/build/video/buffer/index.d.ts.map +1 -0
  51. package/build/video/{webgl/buffer → buffer}/vertex.d.ts +11 -4
  52. package/build/video/buffer/vertex.d.ts.map +1 -0
  53. package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
  54. package/build/video/renderer.d.ts +79 -0
  55. package/build/video/renderer.d.ts.map +1 -1
  56. package/build/video/renderstate.d.ts +7 -0
  57. package/build/video/renderstate.d.ts.map +1 -1
  58. package/build/video/rendertarget/canvasrendertarget.d.ts +26 -63
  59. package/build/video/rendertarget/canvasrendertarget.d.ts.map +1 -1
  60. package/build/video/rendertarget/render_target_pool.d.ts +73 -0
  61. package/build/video/rendertarget/render_target_pool.d.ts.map +1 -0
  62. package/build/video/rendertarget/rendertarget.d.ts +77 -0
  63. package/build/video/rendertarget/rendertarget.d.ts.map +1 -0
  64. package/build/video/rendertarget/webglrendertarget.d.ts +30 -0
  65. package/build/video/rendertarget/webglrendertarget.d.ts.map +1 -0
  66. package/build/video/texture/cache.d.ts +6 -0
  67. package/build/video/texture/cache.d.ts.map +1 -1
  68. package/build/video/webgl/batchers/batcher.d.ts +4 -4
  69. package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
  70. package/build/video/webgl/batchers/material_batcher.d.ts +3 -3
  71. package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
  72. package/build/video/webgl/batchers/quad_batcher.d.ts +34 -0
  73. package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
  74. package/build/video/webgl/buffer/index.d.ts +5 -34
  75. package/build/video/webgl/buffer/index.d.ts.map +1 -1
  76. package/build/video/webgl/effects/colorMatrix.d.ts +92 -0
  77. package/build/video/webgl/effects/colorMatrix.d.ts.map +1 -0
  78. package/build/video/webgl/effects/desaturate.d.ts +2 -4
  79. package/build/video/webgl/effects/desaturate.d.ts.map +1 -1
  80. package/build/video/webgl/effects/invert.d.ts +2 -2
  81. package/build/video/webgl/effects/invert.d.ts.map +1 -1
  82. package/build/video/webgl/effects/sepia.d.ts +2 -2
  83. package/build/video/webgl/effects/sepia.d.ts.map +1 -1
  84. package/build/video/webgl/effects/vignette.d.ts +42 -0
  85. package/build/video/webgl/effects/vignette.d.ts.map +1 -0
  86. package/build/video/webgl/shadereffect.d.ts.map +1 -1
  87. package/build/video/webgl/shaders/multitexture.d.ts +10 -0
  88. package/build/video/webgl/shaders/multitexture.d.ts.map +1 -0
  89. package/build/video/webgl/utils/precision.d.ts +4 -3
  90. package/build/video/webgl/utils/precision.d.ts.map +1 -1
  91. package/build/video/webgl/webgl_renderer.d.ts +15 -8
  92. package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
  93. package/package.json +1 -1
  94. package/build/video/webgl/buffer/vertex.d.ts.map +0 -1
package/build/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * melonJS Game Engine - 19.0.0
2
+ * melonJS Game Engine - 19.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
@@ -6228,6 +6228,7 @@ var defaultApplicationSettings = {
6228
6228
  blendMode: "normal",
6229
6229
  physic: "builtin",
6230
6230
  failIfMajorPerformanceCaveat: true,
6231
+ highPrecisionShader: true,
6231
6232
  subPixel: false,
6232
6233
  verbose: false,
6233
6234
  legacy: false,
@@ -7059,31 +7060,8 @@ function unbindKey(keyCode) {
7059
7060
  delete _preventDefaultForKeys[keyCode];
7060
7061
  }
7061
7062
 
7062
- // src/lang/console.js
7063
- function warning(deprecated, replacement, version2) {
7064
- const msg = "melonJS: %s is deprecated since version %s, please use %s";
7065
- const stack = new Error().stack;
7066
- if (console.groupCollapsed) {
7067
- console.groupCollapsed(
7068
- "%c" + msg,
7069
- "font-weight:normal;color:yellow;",
7070
- deprecated,
7071
- version2,
7072
- replacement
7073
- );
7074
- } else {
7075
- console.warn(msg, deprecated, version2, replacement);
7076
- }
7077
- if (typeof stack !== "undefined") {
7078
- console.warn(stack);
7079
- }
7080
- if (console.groupCollapsed) {
7081
- console.groupEnd();
7082
- }
7083
- }
7084
-
7085
7063
  // src/geometries/ellipse.ts
7086
- var Ellipse = class _Ellipse {
7064
+ var Ellipse = class {
7087
7065
  /**
7088
7066
  * the center coordinates of the ellipse
7089
7067
  */
@@ -7351,7 +7329,7 @@ var Ellipse = class _Ellipse {
7351
7329
  * @returns new Ellipse
7352
7330
  */
7353
7331
  clone() {
7354
- const clone = new _Ellipse(
7332
+ const clone = ellipsePool.get(
7355
7333
  this.pos.x,
7356
7334
  this.pos.y,
7357
7335
  this.radiusV.x * 2,
@@ -8985,6 +8963,73 @@ var Matrix3d = class {
8985
8963
  a[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
8986
8964
  return this;
8987
8965
  }
8966
+ transform(b00, b01, b02, b03, b10, b11, b12, b13, b20, b21, b22, b23, b30, b31, b32, b33) {
8967
+ const argc = arguments.length;
8968
+ if (argc !== 6 && argc !== 16) {
8969
+ throw new Error(`transform() requires 6 or 16 arguments, got ${argc}`);
8970
+ }
8971
+ let c0, c1, c2, c3;
8972
+ let c4, c5, c6, c7;
8973
+ let c8, c9, c10, c11;
8974
+ let c12, c13, c14, c15;
8975
+ if (argc === 6) {
8976
+ c0 = b00;
8977
+ c1 = b01;
8978
+ c2 = 0;
8979
+ c3 = 0;
8980
+ c4 = b02;
8981
+ c5 = b03;
8982
+ c6 = 0;
8983
+ c7 = 0;
8984
+ c8 = 0;
8985
+ c9 = 0;
8986
+ c10 = 1;
8987
+ c11 = 0;
8988
+ c12 = b10;
8989
+ c13 = b11;
8990
+ c14 = 0;
8991
+ c15 = 1;
8992
+ } else {
8993
+ c0 = b00;
8994
+ c1 = b01;
8995
+ c2 = b02;
8996
+ c3 = b03;
8997
+ c4 = b10;
8998
+ c5 = b11;
8999
+ c6 = b12;
9000
+ c7 = b13;
9001
+ c8 = b20;
9002
+ c9 = b21;
9003
+ c10 = b22;
9004
+ c11 = b23;
9005
+ c12 = b30;
9006
+ c13 = b31;
9007
+ c14 = b32;
9008
+ c15 = b33;
9009
+ }
9010
+ const a = this.val;
9011
+ const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
9012
+ const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
9013
+ const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
9014
+ const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];
9015
+ a[0] = c0 * a00 + c1 * a10 + c2 * a20 + c3 * a30;
9016
+ a[1] = c0 * a01 + c1 * a11 + c2 * a21 + c3 * a31;
9017
+ a[2] = c0 * a02 + c1 * a12 + c2 * a22 + c3 * a32;
9018
+ a[3] = c0 * a03 + c1 * a13 + c2 * a23 + c3 * a33;
9019
+ a[4] = c4 * a00 + c5 * a10 + c6 * a20 + c7 * a30;
9020
+ a[5] = c4 * a01 + c5 * a11 + c6 * a21 + c7 * a31;
9021
+ a[6] = c4 * a02 + c5 * a12 + c6 * a22 + c7 * a32;
9022
+ a[7] = c4 * a03 + c5 * a13 + c6 * a23 + c7 * a33;
9023
+ a[8] = c8 * a00 + c9 * a10 + c10 * a20 + c11 * a30;
9024
+ a[9] = c8 * a01 + c9 * a11 + c10 * a21 + c11 * a31;
9025
+ a[10] = c8 * a02 + c9 * a12 + c10 * a22 + c11 * a32;
9026
+ a[11] = c8 * a03 + c9 * a13 + c10 * a23 + c11 * a33;
9027
+ a[12] = c12 * a00 + c13 * a10 + c14 * a20 + c15 * a30;
9028
+ a[13] = c12 * a01 + c13 * a11 + c14 * a21 + c15 * a31;
9029
+ a[14] = c12 * a02 + c13 * a12 + c14 * a22 + c15 * a32;
9030
+ a[15] = c12 * a03 + c13 * a13 + c14 * a23 + c15 * a33;
9031
+ return this;
9032
+ }
8988
9033
  /**
8989
9034
  * Transpose the value of this matrix.
8990
9035
  * @returns Reference to this object for method chaining
@@ -9435,12 +9480,12 @@ var Matrix2d = class {
9435
9480
  }
9436
9481
  /**
9437
9482
  * Multiplies the current transformation with the matrix described by the arguments of this method.
9438
- * @param a a component
9439
- * @param b b component
9440
- * @param c c component
9441
- * @param d d component
9442
- * @param e e component
9443
- * @param f f component
9483
+ * @param a - a component (scale x / cos)
9484
+ * @param b - b component (skew y / sin)
9485
+ * @param c - c component (skew x / -sin)
9486
+ * @param d - d component (scale y / cos)
9487
+ * @param e - e component (translate x)
9488
+ * @param f - f component (translate y)
9444
9489
  * @returns Reference to this object for method chaining
9445
9490
  */
9446
9491
  transform(a, b, c, d, e, f) {
@@ -10243,6 +10288,238 @@ function unloadAll() {
10243
10288
  }
10244
10289
  }
10245
10290
 
10291
+ // src/camera/effects/camera_effect.ts
10292
+ var CameraEffect = class {
10293
+ /**
10294
+ * the camera this effect is attached to
10295
+ */
10296
+ camera;
10297
+ /**
10298
+ * whether this effect has finished and should be removed
10299
+ */
10300
+ isComplete;
10301
+ /**
10302
+ * whether this effect should persist across camera/game resets
10303
+ * (e.g. transition effects that span state changes)
10304
+ * @default false
10305
+ */
10306
+ isPersistent;
10307
+ /**
10308
+ * @param camera - the camera this effect is attached to
10309
+ */
10310
+ constructor(camera) {
10311
+ this.camera = camera;
10312
+ this.isComplete = false;
10313
+ this.isPersistent = false;
10314
+ }
10315
+ /**
10316
+ * Called each frame to update the effect state (e.g. modify camera offset, countdown duration).
10317
+ * @param _dt - time elapsed since last frame in milliseconds
10318
+ */
10319
+ update(_dt) {
10320
+ }
10321
+ // eslint-disable-line @typescript-eslint/no-unused-vars
10322
+ /**
10323
+ * Called after the scene renders to draw visual overlays (e.g. color fills for fading).
10324
+ * @param _renderer - the renderer to draw with
10325
+ * @param _width - the camera viewport width
10326
+ * @param _height - the camera viewport height
10327
+ */
10328
+ draw(_renderer2, _width, _height) {
10329
+ }
10330
+ // eslint-disable-line @typescript-eslint/no-unused-vars
10331
+ /**
10332
+ * Called when the effect is removed from the camera. Override to clean up resources.
10333
+ */
10334
+ destroy() {
10335
+ }
10336
+ };
10337
+
10338
+ // src/camera/effects/fade_effect.ts
10339
+ var FadeEffect = class extends CameraEffect {
10340
+ /**
10341
+ * the overlay color
10342
+ */
10343
+ color;
10344
+ /**
10345
+ * the tween controlling alpha transition
10346
+ */
10347
+ tween;
10348
+ /**
10349
+ * fade direction: "in" fades to the color, "out" fades from the color back to transparent
10350
+ */
10351
+ direction;
10352
+ /**
10353
+ * target alpha value for completion check
10354
+ * @ignore
10355
+ */
10356
+ _targetAlpha;
10357
+ /**
10358
+ * @param camera - the camera to apply the fade to
10359
+ * @param options - fade parameters
10360
+ * @param options.color - CSS color value or Color instance
10361
+ * @param [options.duration=1000] - fade duration in milliseconds
10362
+ * @param [options.direction="in"] - "in" fades to color, "out" fades from color to transparent
10363
+ * @param [options.onComplete] - callback when the fade finishes
10364
+ */
10365
+ constructor(camera, options) {
10366
+ super(camera);
10367
+ const duration = options.duration ?? 1e3;
10368
+ this.direction = options.direction ?? "in";
10369
+ this.color = colorPool.get(options.color);
10370
+ if (this.direction === "in") {
10371
+ this._targetAlpha = this.color.alpha;
10372
+ this.color.alpha = 0;
10373
+ this.tween = tweenPool.get(this.color).to({ alpha: this._targetAlpha }, { duration });
10374
+ } else {
10375
+ this._targetAlpha = 0;
10376
+ this.tween = tweenPool.get(this.color).to({ alpha: 0 }, { duration });
10377
+ }
10378
+ if (options.onComplete) {
10379
+ this.tween.onComplete(options.onComplete);
10380
+ }
10381
+ this.tween.isPersistent = true;
10382
+ this.tween.start();
10383
+ }
10384
+ draw(renderer2, width, height) {
10385
+ if (this.color.alpha === 0) {
10386
+ return;
10387
+ }
10388
+ const r = renderer2;
10389
+ r.save();
10390
+ r.resetTransform();
10391
+ r.setColor(this.color);
10392
+ r.fillRect(0, 0, width, height);
10393
+ r.restore();
10394
+ }
10395
+ update() {
10396
+ if (this.direction === "in" && this.color.alpha >= this._targetAlpha) {
10397
+ this.isComplete = true;
10398
+ } else if (this.direction === "out" && this.color.alpha <= 0) {
10399
+ this.isComplete = true;
10400
+ }
10401
+ }
10402
+ destroy() {
10403
+ this.tween.stop();
10404
+ tweenPool.release(this.tween);
10405
+ colorPool.release(this.color);
10406
+ }
10407
+ };
10408
+
10409
+ // src/camera/effects/mask_effect.ts
10410
+ var MaskEffect = class extends CameraEffect {
10411
+ /**
10412
+ * the transition fill color
10413
+ */
10414
+ color;
10415
+ /**
10416
+ * the mask shape template (unit-sized, centered at origin)
10417
+ */
10418
+ shape;
10419
+ /**
10420
+ * current progress value (0 = fully covered, 1 = fully visible)
10421
+ */
10422
+ progress;
10423
+ /**
10424
+ * the tween controlling progress
10425
+ */
10426
+ tween;
10427
+ /**
10428
+ * transition direction
10429
+ */
10430
+ direction;
10431
+ /**
10432
+ * optional callback when transition completes
10433
+ */
10434
+ onComplete;
10435
+ /**
10436
+ * pooled shape used for rendering (avoids per-frame allocation)
10437
+ * @ignore
10438
+ */
10439
+ _maskShape;
10440
+ /**
10441
+ * @param camera - the camera to apply the transition to
10442
+ * @param options - transition parameters
10443
+ * @param options.shape - an Ellipse or Polygon (unit-sized, centered at origin) defining the mask shape
10444
+ * @param options.color - CSS color value or Color instance for the transition fill
10445
+ * @param [options.duration=500] - transition duration in milliseconds
10446
+ * @param [options.direction="hide"] - "hide" shrinks the visible area, "reveal" grows it
10447
+ * @param [options.onComplete] - callback when the transition finishes
10448
+ */
10449
+ constructor(camera, options) {
10450
+ super(camera);
10451
+ this.color = colorPool.get(options.color);
10452
+ this.direction = options.direction ?? "hide";
10453
+ this.onComplete = options.onComplete;
10454
+ this.shape = options.shape;
10455
+ this._maskShape = this.shape.clone();
10456
+ const duration = options.duration ?? 500;
10457
+ if (this.direction === "hide") {
10458
+ this.progress = { value: 1 };
10459
+ this.tween = tweenPool.get(this.progress).to({ value: 0 }, { duration });
10460
+ } else {
10461
+ this.progress = { value: 0 };
10462
+ this.tween = tweenPool.get(this.progress).to({ value: 1 }, { duration });
10463
+ }
10464
+ this.tween.isPersistent = true;
10465
+ this.tween.start();
10466
+ }
10467
+ update() {
10468
+ if (this.direction === "hide" && this.progress.value <= 0 || this.direction === "reveal" && this.progress.value >= 1) {
10469
+ this.progress.value = this.direction === "hide" ? 0 : 1;
10470
+ this.isComplete = true;
10471
+ if (typeof this.onComplete === "function") {
10472
+ this.onComplete();
10473
+ }
10474
+ return;
10475
+ }
10476
+ const width = this.camera.width;
10477
+ const height = this.camera.height;
10478
+ const maxRadius = Math.sqrt(width * width + height * height) / 2;
10479
+ const scale2 = this.progress.value * maxRadius;
10480
+ const cx = width / 2;
10481
+ const cy = height / 2;
10482
+ if (this._maskShape.type === "Ellipse") {
10483
+ this._maskShape.setShape(cx, cy, scale2 * 2, scale2 * 2);
10484
+ } else {
10485
+ const mask = this._maskShape;
10486
+ const srcPoints = this.shape.points;
10487
+ for (let i = 0; i < srcPoints.length; i++) {
10488
+ mask.points[i].set(srcPoints[i].x * scale2, srcPoints[i].y * scale2);
10489
+ }
10490
+ mask.pos.set(cx, cy);
10491
+ }
10492
+ }
10493
+ draw(renderer2, width, height) {
10494
+ if (this.isComplete && this.direction === "reveal") {
10495
+ return;
10496
+ }
10497
+ const r = renderer2;
10498
+ r.save();
10499
+ r.resetTransform();
10500
+ if (this.progress.value <= 0) {
10501
+ r.setColor(this.color);
10502
+ r.fillRect(0, 0, width, height);
10503
+ } else {
10504
+ r.setMask(this._maskShape, true);
10505
+ r.setColor(this.color);
10506
+ r.fillRect(0, 0, width, height);
10507
+ r.clearMask();
10508
+ }
10509
+ r.restore();
10510
+ }
10511
+ destroy() {
10512
+ this.tween.stop();
10513
+ tweenPool.release(this.tween);
10514
+ colorPool.release(this.color);
10515
+ if (this._maskShape.type === "Ellipse") {
10516
+ ellipsePool.release(this._maskShape);
10517
+ } else {
10518
+ polygonPool.release(this._maskShape);
10519
+ }
10520
+ }
10521
+ };
10522
+
10246
10523
  // src/geometries/observablePoint.ts
10247
10524
  var ObservablePoint = class _ObservablePoint {
10248
10525
  _callback;
@@ -11460,7 +11737,8 @@ var Renderable = class _Renderable extends Rect {
11460
11737
  this.alpha = 1;
11461
11738
  this.ancestor = void 0;
11462
11739
  this.mask = void 0;
11463
- this.shader = void 0;
11740
+ this.postEffects = [];
11741
+ this._postEffectManaged = false;
11464
11742
  this.blendMode = "normal";
11465
11743
  this.name = "";
11466
11744
  this.isRenderable = true;
@@ -11538,6 +11816,82 @@ var Renderable = class _Renderable extends Rect {
11538
11816
  }
11539
11817
  }
11540
11818
  }
11819
+ /**
11820
+ * @deprecated since 19.2.0 — use {@link addPostEffect} / {@link getPostEffect} / {@link removePostEffect} instead
11821
+ * @type {GLShader|ShaderEffect|undefined}
11822
+ */
11823
+ get shader() {
11824
+ return this.postEffects[0];
11825
+ }
11826
+ set shader(value) {
11827
+ for (const effect of this.postEffects) {
11828
+ if (typeof effect.destroy === "function") {
11829
+ effect.destroy();
11830
+ }
11831
+ }
11832
+ if (typeof value === "undefined") {
11833
+ this.postEffects.length = 0;
11834
+ } else {
11835
+ this.postEffects = [value];
11836
+ }
11837
+ }
11838
+ /**
11839
+ * Add a post-processing shader effect to this renderable.
11840
+ * @param {GLShader|ShaderEffect} effect - the effect to add
11841
+ * @returns {GLShader|ShaderEffect} the added effect
11842
+ * @example
11843
+ * mySprite.addPostEffect(new DesaturateEffect(renderer));
11844
+ */
11845
+ addPostEffect(effect) {
11846
+ this.postEffects.push(effect);
11847
+ return effect;
11848
+ }
11849
+ /**
11850
+ * Get post-processing shader effects.
11851
+ * When called with a class, returns the first effect matching the given class.
11852
+ * When called without arguments, returns the full effects array.
11853
+ * @param {Function} [effectClass] - the effect class to search for
11854
+ * @returns {GLShader|ShaderEffect|Array|undefined} the matching effect, the effects array, or undefined
11855
+ * @example
11856
+ * const desat = sprite.getPostEffect(DesaturateEffect);
11857
+ * const allEffects = sprite.getPostEffect();
11858
+ */
11859
+ getPostEffect(effectClass) {
11860
+ if (typeof effectClass === "undefined") {
11861
+ return this.postEffects;
11862
+ }
11863
+ return this.postEffects.find((fx) => {
11864
+ return fx instanceof effectClass;
11865
+ });
11866
+ }
11867
+ /**
11868
+ * Remove all post-processing shader effects.
11869
+ * @example
11870
+ * sprite.clearPostEffects();
11871
+ */
11872
+ clearPostEffects() {
11873
+ for (const effect of this.postEffects) {
11874
+ if (typeof effect.destroy === "function") {
11875
+ effect.destroy();
11876
+ }
11877
+ }
11878
+ this.postEffects.length = 0;
11879
+ }
11880
+ /**
11881
+ * Remove a specific post-processing shader effect.
11882
+ * @param {GLShader|ShaderEffect} effect - the effect to remove
11883
+ * @example
11884
+ * sprite.removePostEffect(effect);
11885
+ */
11886
+ removePostEffect(effect) {
11887
+ const idx = this.postEffects.indexOf(effect);
11888
+ if (idx !== -1) {
11889
+ this.postEffects.splice(idx, 1);
11890
+ if (typeof effect.destroy === "function") {
11891
+ effect.destroy();
11892
+ }
11893
+ }
11894
+ }
11541
11895
  /**
11542
11896
  * returns true if this renderable is flipped on the horizontal axis
11543
11897
  * @public
@@ -11819,8 +12173,8 @@ var Renderable = class _Renderable extends Rect {
11819
12173
  renderer2.setMask(this.mask);
11820
12174
  renderer2.translate(-this.pos.x, -this.pos.y);
11821
12175
  }
11822
- if (this.shader) {
11823
- renderer2.customShader = this.shader;
12176
+ if (!this._postEffectManaged) {
12177
+ renderer2.beginPostEffect(this);
11824
12178
  }
11825
12179
  if (this.autoTransform === true && !this.currentTransform.isIdentity()) {
11826
12180
  renderer2.translate(this.pos.x, this.pos.y);
@@ -11860,8 +12214,8 @@ var Renderable = class _Renderable extends Rect {
11860
12214
  if (this.mask) {
11861
12215
  renderer2.clearMask();
11862
12216
  }
11863
- if (this.shader) {
11864
- renderer2.customShader = void 0;
12217
+ if (!this._postEffectManaged) {
12218
+ renderer2.endPostEffect(this);
11865
12219
  }
11866
12220
  renderer2.restore();
11867
12221
  this.isDirty = false;
@@ -11923,10 +12277,12 @@ var Renderable = class _Renderable extends Rect {
11923
12277
  }
11924
12278
  releaseAllPointerEvents(this);
11925
12279
  this.onDestroyEvent.apply(this, arguments);
11926
- if (this.shader && typeof this.shader.destroy === "function") {
11927
- this.shader.destroy();
11928
- this.shader = void 0;
12280
+ for (const effect of this.postEffects) {
12281
+ if (typeof effect.destroy === "function") {
12282
+ effect.destroy();
12283
+ }
11929
12284
  }
12285
+ this.postEffects.length = 0;
11930
12286
  }
11931
12287
  /**
11932
12288
  * OnDestroy Notification function<br>
@@ -11965,6 +12321,29 @@ __export(loader_exports, {
11965
12321
  withCredentials: () => withCredentials
11966
12322
  });
11967
12323
 
12324
+ // src/lang/console.js
12325
+ function warning(deprecated, replacement, version2) {
12326
+ const msg = "melonJS: %s is deprecated since version %s, please use %s";
12327
+ const stack = new Error().stack;
12328
+ if (console.groupCollapsed) {
12329
+ console.groupCollapsed(
12330
+ "%c" + msg,
12331
+ "font-weight:normal;color:yellow;",
12332
+ deprecated,
12333
+ version2,
12334
+ replacement
12335
+ );
12336
+ } else {
12337
+ console.warn(msg, deprecated, version2, replacement);
12338
+ }
12339
+ if (typeof stack !== "undefined") {
12340
+ console.warn(stack);
12341
+ }
12342
+ if (console.groupCollapsed) {
12343
+ console.groupEnd();
12344
+ }
12345
+ }
12346
+
11968
12347
  // src/utils/file.ts
11969
12348
  var file_exports = {};
11970
12349
  __export(file_exports, {
@@ -13804,6 +14183,97 @@ var TMX_FLIP_AD = 536870912;
13804
14183
  var TMX_CLEAR_BIT_MASK = ~(TMX_FLIP_H | TMX_FLIP_V | TMX_FLIP_AD);
13805
14184
  var COLLISION_GROUP = "collision";
13806
14185
 
14186
+ // src/video/rendertarget/rendertarget.ts
14187
+ var RenderTarget = class {
14188
+ /**
14189
+ * Bind this render target as the active draw destination.
14190
+ * All subsequent draw calls will render into this target until {@link unbind} is called.
14191
+ * No-op by default — subclasses override for GPU render targets (e.g. WebGL FBOs).
14192
+ */
14193
+ bind() {
14194
+ }
14195
+ /**
14196
+ * Unbind this render target, restoring the default (screen) output.
14197
+ * No-op by default — subclasses override for GPU render targets.
14198
+ */
14199
+ unbind() {
14200
+ }
14201
+ /**
14202
+ * Creates a Blob object representing the image contained in this render target.
14203
+ * @param type - a string indicating the image format (default "image/png")
14204
+ * @param quality - a number between 0 and 1 for lossy formats (e.g. image/jpeg)
14205
+ * @returns a Promise resolving to a Blob
14206
+ */
14207
+ toBlob(type = "image/png", quality) {
14208
+ const imageData = this.getImageData();
14209
+ if (typeof OffscreenCanvas !== "undefined") {
14210
+ const canvas2 = new OffscreenCanvas(this.width, this.height);
14211
+ const ctx2 = canvas2.getContext("2d");
14212
+ if (!ctx2) {
14213
+ return Promise.reject(new Error("Failed to get 2d context"));
14214
+ }
14215
+ ctx2.putImageData(imageData, 0, 0);
14216
+ const options = { type };
14217
+ if (typeof quality !== "undefined") {
14218
+ options.quality = quality;
14219
+ }
14220
+ return canvas2.convertToBlob(options);
14221
+ }
14222
+ const canvas = document.createElement("canvas");
14223
+ canvas.width = this.width;
14224
+ canvas.height = this.height;
14225
+ const ctx = canvas.getContext("2d");
14226
+ if (!ctx) {
14227
+ return Promise.reject(new Error("Failed to get 2d context"));
14228
+ }
14229
+ ctx.putImageData(imageData, 0, 0);
14230
+ return new Promise((resolve, reject) => {
14231
+ canvas.toBlob(
14232
+ (blob) => {
14233
+ if (blob) {
14234
+ resolve(blob);
14235
+ } else {
14236
+ reject(new Error(`toBlob failed for type "${type}"`));
14237
+ }
14238
+ },
14239
+ type,
14240
+ quality
14241
+ );
14242
+ });
14243
+ }
14244
+ /**
14245
+ * Creates an ImageBitmap object from the current contents of this render target.
14246
+ * @returns a Promise resolving to an ImageBitmap
14247
+ */
14248
+ toImageBitmap() {
14249
+ const imageData = this.getImageData();
14250
+ return globalThis.createImageBitmap(imageData);
14251
+ }
14252
+ /**
14253
+ * Returns a data URL containing a representation of the current contents.
14254
+ * @param type - a string indicating the image format (default "image/png")
14255
+ * @param quality - a number between 0 and 1 for lossy formats (e.g. image/jpeg)
14256
+ * @returns a Promise resolving to a data URL string
14257
+ */
14258
+ toDataURL(type = "image/png", quality) {
14259
+ return this.toBlob(type, quality).then((blob) => {
14260
+ const reader = new FileReader();
14261
+ return new Promise((resolve, reject) => {
14262
+ reader.onload = () => {
14263
+ resolve(reader.result);
14264
+ };
14265
+ reader.onerror = () => {
14266
+ reject(new Error(reader.error?.message ?? "FileReader failed"));
14267
+ };
14268
+ reader.onabort = () => {
14269
+ reject(new Error("FileReader aborted"));
14270
+ };
14271
+ reader.readAsDataURL(blob);
14272
+ });
14273
+ });
14274
+ }
14275
+ };
14276
+
13807
14277
  // src/video/rendertarget/canvasrendertarget.js
13808
14278
  var defaultAttributes = {
13809
14279
  offscreenCanvas: false,
@@ -13854,7 +14324,7 @@ function createContext(canvas, attributes) {
13854
14324
  }
13855
14325
  return context;
13856
14326
  }
13857
- var CanvasRenderTarget = class {
14327
+ var CanvasRenderTarget = class extends RenderTarget {
13858
14328
  /**
13859
14329
  * @param {number} width - the desired width of the canvas
13860
14330
  * @param {number} height - the desired height of the canvas
@@ -13867,6 +14337,7 @@ var CanvasRenderTarget = class {
13867
14337
  * @param {boolean} [attributes.antiAlias=false] - Whether to enable anti-aliasing, use false (default) for a pixelated effect.
13868
14338
  */
13869
14339
  constructor(width, height, attributes = defaultAttributes) {
14340
+ super();
13870
14341
  this.attributes = Object.assign({}, defaultAttributes, attributes);
13871
14342
  if (typeof attributes.context === "undefined") {
13872
14343
  attributes.context = "2d";
@@ -13946,64 +14417,48 @@ var CanvasRenderTarget = class {
13946
14417
  return this.context.getImageData(x, y, width, height);
13947
14418
  }
13948
14419
  /**
13949
- * creates a Blob object representing the image contained in this canvas texture
14420
+ * Returns a data URL directly from the canvas (avoids the getImageData/Blob round-trip).
14421
+ * Note: not supported by OffscreenCanvas — falls back to the base implementation.
13950
14422
  * @param {string} [type="image/png"] - A string indicating the image format
13951
- * @param {number} [quality] - A Number between 0 and 1 indicating the image quality to be used when creating images using file formats that support lossy compression (such as image/jpeg or image/webp). A user agent will use its default quality value if this option is not specified, or if the number is outside the allowed range.
13952
- * @returns {Promise} A Promise returning a Blob object representing the image contained in this canvas texture
13953
- * @example
13954
- * renderTarget.convertToBlob().then((blob) => console.log(blob));
14423
+ * @param {number} [quality] - A number between 0 and 1 for lossy formats (image/jpeg, image/webp)
14424
+ * @returns {Promise<string>} A Promise resolving to a data URL string
13955
14425
  */
13956
- toBlob(type = "image/png", quality) {
13957
- if (typeof this.canvas.convertToBlob === "function") {
13958
- return this.canvas.convertToBlob({ type, quality });
13959
- } else {
13960
- return new Promise((resolve) => {
13961
- this.canvas.toBlob(
13962
- (blob) => {
13963
- resolve(blob);
13964
- },
13965
- type,
13966
- quality
13967
- );
13968
- });
14426
+ toDataURL(type = "image/png", quality) {
14427
+ if (typeof this.canvas.toDataURL === "function") {
14428
+ return Promise.resolve(this.canvas.toDataURL(type, quality));
13969
14429
  }
14430
+ return super.toDataURL(type, quality);
13970
14431
  }
13971
14432
  /**
13972
- * creates an ImageBitmap object from the most recently rendered image of this canvas texture
14433
+ * Creates a Blob directly from the canvas (avoids the getImageData round-trip).
13973
14434
  * @param {string} [type="image/png"] - A string indicating the image format
13974
- * @param {number} [quality] - A Number between 0 and 1 indicating the image quality to be used when creating images using file formats that support lossy compression (such as image/jpeg or image/webp). A user agent will use its default quality value if this option is not specified, or if the number is outside the allowed range.
13975
- * @returns {Promise} A Promise returning an ImageBitmap.
13976
- * @example
13977
- * renderTarget.transferToImageBitmap().then((bitmap) => console.log(bitmap));
14435
+ * @param {number} [quality] - A number between 0 and 1 for lossy formats (image/jpeg, image/webp)
14436
+ * @returns {Promise<Blob>} A Promise resolving to a Blob
13978
14437
  */
13979
- toImageBitmap(type = "image/png", quality) {
13980
- return new Promise((resolve) => {
13981
- if (typeof this.canvas.transferToImageBitmap === "function") {
13982
- resolve(this.canvas.transferToImageBitmap());
13983
- } else {
13984
- const image = new Image();
13985
- image.src = this.canvas.toDataURL(type, quality);
13986
- image.onload = () => {
13987
- globalThis.createImageBitmap(image).then((bitmap) => {
13988
- return resolve(bitmap);
13989
- });
13990
- };
13991
- }
14438
+ toBlob(type = "image/png", quality) {
14439
+ if (typeof this.canvas.convertToBlob === "function") {
14440
+ return this.canvas.convertToBlob({ type, quality });
14441
+ }
14442
+ return new Promise((resolve, reject) => {
14443
+ this.canvas.toBlob(
14444
+ (blob) => {
14445
+ if (blob) {
14446
+ resolve(blob);
14447
+ } else {
14448
+ reject(new Error(`toBlob failed for type "${type}"`));
14449
+ }
14450
+ },
14451
+ type,
14452
+ quality
14453
+ );
13992
14454
  });
13993
14455
  }
13994
14456
  /**
13995
- * returns a data URL containing a representation of the most recently rendered image of this canvas texture
13996
- * (not supported by OffscreenCanvas)
13997
- * @param {string} [type="image/png"] - A string indicating the image format
13998
- * @param {number} [quality] - A Number between 0 and 1 indicating the image quality to be used when creating images using file formats that support lossy compression (such as image/jpeg or image/webp). A user agent will use its default quality value if this option is not specified, or if the number is outside the allowed range.
13999
- * @returns {Promise} A Promise returning a string containing the requested data URL.
14000
- * @example
14001
- * renderer.toDataURL().then((dataURL) => console.log(dataURL));
14457
+ * Creates an ImageBitmap directly from the canvas (avoids the getImageData round-trip).
14458
+ * @returns {Promise<ImageBitmap>} A Promise resolving to an ImageBitmap
14002
14459
  */
14003
- toDataURL(type = "image/png", quality) {
14004
- return new Promise((resolve) => {
14005
- resolve(this.canvas.toDataURL(type, quality));
14006
- });
14460
+ toImageBitmap() {
14461
+ return globalThis.createImageBitmap(this.canvas);
14007
14462
  }
14008
14463
  /**
14009
14464
  * invalidate the current CanvasRenderTarget, and force a reupload of the corresponding texture
@@ -14012,6 +14467,7 @@ var CanvasRenderTarget = class {
14012
14467
  */
14013
14468
  invalidate(renderer2) {
14014
14469
  if (typeof renderer2.gl !== "undefined") {
14470
+ renderer2.flush();
14015
14471
  renderer2.setBatcher("quad");
14016
14472
  this.glTextureUnit = renderer2.cache.getUnit(
14017
14473
  renderer2.cache.get(this.canvas)
@@ -14865,6 +15321,7 @@ var RenderState = class {
14865
15321
  this.currentGradient = null;
14866
15322
  this.lineDash = [];
14867
15323
  this.currentBlendMode = "none";
15324
+ this.currentShader = void 0;
14868
15325
  this._stackCapacity = 32;
14869
15326
  this._stackDepth = 0;
14870
15327
  this._colorStack = Array.from({ length: this._stackCapacity }, () => {
@@ -14883,6 +15340,7 @@ var RenderState = class {
14883
15340
  this._scissorActive = new Uint8Array(this._stackCapacity);
14884
15341
  this._gradientStack = new Array(this._stackCapacity);
14885
15342
  this._blendStack = new Array(this._stackCapacity);
15343
+ this._shaderStack = new Array(this._stackCapacity);
14886
15344
  }
14887
15345
  /**
14888
15346
  * Save the current state onto the stack (zero allocations).
@@ -14899,6 +15357,7 @@ var RenderState = class {
14899
15357
  this._gradientStack[depth] = this.currentGradient;
14900
15358
  this._lineDashStack[depth] = this.lineDash;
14901
15359
  this._blendStack[depth] = this.currentBlendMode;
15360
+ this._shaderStack[depth] = this.currentShader;
14902
15361
  if (scissorTestActive) {
14903
15362
  this._scissorStack[depth].set(this.currentScissor);
14904
15363
  this._scissorActive[depth] = 1;
@@ -14924,6 +15383,7 @@ var RenderState = class {
14924
15383
  this.currentTransform.copy(this._matrixStack[depth]);
14925
15384
  this.currentGradient = this._gradientStack[depth];
14926
15385
  this.lineDash = this._lineDashStack[depth];
15386
+ this.currentShader = this._shaderStack[depth];
14927
15387
  const scissorActive = !!this._scissorActive[depth];
14928
15388
  if (scissorActive) {
14929
15389
  this.currentScissor.set(this._scissorStack[depth]);
@@ -14949,6 +15409,7 @@ var RenderState = class {
14949
15409
  this.currentScissor[1] = 0;
14950
15410
  this.currentScissor[2] = width;
14951
15411
  this.currentScissor[3] = height;
15412
+ this.currentShader = void 0;
14952
15413
  }
14953
15414
  /** @private — doubles stack capacity when exceeded */
14954
15415
  _growStacks() {
@@ -14962,6 +15423,7 @@ var RenderState = class {
14962
15423
  this._gradientStack.push(null);
14963
15424
  this._lineDashStack.push([]);
14964
15425
  this._blendStack.push(void 0);
15426
+ this._shaderStack.push(void 0);
14965
15427
  }
14966
15428
  const newScissorActive = new Uint8Array(newCap);
14967
15429
  newScissorActive.set(this._scissorActive);
@@ -14988,6 +15450,7 @@ var Renderer = class {
14988
15450
  this.isContextValid = true;
14989
15451
  this.GPURenderer = void 0;
14990
15452
  this.customShader = void 0;
15453
+ this._renderTargetPool = null;
14991
15454
  this.path2D = new path2d_default();
14992
15455
  this.type = "Generic";
14993
15456
  this.backgroundColor = new Color(
@@ -15106,14 +15569,107 @@ var Renderer = class {
15106
15569
  hasSupportedCompressedFormats(format) {
15107
15570
  const supportedFormats = this.getSupportedCompressedTextureFormats();
15108
15571
  for (const supportedFormat in supportedFormats) {
15109
- for (const extension in supportedFormats[supportedFormat]) {
15110
- if (format === supportedFormats[supportedFormat][extension]) {
15572
+ const entry = supportedFormats[supportedFormat];
15573
+ if (entry === null || typeof entry === "undefined") {
15574
+ continue;
15575
+ }
15576
+ for (const extension in entry) {
15577
+ if (format === entry[extension]) {
15111
15578
  return true;
15112
15579
  }
15113
15580
  }
15114
15581
  }
15115
15582
  return false;
15116
15583
  }
15584
+ /**
15585
+ * Begin capturing rendering to an offscreen buffer for post-effect processing.
15586
+ * Call endPostEffect() after rendering to blit the result to the screen.
15587
+ * No-op on Canvas renderer.
15588
+ * @param {Renderable} renderable - the renderable with postEffects to apply
15589
+ * @returns {boolean} false (Canvas renderer does not support post-effect processing)
15590
+ * @ignore
15591
+ */
15592
+ beginPostEffect(renderable) {
15593
+ const effects = renderable.postEffects.filter((fx) => {
15594
+ return fx.enabled !== false;
15595
+ });
15596
+ if (effects.length === 1) {
15597
+ this.customShader = effects[0];
15598
+ } else {
15599
+ this.customShader = void 0;
15600
+ }
15601
+ return false;
15602
+ }
15603
+ /**
15604
+ * End post-effect capture and blit the offscreen buffer to the screen
15605
+ * through the renderable's post-effects.
15606
+ * No-op on Canvas renderer.
15607
+ * @param {Renderable} renderable - the renderable with postEffects to apply
15608
+ * @ignore
15609
+ */
15610
+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
15611
+ endPostEffect(renderable) {
15612
+ }
15613
+ /**
15614
+ * Blit a texture to the screen through a shader effect.
15615
+ * Draws a screen-aligned quad using the given texture as source
15616
+ * and the given shader for post-processing (e.g. scanlines, desaturation).
15617
+ * No-op on Canvas renderer.
15618
+ * @param {WebGLTexture} source - the source texture to blit
15619
+ * @param {number} x - destination x position
15620
+ * @param {number} y - destination y position
15621
+ * @param {number} width - destination width
15622
+ * @param {number} height - destination height
15623
+ * @param {ShaderEffect} shader - the shader effect to apply
15624
+ * @param {boolean} [keepBlend=false] - if true, keep current blend mode (for sprite compositing)
15625
+ */
15626
+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
15627
+ blitEffect(source, x, y, width, height, shader, keepBlend) {
15628
+ }
15629
+ /**
15630
+ * Sets the viewport for the renderer.
15631
+ * Defines the affine transformation from normalized device coordinates to window coordinates.
15632
+ * No-op on Canvas renderer.
15633
+ * @param {number} [x=0] - x coordinate of the viewport origin
15634
+ * @param {number} [y=0] - y coordinate of the viewport origin
15635
+ * @param {number} [w] - width of the viewport (defaults to canvas width)
15636
+ * @param {number} [h] - height of the viewport (defaults to canvas height)
15637
+ */
15638
+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
15639
+ setViewport(x = 0, y = 0, w, h) {
15640
+ }
15641
+ /**
15642
+ * Clear the current render target to transparent black (color + stencil).
15643
+ * Used to prepare FBOs for post-effect capture.
15644
+ * No-op on Canvas renderer.
15645
+ */
15646
+ clearRenderTarget() {
15647
+ }
15648
+ /**
15649
+ * Enable the scissor test with the given rectangle.
15650
+ * No-op on Canvas renderer.
15651
+ * @param {number} x - x coordinate of the scissor rectangle
15652
+ * @param {number} y - y coordinate of the scissor rectangle
15653
+ * @param {number} width - width of the scissor rectangle
15654
+ * @param {number} height - height of the scissor rectangle
15655
+ */
15656
+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
15657
+ enableScissor(x, y, width, height) {
15658
+ }
15659
+ /**
15660
+ * Disable the scissor test, allowing rendering to the full viewport.
15661
+ * No-op on Canvas renderer.
15662
+ */
15663
+ disableScissor() {
15664
+ }
15665
+ /**
15666
+ * Enable or disable alpha blending.
15667
+ * No-op on Canvas renderer (Canvas always blends).
15668
+ * @param {boolean} enable - true to enable blending, false to disable
15669
+ */
15670
+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
15671
+ setBlendEnabled(enable2) {
15672
+ }
15117
15673
  /**
15118
15674
  * returns the current blend mode for this renderer
15119
15675
  * @returns {string} blend mode
@@ -16128,6 +16684,19 @@ var TextureCache = class {
16128
16684
  this.usedUnits.add(0);
16129
16685
  return 0;
16130
16686
  }
16687
+ /**
16688
+ * Reset all texture unit assignments without clearing the texture cache.
16689
+ * Used by multi-texture batching when the shader's sampler range is exceeded.
16690
+ * @ignore
16691
+ */
16692
+ resetUnitAssignments() {
16693
+ if (this.renderer.currentBatcher) {
16694
+ this.renderer.currentBatcher.boundTextures.length = 0;
16695
+ this.renderer.currentBatcher.currentTextureUnit = -1;
16696
+ }
16697
+ this.units.clear();
16698
+ this.usedUnits.clear();
16699
+ }
16131
16700
  /**
16132
16701
  * @ignore
16133
16702
  */
@@ -16995,6 +17564,7 @@ var CanvasRenderer = class extends Renderer {
16995
17564
  if (result !== null) {
16996
17565
  this.setBlendMode(result.blendMode);
16997
17566
  }
17567
+ this.customShader = this.renderState.currentShader;
16998
17568
  if (typeof context.fillStyle === "string") {
16999
17569
  this.currentColor.copy(context.fillStyle);
17000
17570
  }
@@ -17019,6 +17589,7 @@ var CanvasRenderer = class extends Renderer {
17019
17589
  */
17020
17590
  save() {
17021
17591
  this.getContext().save();
17592
+ this.renderState.currentShader = this.customShader;
17022
17593
  this.renderState.save();
17023
17594
  }
17024
17595
  /**
@@ -17266,9 +17837,8 @@ var CanvasRenderer = class extends Renderer {
17266
17837
  }
17267
17838
  this.maskLevel++;
17268
17839
  if (invert === true) {
17269
- context.closePath();
17270
- context.globalCompositeOperation = "destination-atop";
17271
- context.fill();
17840
+ context.rect(0, 0, this.getCanvas().width, this.getCanvas().height);
17841
+ context.clip("evenodd");
17272
17842
  } else {
17273
17843
  context.clip();
17274
17844
  }
@@ -21349,6 +21919,723 @@ var Sprite = class extends Renderable {
21349
21919
  }
21350
21920
  };
21351
21921
 
21922
+ // src/math/color_matrix.ts
21923
+ var ColorMatrix = class extends Matrix3d {
21924
+ /**
21925
+ * Apply a brightness adjustment.
21926
+ * @param amount - brightness multiplier (1.0 = normal, >1 brighter, <1 darker)
21927
+ * @returns this instance for chaining
21928
+ */
21929
+ brightness(amount) {
21930
+ return this.transform(
21931
+ amount,
21932
+ 0,
21933
+ 0,
21934
+ 0,
21935
+ 0,
21936
+ amount,
21937
+ 0,
21938
+ 0,
21939
+ 0,
21940
+ 0,
21941
+ amount,
21942
+ 0,
21943
+ 0,
21944
+ 0,
21945
+ 0,
21946
+ 1
21947
+ );
21948
+ }
21949
+ /**
21950
+ * Apply a contrast adjustment.
21951
+ * @param amount - contrast multiplier (1.0 = normal, >1 more contrast, <1 less)
21952
+ * @returns this instance for chaining
21953
+ */
21954
+ contrast(amount) {
21955
+ const o = (1 - amount) / 2;
21956
+ return this.transform(
21957
+ amount,
21958
+ 0,
21959
+ 0,
21960
+ 0,
21961
+ 0,
21962
+ amount,
21963
+ 0,
21964
+ 0,
21965
+ 0,
21966
+ 0,
21967
+ amount,
21968
+ 0,
21969
+ o,
21970
+ o,
21971
+ o,
21972
+ 1
21973
+ );
21974
+ }
21975
+ /**
21976
+ * Apply a saturation adjustment.
21977
+ * @param amount - saturation level (0.0 = grayscale, 1.0 = normal, >1 over-saturated)
21978
+ * @returns this instance for chaining
21979
+ */
21980
+ saturate(amount) {
21981
+ const ir = 0.299 * (1 - amount);
21982
+ const ig = 0.587 * (1 - amount);
21983
+ const ib = 0.114 * (1 - amount);
21984
+ return this.transform(
21985
+ ir + amount,
21986
+ ir,
21987
+ ir,
21988
+ 0,
21989
+ ig,
21990
+ ig + amount,
21991
+ ig,
21992
+ 0,
21993
+ ib,
21994
+ ib,
21995
+ ib + amount,
21996
+ 0,
21997
+ 0,
21998
+ 0,
21999
+ 0,
22000
+ 1
22001
+ );
22002
+ }
22003
+ /**
22004
+ * Apply a hue rotation.
22005
+ * @param angle - rotation angle in radians
22006
+ * @returns this instance for chaining
22007
+ */
22008
+ hueRotate(angle) {
22009
+ const cos = Math.cos(angle);
22010
+ const sin = Math.sin(angle);
22011
+ const lumR = 0.299;
22012
+ const lumG = 0.587;
22013
+ const lumB = 0.114;
22014
+ return this.transform(
22015
+ lumR + cos * (1 - lumR) + sin * -lumR,
22016
+ lumR + cos * -lumR + sin * 0.143,
22017
+ lumR + cos * -lumR + sin * -(1 - lumR),
22018
+ 0,
22019
+ lumG + cos * -lumG + sin * -lumG,
22020
+ lumG + cos * (1 - lumG) + sin * 0.14,
22021
+ lumG + cos * -lumG + sin * lumG,
22022
+ 0,
22023
+ lumB + cos * -lumB + sin * (1 - lumB),
22024
+ lumB + cos * -lumB + sin * -0.283,
22025
+ lumB + cos * (1 - lumB) + sin * lumB,
22026
+ 0,
22027
+ 0,
22028
+ 0,
22029
+ 0,
22030
+ 1
22031
+ );
22032
+ }
22033
+ /**
22034
+ * Apply a sepia tone.
22035
+ * @param amount - sepia intensity (0.0 = original, 1.0 = full sepia)
22036
+ * @returns this instance for chaining
22037
+ */
22038
+ sepia(amount = 1) {
22039
+ if (amount >= 1) {
22040
+ return this.transform(
22041
+ 0.393,
22042
+ 0.349,
22043
+ 0.272,
22044
+ 0,
22045
+ 0.769,
22046
+ 0.686,
22047
+ 0.534,
22048
+ 0,
22049
+ 0.189,
22050
+ 0.168,
22051
+ 0.131,
22052
+ 0,
22053
+ 0,
22054
+ 0,
22055
+ 0,
22056
+ 1
22057
+ );
22058
+ }
22059
+ const a = amount;
22060
+ const b = 1 - a;
22061
+ return this.transform(
22062
+ b + 0.393 * a,
22063
+ 0.349 * a,
22064
+ 0.272 * a,
22065
+ 0,
22066
+ 0.769 * a,
22067
+ b + 0.686 * a,
22068
+ 0.534 * a,
22069
+ 0,
22070
+ 0.189 * a,
22071
+ 0.168 * a,
22072
+ b + 0.131 * a,
22073
+ 0,
22074
+ 0,
22075
+ 0,
22076
+ 0,
22077
+ 1
22078
+ );
22079
+ }
22080
+ /**
22081
+ * Apply a color inversion.
22082
+ * @param amount - inversion amount (0.0 = original, 1.0 = fully inverted)
22083
+ * @returns this instance for chaining
22084
+ */
22085
+ invertColors(amount = 1) {
22086
+ const c = 1 - 2 * amount;
22087
+ return this.transform(
22088
+ c,
22089
+ 0,
22090
+ 0,
22091
+ 0,
22092
+ 0,
22093
+ c,
22094
+ 0,
22095
+ 0,
22096
+ 0,
22097
+ 0,
22098
+ c,
22099
+ 0,
22100
+ amount,
22101
+ amount,
22102
+ amount,
22103
+ 1
22104
+ );
22105
+ }
22106
+ };
22107
+
22108
+ // src/video/webgl/utils/attributes.js
22109
+ function extractAttributes(gl, shader) {
22110
+ const attributes = {};
22111
+ const attrRx = /attribute\s+\w+\s+(\w+)/g;
22112
+ let match;
22113
+ let i = 0;
22114
+ while (match = attrRx.exec(shader.vertex)) {
22115
+ attributes[match[1]] = i++;
22116
+ }
22117
+ return attributes;
22118
+ }
22119
+
22120
+ // src/video/webgl/utils/precision.js
22121
+ function setPrecision(src, precision) {
22122
+ if (src.substring(0, 9) !== "precision") {
22123
+ return "precision " + precision + " float;\n" + src;
22124
+ }
22125
+ return src;
22126
+ }
22127
+ function getMaxShaderPrecision(gl, highPrecision = true) {
22128
+ if (highPrecision && gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT).precision > 0 && gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT).precision > 0) {
22129
+ return "highp";
22130
+ }
22131
+ if (gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT).precision > 0 && gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT).precision > 0) {
22132
+ return "mediump";
22133
+ }
22134
+ return "lowp";
22135
+ }
22136
+
22137
+ // src/video/webgl/utils/program.js
22138
+ function compileShader(gl, type, source) {
22139
+ const shader = gl.createShader(type);
22140
+ gl.shaderSource(shader, source);
22141
+ gl.compileShader(shader);
22142
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
22143
+ throw new Error(gl.getShaderInfoLog(shader));
22144
+ }
22145
+ return shader;
22146
+ }
22147
+ function compileProgram(gl, vertex, fragment, attributes) {
22148
+ const vertShader = compileShader(gl, gl.VERTEX_SHADER, vertex);
22149
+ const fragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragment);
22150
+ const program = gl.createProgram();
22151
+ gl.attachShader(program, vertShader);
22152
+ gl.attachShader(program, fragShader);
22153
+ for (const location in attributes) {
22154
+ gl.bindAttribLocation(program, attributes[location], location);
22155
+ }
22156
+ gl.linkProgram(program);
22157
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
22158
+ const error_msg = "Error initializing Shader " + this + "\ngl.VALIDATE_STATUS: " + gl.getProgramParameter(program, gl.VALIDATE_STATUS) + "\ngl.getError()" + gl.getError() + "\ngl.getProgramInfoLog()" + gl.getProgramInfoLog(program);
22159
+ gl.deleteProgram(program);
22160
+ throw new Error(error_msg);
22161
+ }
22162
+ gl.useProgram(program);
22163
+ gl.deleteShader(vertShader);
22164
+ gl.deleteShader(fragShader);
22165
+ return program;
22166
+ }
22167
+
22168
+ // src/video/webgl/utils/string.js
22169
+ function minify(src) {
22170
+ src = src.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "$1");
22171
+ src = src.replace(/(\\n\s+)|(\s+\\n)/g, "");
22172
+ src = src.replace(/(\\r|\\n)+/g, "");
22173
+ src = src.replace(/[ \t]*([;,[\](){}\\\/\-+*|^&!=<>?~%])[ \t]*/g, "$1");
22174
+ return src;
22175
+ }
22176
+
22177
+ // src/video/webgl/utils/uniforms.js
22178
+ var fnHash = {
22179
+ bool: "1i",
22180
+ int: "1i",
22181
+ float: "1f",
22182
+ vec2: "2fv",
22183
+ vec3: "3fv",
22184
+ vec4: "4fv",
22185
+ bvec2: "2iv",
22186
+ bvec3: "3iv",
22187
+ bvec4: "4iv",
22188
+ ivec2: "2iv",
22189
+ ivec3: "3iv",
22190
+ ivec4: "4iv",
22191
+ mat2: "Matrix2fv",
22192
+ mat3: "Matrix3fv",
22193
+ mat4: "Matrix4fv",
22194
+ sampler2D: "1i"
22195
+ };
22196
+ function extractUniforms(gl, shader) {
22197
+ const uniforms = {};
22198
+ const uniRx = /uniform\s+(\w+)\s+(\w+)/g;
22199
+ const uniformsData = {};
22200
+ const descriptor = {};
22201
+ const locations = {};
22202
+ let match;
22203
+ [shader.vertex, shader.fragment].forEach((shader2) => {
22204
+ while (match = uniRx.exec(shader2)) {
22205
+ uniformsData[match[2]] = match[1];
22206
+ }
22207
+ });
22208
+ Object.keys(uniformsData).forEach((name) => {
22209
+ const type = uniformsData[name];
22210
+ locations[name] = gl.getUniformLocation(shader.program, name);
22211
+ descriptor[name] = {
22212
+ get: /* @__PURE__ */ (function(name2) {
22213
+ return function() {
22214
+ return locations[name2];
22215
+ };
22216
+ })(name),
22217
+ set: (function(name2, type2, fn) {
22218
+ if (/^mat/.test(type2)) {
22219
+ return function(val) {
22220
+ gl[fn](locations[name2], false, val);
22221
+ };
22222
+ } else {
22223
+ return function(val) {
22224
+ let fnv = fn;
22225
+ if (val.length && !/v$/.test(fn)) {
22226
+ fnv += "v";
22227
+ }
22228
+ gl[fnv](locations[name2], val);
22229
+ };
22230
+ }
22231
+ })(name, type, "uniform" + fnHash[type])
22232
+ };
22233
+ });
22234
+ Object.defineProperties(uniforms, descriptor);
22235
+ return uniforms;
22236
+ }
22237
+
22238
+ // src/video/webgl/glshader.js
22239
+ var GLShader = class {
22240
+ /**
22241
+ * @param {WebGLRenderingContext} gl - the current WebGL rendering context
22242
+ * @param {string} vertex - a string containing the GLSL source code to set
22243
+ * @param {string} fragment - a string containing the GLSL source code to set
22244
+ * @param {string} [precision=auto detected] - float precision ('lowp', 'mediump' or 'highp').
22245
+ * @see https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders
22246
+ * @example
22247
+ * // create a basic shader
22248
+ * let myShader = new me.GLShader(
22249
+ * // WebGL rendering context
22250
+ * gl,
22251
+ * // vertex shader
22252
+ * [
22253
+ * "void main() {",
22254
+ * " gl_Position = doMathToMakeClipspaceCoordinates;",
22255
+ * "}"
22256
+ * ].join("\n"),
22257
+ * // fragment shader
22258
+ * [
22259
+ * "void main() {",
22260
+ * " gl_FragColor = doMathToMakeAColor;",
22261
+ * "}"
22262
+ * ].join("\n")
22263
+ * )
22264
+ * // use the shader
22265
+ * myShader.bind();
22266
+ */
22267
+ constructor(gl, vertex, fragment, precision) {
22268
+ this.gl = gl;
22269
+ this.vertex = setPrecision(
22270
+ minify(vertex),
22271
+ precision || getMaxShaderPrecision(this.gl)
22272
+ );
22273
+ this.fragment = setPrecision(
22274
+ minify(fragment),
22275
+ precision || getMaxShaderPrecision(this.gl)
22276
+ );
22277
+ this.attributes = extractAttributes(this.gl, this);
22278
+ this.program = compileProgram(
22279
+ this.gl,
22280
+ this.vertex,
22281
+ this.fragment,
22282
+ this.attributes
22283
+ );
22284
+ this.uniforms = extractUniforms(this.gl, this);
22285
+ on(ONCONTEXT_LOST, this.destroy, this);
22286
+ }
22287
+ /**
22288
+ * Installs this shader program as part of current rendering state
22289
+ */
22290
+ bind() {
22291
+ this.gl.useProgram(this.program);
22292
+ }
22293
+ /**
22294
+ * returns the location of an attribute variable in this shader program
22295
+ * @param {string} name - the name of the attribute variable whose location to get.
22296
+ * @returns {GLint} number indicating the location of the variable name if found. Returns -1 otherwise
22297
+ */
22298
+ getAttribLocation(name) {
22299
+ const attr = this.attributes[name];
22300
+ if (typeof attr !== "undefined") {
22301
+ return attr;
22302
+ } else {
22303
+ return -1;
22304
+ }
22305
+ }
22306
+ /**
22307
+ * Set the uniform to the given value
22308
+ * @param {string} name - the uniform name
22309
+ * @param {object|Float32Array} value - the value to assign to that uniform
22310
+ * @example
22311
+ * myShader.setUniform("uProjectionMatrix", this.projectionMatrix);
22312
+ */
22313
+ setUniform(name, value) {
22314
+ const uniforms = this.uniforms;
22315
+ if (typeof uniforms[name] !== "undefined") {
22316
+ this.bind();
22317
+ if (typeof value === "object" && typeof value.toArray === "function") {
22318
+ uniforms[name] = value.toArray();
22319
+ } else {
22320
+ uniforms[name] = value;
22321
+ }
22322
+ } else {
22323
+ throw new Error("undefined (" + name + ") uniform for shader " + this);
22324
+ }
22325
+ }
22326
+ /**
22327
+ * activate the given vertex attribute for this shader
22328
+ * @param {WebGLRenderingContext} gl - the current WebGL rendering context
22329
+ * @param {object[]} attributes - an array of vertex attributes
22330
+ * @param {number} stride - the size of a single vertex in bytes
22331
+ */
22332
+ setVertexAttributes(gl, attributes, stride) {
22333
+ for (let index = 0; index < attributes.length; ++index) {
22334
+ const element = attributes[index];
22335
+ const location = this.getAttribLocation(element.name);
22336
+ if (location !== -1) {
22337
+ gl.enableVertexAttribArray(location);
22338
+ gl.vertexAttribPointer(
22339
+ location,
22340
+ element.size,
22341
+ element.type,
22342
+ element.normalized,
22343
+ stride,
22344
+ element.offset
22345
+ );
22346
+ } else {
22347
+ gl.disableVertexAttribArray(index);
22348
+ }
22349
+ }
22350
+ }
22351
+ /**
22352
+ * destroy this shader objects resources (program, attributes, uniforms)
22353
+ */
22354
+ destroy() {
22355
+ this.uniforms = null;
22356
+ this.attributes = null;
22357
+ this.gl.deleteProgram(this.program);
22358
+ this.vertex = null;
22359
+ this.fragment = null;
22360
+ }
22361
+ };
22362
+
22363
+ // src/video/webgl/shaders/quad.vert
22364
+ var quad_default = "// Current vertex point\nattribute vec2 aVertex;\nattribute vec2 aRegion;\nattribute vec4 aColor;\n\n// Projection matrix\nuniform mat4 uProjectionMatrix;\n\nvarying vec2 vRegion;\nvarying vec4 vColor;\n\nvoid main(void) {\n // Transform the vertex position by the projection matrix\n gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0);\n // Pass the remaining attributes to the fragment shader\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n}\n";
22365
+
22366
+ // src/video/webgl/shadereffect.js
22367
+ var ShaderEffect = class {
22368
+ /**
22369
+ * whether this effect is active (false in Canvas mode)
22370
+ * @type {boolean}
22371
+ */
22372
+ enabled = false;
22373
+ /**
22374
+ * @param {WebGLRenderer|CanvasRenderer} renderer - the current renderer instance
22375
+ * @param {string} fragmentBody - GLSL code containing a `vec4 apply(vec4 color, vec2 uv)` function
22376
+ * that receives the sampled pixel color and UV coordinates, and returns the modified color.
22377
+ * You can declare additional `uniform` variables before the `apply()` function.
22378
+ * @param {string} [precision=auto detected] - float precision ('lowp', 'mediump' or 'highp')
22379
+ */
22380
+ constructor(renderer2, fragmentBody, precision) {
22381
+ if (typeof renderer2.gl === "undefined") {
22382
+ console.warn(
22383
+ "ShaderEffect requires WebGL and is disabled in Canvas mode"
22384
+ );
22385
+ return;
22386
+ }
22387
+ const fragment = [
22388
+ "uniform sampler2D uSampler;",
22389
+ "varying vec4 vColor;",
22390
+ "varying vec2 vRegion;",
22391
+ // user-provided fragment body (uniforms + apply function)
22392
+ fragmentBody,
22393
+ "void main(void) {",
22394
+ " vec4 texColor = texture2D(uSampler, vRegion) * vColor;",
22395
+ " gl_FragColor = apply(texColor, vRegion);",
22396
+ "}"
22397
+ ].join("\n");
22398
+ this._shader = new GLShader(
22399
+ renderer2.gl,
22400
+ quad_default,
22401
+ fragment,
22402
+ precision || renderer2.shaderPrecision
22403
+ );
22404
+ this.enabled = true;
22405
+ }
22406
+ /**
22407
+ * Set the uniform to the given value
22408
+ * @param {string} name - the uniform name
22409
+ * @param {object|Float32Array} value - the value to assign to that uniform
22410
+ */
22411
+ setUniform(name, value) {
22412
+ if (this.enabled) {
22413
+ this._shader.setUniform(name, value);
22414
+ }
22415
+ }
22416
+ /** @ignore */
22417
+ bind() {
22418
+ if (this.enabled) {
22419
+ this._shader.bind();
22420
+ }
22421
+ }
22422
+ /** @ignore */
22423
+ getAttribLocation(name) {
22424
+ return this.enabled ? this._shader.getAttribLocation(name) : -1;
22425
+ }
22426
+ /** @ignore */
22427
+ setVertexAttributes(gl, attributes, stride) {
22428
+ if (this.enabled) {
22429
+ this._shader.setVertexAttributes(gl, attributes, stride);
22430
+ }
22431
+ }
22432
+ /** @ignore */
22433
+ get program() {
22434
+ return this.enabled ? this._shader.program : null;
22435
+ }
22436
+ /** @ignore */
22437
+ get vertex() {
22438
+ return this.enabled ? this._shader.vertex : null;
22439
+ }
22440
+ /** @ignore */
22441
+ get fragment() {
22442
+ return this.enabled ? this._shader.fragment : null;
22443
+ }
22444
+ /** @ignore */
22445
+ get attributes() {
22446
+ return this.enabled ? this._shader.attributes : {};
22447
+ }
22448
+ /** @ignore */
22449
+ get uniforms() {
22450
+ return this.enabled ? this._shader.uniforms : {};
22451
+ }
22452
+ /**
22453
+ * destroy this shader effect
22454
+ */
22455
+ destroy() {
22456
+ if (this.enabled) {
22457
+ this._shader.destroy();
22458
+ this.enabled = false;
22459
+ }
22460
+ }
22461
+ };
22462
+
22463
+ // src/video/webgl/effects/colorMatrix.js
22464
+ var ColorMatrixEffect = class extends ShaderEffect {
22465
+ /**
22466
+ * @param {import("../webgl_renderer.js").default} renderer - the current renderer instance
22467
+ * @param {object} [options] - effect options
22468
+ * @param {ColorMatrix} [options.matrix] - an initial color matrix. Defaults to identity.
22469
+ */
22470
+ constructor(renderer2, options = {}) {
22471
+ super(
22472
+ renderer2,
22473
+ `
22474
+ uniform mat4 uColorMatrix;
22475
+ vec4 apply(vec4 color, vec2 uv) {
22476
+ return uColorMatrix * color;
22477
+ }
22478
+ `
22479
+ );
22480
+ this._matrix = options.matrix || new ColorMatrix();
22481
+ this._syncUniform();
22482
+ }
22483
+ /**
22484
+ * Push the current matrix values to the GPU.
22485
+ * @ignore
22486
+ */
22487
+ _syncUniform() {
22488
+ this.setUniform("uColorMatrix", this._matrix.val);
22489
+ }
22490
+ /**
22491
+ * Reset the color matrix to identity (no color change).
22492
+ * @returns {this} this instance for chaining
22493
+ */
22494
+ reset() {
22495
+ this._matrix.identity();
22496
+ this._syncUniform();
22497
+ return this;
22498
+ }
22499
+ /**
22500
+ * Apply a brightness adjustment.
22501
+ * @param {number} amount - brightness multiplier (1.0 = normal, >1 brighter, <1 darker)
22502
+ * @returns {this} this instance for chaining
22503
+ */
22504
+ brightness(amount) {
22505
+ this._matrix.brightness(amount);
22506
+ this._syncUniform();
22507
+ return this;
22508
+ }
22509
+ /**
22510
+ * Apply a contrast adjustment.
22511
+ * @param {number} amount - contrast multiplier (1.0 = normal, >1 more contrast, <1 less)
22512
+ * @returns {this} this instance for chaining
22513
+ */
22514
+ contrast(amount) {
22515
+ this._matrix.contrast(amount);
22516
+ this._syncUniform();
22517
+ return this;
22518
+ }
22519
+ /**
22520
+ * Apply a saturation adjustment.
22521
+ * @param {number} amount - saturation level (0.0 = grayscale, 1.0 = normal, >1 over-saturated)
22522
+ * @returns {this} this instance for chaining
22523
+ */
22524
+ saturate(amount) {
22525
+ this._matrix.saturate(amount);
22526
+ this._syncUniform();
22527
+ return this;
22528
+ }
22529
+ /**
22530
+ * Apply a hue rotation.
22531
+ * @param {number} angle - rotation angle in radians
22532
+ * @returns {this} this instance for chaining
22533
+ */
22534
+ hueRotate(angle) {
22535
+ this._matrix.hueRotate(angle);
22536
+ this._syncUniform();
22537
+ return this;
22538
+ }
22539
+ /**
22540
+ * Apply a sepia tone.
22541
+ * @param {number} [amount=1.0] - sepia intensity (0.0 = original, 1.0 = full sepia)
22542
+ * @returns {this} this instance for chaining
22543
+ */
22544
+ sepia(amount = 1) {
22545
+ this._matrix.sepia(amount);
22546
+ this._syncUniform();
22547
+ return this;
22548
+ }
22549
+ /**
22550
+ * Apply a color inversion.
22551
+ * @param {number} [amount=1.0] - inversion amount (0.0 = original, 1.0 = fully inverted)
22552
+ * @returns {this} this instance for chaining
22553
+ */
22554
+ invertColors(amount = 1) {
22555
+ this._matrix.invertColors(amount);
22556
+ this._syncUniform();
22557
+ return this;
22558
+ }
22559
+ /**
22560
+ * Multiply the current matrix by another color matrix.
22561
+ * @param {ColorMatrix} matrix - the matrix to multiply with
22562
+ * @returns {this} this instance for chaining
22563
+ */
22564
+ multiply(matrix) {
22565
+ this._matrix.multiply(matrix);
22566
+ this._syncUniform();
22567
+ return this;
22568
+ }
22569
+ /**
22570
+ * Multiplies the current matrix with a transform described by individual values.
22571
+ * Accepts either 6 values (2D affine: a, b, c, d, e, f) or 16 values (full 4x4 column-major).
22572
+ * @param {...number} args - 6 or 16 numeric values
22573
+ * @returns {this} this instance for chaining
22574
+ */
22575
+ transform(...args) {
22576
+ this._matrix.transform(...args);
22577
+ this._syncUniform();
22578
+ return this;
22579
+ }
22580
+ };
22581
+
22582
+ // src/camera/effects/shake_effect.ts
22583
+ var ShakeEffect = class extends CameraEffect {
22584
+ /**
22585
+ * maximum pixel offset during shake
22586
+ */
22587
+ intensity;
22588
+ /**
22589
+ * remaining duration in milliseconds
22590
+ */
22591
+ duration;
22592
+ /**
22593
+ * which axes to shake (use camera.AXIS constants)
22594
+ */
22595
+ axis;
22596
+ /**
22597
+ * optional callback when shake completes
22598
+ */
22599
+ onComplete;
22600
+ /**
22601
+ * @param camera - the camera to shake
22602
+ * @param options - shake parameters
22603
+ * @param options.intensity - maximum offset in pixels
22604
+ * @param options.duration - duration in milliseconds
22605
+ * @param [options.axis=3] - which axes (NONE=0, HORIZONTAL=1, VERTICAL=2, BOTH=3)
22606
+ * @param [options.onComplete] - callback when the shake finishes
22607
+ */
22608
+ constructor(camera, options) {
22609
+ super(camera);
22610
+ this.intensity = options.intensity;
22611
+ this.duration = options.duration;
22612
+ this.axis = options.axis ?? camera.AXIS.BOTH;
22613
+ this.onComplete = options.onComplete;
22614
+ }
22615
+ update(dt) {
22616
+ this.duration -= dt;
22617
+ if (this.duration <= 0) {
22618
+ this.duration = 0;
22619
+ this.camera.offset.setZero();
22620
+ this.isComplete = true;
22621
+ if (typeof this.onComplete === "function") {
22622
+ this.onComplete();
22623
+ }
22624
+ } else {
22625
+ const axis = this.camera.AXIS;
22626
+ if (this.axis === axis.BOTH || this.axis === axis.HORIZONTAL) {
22627
+ this.camera.offset.x = (Math.random() - 0.5) * this.intensity;
22628
+ }
22629
+ if (this.axis === axis.BOTH || this.axis === axis.VERTICAL) {
22630
+ this.camera.offset.y = (Math.random() - 0.5) * this.intensity;
22631
+ }
22632
+ }
22633
+ }
22634
+ destroy() {
22635
+ this.camera.offset.setZero();
22636
+ }
22637
+ };
22638
+
21352
22639
  // src/camera/camera2d.ts
21353
22640
  var targetV = new Vector2d();
21354
22641
  var Camera2d = class extends Renderable {
@@ -21439,20 +22726,23 @@ var Camera2d = class extends Renderable {
21439
22726
  /** default value follow */
21440
22727
  follow_axis;
21441
22728
  /**
21442
- * shake variables
21443
- * @ignore
22729
+ * active camera effects (shake, fade, etc.)
21444
22730
  */
21445
- _shake;
22731
+ cameraEffects;
21446
22732
  /**
21447
- * flash variables
21448
- * @ignore
21449
- */
21450
- _fadeOut;
21451
- /**
21452
- * fade variables
21453
- * @ignore
22733
+ * A built-in color transformation matrix applied as the final post-processing pass.
22734
+ * Provides convenient color grading (brightness, contrast, saturation, etc.)
22735
+ * that is always applied after any effects added via {@link addPostEffect}.
22736
+ * When set to identity (default), no effect is applied and there is zero overhead.
22737
+ * @example
22738
+ * // warm HDR-like color grading
22739
+ * camera.colorMatrix.contrast(1.2).saturate(1.1);
22740
+ * // reset to no color grading
22741
+ * camera.colorMatrix.identity();
21454
22742
  */
21455
- _fadeIn;
22743
+ colorMatrix;
22744
+ /** @ignore */
22745
+ _colorMatrixEffect;
21456
22746
  /** the camera deadzone */
21457
22747
  deadzone;
21458
22748
  /**
@@ -21479,20 +22769,7 @@ var Camera2d = class extends Renderable {
21479
22769
  this.offset = new Vector2d();
21480
22770
  this.target = null;
21481
22771
  this.follow_axis = this.AXIS.NONE;
21482
- this._shake = {
21483
- intensity: 0,
21484
- duration: 0,
21485
- axis: this.AXIS.BOTH,
21486
- onComplete: null
21487
- };
21488
- this._fadeOut = {
21489
- color: null,
21490
- tween: null
21491
- };
21492
- this._fadeIn = {
21493
- color: null,
21494
- tween: null
21495
- };
22772
+ this.cameraEffects = [];
21496
22773
  this.screenX = 0;
21497
22774
  this.screenY = 0;
21498
22775
  this.zoom = 1;
@@ -21504,6 +22781,9 @@ var Camera2d = class extends Renderable {
21504
22781
  this.setDeadzone(this.width / 6, this.height / 6);
21505
22782
  this.anchorPoint.set(0, 0);
21506
22783
  this.isKinematic = false;
22784
+ this._postEffectManaged = true;
22785
+ this.colorMatrix = new ColorMatrix();
22786
+ this._colorMatrixEffect = null;
21507
22787
  this.bounds.setMinMax(minX, minY, maxX, maxY);
21508
22788
  this._updateProjectionMatrix();
21509
22789
  on(GAME_RESET, this.reset, this);
@@ -21619,6 +22899,12 @@ var Camera2d = class extends Renderable {
21619
22899
  this.worldProjection.identity();
21620
22900
  this.screenProjection.identity();
21621
22901
  this._updateProjectionMatrix();
22902
+ for (let i = this.cameraEffects.length - 1; i >= 0; i--) {
22903
+ if (!this.cameraEffects[i].isPersistent) {
22904
+ this.cameraEffects[i].destroy();
22905
+ this.cameraEffects.splice(i, 1);
22906
+ }
22907
+ }
21622
22908
  }
21623
22909
  /**
21624
22910
  * change the deadzone settings.
@@ -21777,20 +23063,14 @@ var Camera2d = class extends Renderable {
21777
23063
  /** @ignore */
21778
23064
  update(dt) {
21779
23065
  this.updateTarget(dt);
21780
- if (this._shake.duration > 0) {
21781
- this._shake.duration -= dt ?? 0;
21782
- if (this._shake.duration <= 0) {
21783
- this._shake.duration = 0;
21784
- this.offset.setZero();
21785
- if (typeof this._shake.onComplete === "function") {
21786
- this._shake.onComplete();
21787
- }
21788
- } else {
21789
- if (this._shake.axis === this.AXIS.BOTH || this._shake.axis === this.AXIS.HORIZONTAL) {
21790
- this.offset.x = (Math.random() - 0.5) * this._shake.intensity;
21791
- }
21792
- if (this._shake.axis === this.AXIS.BOTH || this._shake.axis === this.AXIS.VERTICAL) {
21793
- this.offset.y = (Math.random() - 0.5) * this._shake.intensity;
23066
+ if (this.cameraEffects.length > 0) {
23067
+ for (const fx of this.cameraEffects) {
23068
+ fx.update(dt ?? 0);
23069
+ }
23070
+ for (let i = this.cameraEffects.length - 1; i >= 0; i--) {
23071
+ if (this.cameraEffects[i].isComplete) {
23072
+ this.cameraEffects[i].destroy();
23073
+ this.cameraEffects.splice(i, 1);
21794
23074
  }
21795
23075
  }
21796
23076
  this.isDirty = true;
@@ -21798,9 +23078,6 @@ var Camera2d = class extends Renderable {
21798
23078
  if (this.isDirty) {
21799
23079
  emit(VIEWPORT_ONCHANGE, this.pos);
21800
23080
  }
21801
- if (this._fadeIn.tween != null || this._fadeOut.tween != null) {
21802
- this.isDirty = true;
21803
- }
21804
23081
  if (!this.currentTransform.isIdentity()) {
21805
23082
  this.invCurrentTransform.copy(this.currentTransform).invert();
21806
23083
  } else {
@@ -21821,12 +23098,21 @@ var Camera2d = class extends Renderable {
21821
23098
  * app.viewport.shake(10, 500, app.viewport.AXIS.BOTH);
21822
23099
  */
21823
23100
  shake(intensity, duration, axis, onComplete, force) {
21824
- if (this._shake.duration === 0 || force === true) {
21825
- this._shake.intensity = intensity;
21826
- this._shake.duration = duration;
21827
- this._shake.axis = axis || this.AXIS.BOTH;
21828
- this._shake.onComplete = typeof onComplete === "function" ? onComplete : void 0;
23101
+ const existing = this.getCameraEffect(ShakeEffect);
23102
+ if (existing && force !== true) {
23103
+ return;
23104
+ }
23105
+ if (existing) {
23106
+ this.removeCameraEffect(existing);
21829
23107
  }
23108
+ this.addCameraEffect(
23109
+ new ShakeEffect(this, {
23110
+ intensity,
23111
+ duration,
23112
+ axis: axis || this.AXIS.BOTH,
23113
+ onComplete
23114
+ })
23115
+ );
21830
23116
  }
21831
23117
  /**
21832
23118
  * fadeOut(flash) effect<p>
@@ -21843,13 +23129,14 @@ var Camera2d = class extends Renderable {
21843
23129
  * });
21844
23130
  */
21845
23131
  fadeOut(color, duration = 1e3, onComplete) {
21846
- this._fadeOut.color = colorPool.get(color);
21847
- this._fadeOut.tween = tweenPool.get(this._fadeOut.color).to({ alpha: 0 }, { duration });
21848
- if (onComplete) {
21849
- this._fadeOut.tween.onComplete(onComplete);
21850
- }
21851
- this._fadeOut.tween.isPersistent = true;
21852
- this._fadeOut.tween.start();
23132
+ this.addCameraEffect(
23133
+ new FadeEffect(this, {
23134
+ color,
23135
+ duration,
23136
+ direction: "out",
23137
+ onComplete
23138
+ })
23139
+ );
21853
23140
  }
21854
23141
  /**
21855
23142
  * fadeIn effect <p>
@@ -21862,15 +23149,49 @@ var Camera2d = class extends Renderable {
21862
23149
  * app.viewport.fadeIn("#FFFFFF", 75);
21863
23150
  */
21864
23151
  fadeIn(color, duration = 1e3, onComplete) {
21865
- this._fadeIn.color = colorPool.get(color);
21866
- const _alpha = this._fadeIn.color.alpha;
21867
- this._fadeIn.color.alpha = 0;
21868
- this._fadeIn.tween = tweenPool.get(this._fadeIn.color).to({ alpha: _alpha }, { duration });
21869
- if (onComplete) {
21870
- this._fadeIn.tween.onComplete(onComplete);
23152
+ this.addCameraEffect(
23153
+ new FadeEffect(this, {
23154
+ color,
23155
+ duration,
23156
+ direction: "in",
23157
+ onComplete
23158
+ })
23159
+ );
23160
+ }
23161
+ /**
23162
+ * Add a camera effect to this camera.
23163
+ * @param effect - the camera effect to add
23164
+ * @returns the added effect
23165
+ * @example
23166
+ * camera.addCameraEffect(new ShakeEffect(camera, { intensity: 10, duration: 500 }));
23167
+ */
23168
+ addCameraEffect(effect) {
23169
+ this.cameraEffects.push(effect);
23170
+ return effect;
23171
+ }
23172
+ /**
23173
+ * Get the first camera effect matching the given class.
23174
+ * @param effectClass - the effect class to search for
23175
+ * @returns the first matching effect, or undefined
23176
+ * @example
23177
+ * const shake = camera.getCameraEffect(ShakeEffect);
23178
+ */
23179
+ getCameraEffect(effectClass) {
23180
+ return this.cameraEffects.find((fx) => fx instanceof effectClass);
23181
+ }
23182
+ /**
23183
+ * Remove a specific camera effect instance.
23184
+ * @param effect - the effect to remove
23185
+ * @example
23186
+ * const fade = camera.getCameraEffect(FadeEffect);
23187
+ * if (fade) camera.removeCameraEffect(fade);
23188
+ */
23189
+ removeCameraEffect(effect) {
23190
+ const idx = this.cameraEffects.indexOf(effect);
23191
+ if (idx !== -1) {
23192
+ effect.destroy();
23193
+ this.cameraEffects.splice(idx, 1);
21871
23194
  }
21872
- this._fadeIn.tween.isPersistent = true;
21873
- this._fadeIn.tween.start();
21874
23195
  }
21875
23196
  /**
21876
23197
  * set the camera position around the specified object
@@ -21934,30 +23255,8 @@ var Camera2d = class extends Renderable {
21934
23255
  * @ignore
21935
23256
  */
21936
23257
  drawFX(renderer2) {
21937
- const r = renderer2;
21938
- if (this._fadeIn.tween) {
21939
- r.save();
21940
- r.resetTransform();
21941
- r.setColor(this._fadeIn.color);
21942
- r.fillRect(0, 0, this.width, this.height);
21943
- r.restore();
21944
- if (this._fadeIn.color.alpha === 1) {
21945
- this._fadeIn.tween = null;
21946
- colorPool.release(this._fadeIn.color);
21947
- this._fadeIn.color = null;
21948
- }
21949
- }
21950
- if (this._fadeOut.tween) {
21951
- r.save();
21952
- r.resetTransform();
21953
- r.setColor(this._fadeOut.color);
21954
- r.fillRect(0, 0, this.width, this.height);
21955
- r.restore();
21956
- if (this._fadeOut.color.alpha === 0) {
21957
- this._fadeOut.tween = null;
21958
- colorPool.release(this._fadeOut.color);
21959
- this._fadeOut.color = null;
21960
- }
23258
+ for (const fx of this.cameraEffects) {
23259
+ fx.draw(renderer2, this.width, this.height);
21961
23260
  }
21962
23261
  }
21963
23262
  /**
@@ -21971,9 +23270,19 @@ var Camera2d = class extends Renderable {
21971
23270
  const containerOffsetY = isNonDefault ? container.pos.y : 0;
21972
23271
  const translateX = this.pos.x + this.offset.x + containerOffsetX;
21973
23272
  const translateY = this.pos.y + this.offset.y + containerOffsetY;
23273
+ if (!this.colorMatrix.isIdentity()) {
23274
+ if (!this._colorMatrixEffect) {
23275
+ this._colorMatrixEffect = new ColorMatrixEffect(renderer2);
23276
+ }
23277
+ this._colorMatrixEffect.reset().multiply(this.colorMatrix);
23278
+ this.postEffects.push(this._colorMatrixEffect);
23279
+ }
23280
+ const usePostEffect = r.beginPostEffect(this);
21974
23281
  container.translate(-translateX, -translateY);
21975
23282
  this.preDraw(r);
21976
- r.clipRect(this.screenX, this.screenY, this.width, this.height);
23283
+ if (!usePostEffect) {
23284
+ r.clipRect(this.screenX, this.screenY, this.width, this.height);
23285
+ }
21977
23286
  if (isNonDefault) {
21978
23287
  const left = -this.screenX / this.zoom;
21979
23288
  const top = -this.screenY / this.zoom;
@@ -22015,8 +23324,27 @@ var Camera2d = class extends Renderable {
22015
23324
  this.drawFX(renderer2);
22016
23325
  container.postDraw(r);
22017
23326
  this.postDraw(r);
23327
+ r.endPostEffect(this);
23328
+ if (this._colorMatrixEffect) {
23329
+ const idx = this.postEffects.indexOf(this._colorMatrixEffect);
23330
+ if (idx !== -1) {
23331
+ this.postEffects.splice(idx, 1);
23332
+ }
23333
+ }
22018
23334
  container.translate(translateX, translateY);
22019
23335
  }
23336
+ /**
23337
+ * @ignore
23338
+ */
23339
+ destroy() {
23340
+ if (this._colorMatrixEffect) {
23341
+ if (typeof this._colorMatrixEffect.destroy === "function") {
23342
+ this._colorMatrixEffect.destroy();
23343
+ }
23344
+ this._colorMatrixEffect = null;
23345
+ }
23346
+ super.destroy();
23347
+ }
22020
23348
  };
22021
23349
 
22022
23350
  // src/state/stage.ts
@@ -22308,13 +23636,19 @@ var _state = -1;
22308
23636
  var _animFrameId = -1;
22309
23637
  var _isPaused = false;
22310
23638
  var _stages = {};
22311
- var _fade = {
23639
+ var _transitionType = "fade";
23640
+ var _transitionConfig = {
22312
23641
  color: "",
22313
23642
  duration: 0
22314
23643
  };
22315
23644
  var _onSwitchComplete = null;
22316
23645
  var _extraArgs = null;
22317
23646
  var _pauseTime = 0;
23647
+ var _freezeTimer = null;
23648
+ var _freezeEndsAt = 0;
23649
+ var _freezeMusic = false;
23650
+ var _freezeStartedPause = false;
23651
+ var _freezeResolvers = [];
22318
23652
  function _startRunLoop() {
22319
23653
  if (_animFrameId === -1 && _state !== -1) {
22320
23654
  _animFrameId = globalThis.requestAnimationFrame(_renderFrame);
@@ -22328,6 +23662,32 @@ function _resumeRunLoop() {
22328
23662
  function _pauseRunLoop() {
22329
23663
  _isPaused = true;
22330
23664
  }
23665
+ function _resolveFreezeWaiters() {
23666
+ const resolvers = _freezeResolvers;
23667
+ _freezeResolvers = [];
23668
+ for (const resolve of resolvers) {
23669
+ resolve();
23670
+ }
23671
+ }
23672
+ function _endFreeze() {
23673
+ _freezeTimer = null;
23674
+ _freezeEndsAt = 0;
23675
+ const wasOwnedPause = _freezeStartedPause;
23676
+ _freezeStartedPause = false;
23677
+ if (wasOwnedPause) {
23678
+ state.resume(_freezeMusic);
23679
+ }
23680
+ _resolveFreezeWaiters();
23681
+ }
23682
+ function _cancelFreeze() {
23683
+ if (_freezeTimer !== null) {
23684
+ clearTimeout(_freezeTimer);
23685
+ _freezeTimer = null;
23686
+ _freezeEndsAt = 0;
23687
+ _freezeStartedPause = false;
23688
+ _resolveFreezeWaiters();
23689
+ }
23690
+ }
22331
23691
  function _renderFrame(time) {
22332
23692
  emit(TICK, time);
22333
23693
  if (_animFrameId !== -1) {
@@ -22354,6 +23714,7 @@ function _switchState(stateId) {
22354
23714
  emit(STATE_CHANGE);
22355
23715
  if (_onSwitchComplete) {
22356
23716
  _onSwitchComplete();
23717
+ _onSwitchComplete = null;
22357
23718
  }
22358
23719
  }
22359
23720
  }
@@ -22367,6 +23728,9 @@ once(BOOT, () => {
22367
23728
  once(VIDEO_INIT, () => {
22368
23729
  state.change(state.DEFAULT, true);
22369
23730
  });
23731
+ on(BLUR, () => {
23732
+ _cancelFreeze();
23733
+ });
22370
23734
  });
22371
23735
  var state = {
22372
23736
  /**
@@ -22425,6 +23789,7 @@ var state = {
22425
23789
  */
22426
23790
  stop(shouldPauseTrack = false) {
22427
23791
  if (_state !== this.LOADING && this.isRunning()) {
23792
+ _cancelFreeze();
22428
23793
  _stopRunLoop();
22429
23794
  if (shouldPauseTrack) {
22430
23795
  pauseTrack();
@@ -22467,6 +23832,7 @@ var state = {
22467
23832
  */
22468
23833
  resume(music = false) {
22469
23834
  if (this.isPaused()) {
23835
+ _cancelFreeze();
22470
23836
  _resumeRunLoop();
22471
23837
  if (music) {
22472
23838
  resumeTrack();
@@ -22475,6 +23841,59 @@ var state = {
22475
23841
  emit(STATE_RESUME, _pauseTime);
22476
23842
  }
22477
23843
  },
23844
+ /**
23845
+ * Freeze the current stage for a fixed duration, then automatically resume.
23846
+ * Useful for hit-stop / hit-pause effects on impact.
23847
+ *
23848
+ * Behaviour notes:
23849
+ * - If `freeze()` is called again while a freeze is already active, the
23850
+ * freeze is *extended* to whichever end-time is later (calls do not
23851
+ * stack). The `music` flag from the initial call is preserved.
23852
+ * - If the game was already paused when `freeze()` was called, the freeze
23853
+ * timer will *not* unpause it on expiry — the game stays paused.
23854
+ * - Calling `state.resume()` or `state.stop()` while a freeze is active
23855
+ * cancels the timer and resolves the returned promise immediately.
23856
+ * - The freeze is also cancelled when the window loses focus (BLUR) since
23857
+ * the hit-stop's "moment" is short enough that the user has missed it
23858
+ * by the time they return. The regular `pauseOnBlur` behaviour still
23859
+ * keeps the game paused while away.
23860
+ * - Negative, `NaN`, and `Infinity` durations silently no-op.
23861
+ * @param duration - duration of the freeze in milliseconds
23862
+ * @param [music=false] - also pause the current music track during the freeze
23863
+ * @returns a Promise that resolves once the freeze ends (or is cancelled)
23864
+ * @example
23865
+ * // simple hit-stop on impact
23866
+ * state.freeze(80);
23867
+ *
23868
+ * // chain VFX after the freeze
23869
+ * await state.freeze(120);
23870
+ * spawnImpactParticles();
23871
+ */
23872
+ freeze(duration, music = false) {
23873
+ if (!Number.isFinite(duration) || duration < 0) {
23874
+ return Promise.resolve();
23875
+ }
23876
+ const now = globalThis.performance.now();
23877
+ const newEndsAt = now + duration;
23878
+ if (_freezeTimer !== null) {
23879
+ if (newEndsAt > _freezeEndsAt) {
23880
+ clearTimeout(_freezeTimer);
23881
+ _freezeEndsAt = newEndsAt;
23882
+ _freezeTimer = setTimeout(_endFreeze, newEndsAt - now);
23883
+ }
23884
+ } else {
23885
+ _freezeMusic = music;
23886
+ _freezeEndsAt = newEndsAt;
23887
+ _freezeStartedPause = !this.isPaused();
23888
+ if (_freezeStartedPause) {
23889
+ this.pause(music);
23890
+ }
23891
+ _freezeTimer = setTimeout(_endFreeze, duration);
23892
+ }
23893
+ return new Promise((resolve) => {
23894
+ _freezeResolvers.push(resolve);
23895
+ });
23896
+ },
22478
23897
  /**
22479
23898
  * return the running state of the state manager
22480
23899
  * @returns true if a "process is running"
@@ -22552,15 +23971,28 @@ var state = {
22552
23971
  },
22553
23972
  /**
22554
23973
  * specify a global transition effect
22555
- * @param effect - (only "fade" is supported for now)
23974
+ * @param effect - "fade" for a color fade, "mask" for a shape-based mask transition
22556
23975
  * @param color - a CSS color value
22557
23976
  * @param [duration=1000] - expressed in milliseconds
22558
- */
22559
- transition(effect, color, duration) {
22560
- if (effect === "fade") {
22561
- _fade.color = color;
22562
- _fade.duration = duration;
22563
- }
23977
+ * @param [shape] - an Ellipse or Polygon defining the mask shape (required when effect is "mask")
23978
+ * @example
23979
+ * // classic fade to black
23980
+ * state.transition("fade", "#000", 500);
23981
+ * @example
23982
+ * // iris (circle) mask transition
23983
+ * state.transition("mask", "#000", 500, new Ellipse(0, 0, 1, 1));
23984
+ * @example
23985
+ * // diamond mask transition
23986
+ * state.transition("mask", "#000", 400, new Polygon(0, 0, [
23987
+ * { x: 0, y: -1 }, { x: 1, y: 0 },
23988
+ * { x: 0, y: 1 }, { x: -1, y: 0 },
23989
+ * ]));
23990
+ */
23991
+ transition(effect, color, duration = 1e3, shape) {
23992
+ _transitionType = effect;
23993
+ _transitionConfig.color = color;
23994
+ _transitionConfig.duration = duration;
23995
+ _transitionConfig.shape = shape;
22564
23996
  },
22565
23997
  /**
22566
23998
  * enable/disable the transition to a particular state (by default enabled for all)
@@ -22586,21 +24018,74 @@ var state = {
22586
24018
  }
22587
24019
  if (!this.isCurrent(stateId)) {
22588
24020
  _extraArgs = extraArgs.length > 0 ? extraArgs : null;
22589
- if (_fade.duration && _stages[stateId].transition) {
22590
- _onSwitchComplete = () => {
22591
- _app.viewport.fadeOut(_fade.color, _fade.duration);
24021
+ if (_transitionConfig.duration && _stages[stateId].transition) {
24022
+ const onComplete = () => {
24023
+ defer(
24024
+ _switchState,
24025
+ state,
24026
+ stateId
24027
+ );
22592
24028
  };
22593
- _app.viewport.fadeIn(
22594
- _fade.color,
22595
- _fade.duration,
22596
- function() {
22597
- defer(
22598
- _switchState,
22599
- this,
22600
- stateId
22601
- );
24029
+ switch (_transitionType) {
24030
+ case "fade": {
24031
+ const color = _transitionConfig.color;
24032
+ const duration = _transitionConfig.duration;
24033
+ _onSwitchComplete = () => {
24034
+ _app.viewport.addCameraEffect(
24035
+ new FadeEffect(_app.viewport, {
24036
+ color,
24037
+ duration,
24038
+ direction: "out"
24039
+ })
24040
+ );
24041
+ };
24042
+ const fadeEffect = new FadeEffect(_app.viewport, {
24043
+ color,
24044
+ duration,
24045
+ direction: "in",
24046
+ onComplete
24047
+ });
24048
+ fadeEffect.isPersistent = true;
24049
+ _app.viewport.addCameraEffect(fadeEffect);
24050
+ break;
22602
24051
  }
22603
- );
24052
+ case "mask": {
24053
+ if (!_transitionConfig.shape) {
24054
+ console.warn(
24055
+ "melonJS: mask transition requires a shape, falling back to direct switch"
24056
+ );
24057
+ onComplete();
24058
+ break;
24059
+ }
24060
+ const shape = _transitionConfig.shape;
24061
+ const color = _transitionConfig.color;
24062
+ const duration = _transitionConfig.duration;
24063
+ _onSwitchComplete = () => {
24064
+ _app.viewport.addCameraEffect(
24065
+ new MaskEffect(_app.viewport, {
24066
+ shape,
24067
+ color,
24068
+ duration,
24069
+ direction: "reveal"
24070
+ })
24071
+ );
24072
+ };
24073
+ const maskEffect = new MaskEffect(_app.viewport, {
24074
+ shape,
24075
+ color,
24076
+ duration,
24077
+ direction: "hide",
24078
+ onComplete
24079
+ });
24080
+ maskEffect.isPersistent = true;
24081
+ _app.viewport.addCameraEffect(maskEffect);
24082
+ break;
24083
+ }
24084
+ default:
24085
+ throw new Error(
24086
+ `Invalid transition type: ${_transitionType}`
24087
+ );
24088
+ }
22604
24089
  } else {
22605
24090
  if (forceChange) {
22606
24091
  _switchState(stateId);
@@ -23124,6 +24609,7 @@ var Tween = class {
23124
24609
  _yoyo;
23125
24610
  _reversed;
23126
24611
  _delayTime;
24612
+ _repeatDelayTime;
23127
24613
  _startTime;
23128
24614
  _easingFunction;
23129
24615
  _interpolationFunction;
@@ -23173,6 +24659,7 @@ var Tween = class {
23173
24659
  this._yoyo = false;
23174
24660
  this._reversed = false;
23175
24661
  this._delayTime = 0;
24662
+ this._repeatDelayTime = void 0;
23176
24663
  this._startTime = null;
23177
24664
  this._easingFunction = Easing.Linear.None;
23178
24665
  this._interpolationFunction = Interpolation.Linear;
@@ -23260,6 +24747,7 @@ var Tween = class {
23260
24747
  * @param [options.delay] - delay before starting, in milliseconds
23261
24748
  * @param [options.yoyo] - bounce back to original values when finished (use with `repeat`)
23262
24749
  * @param [options.repeat] - number of times to repeat (use `Infinity` for endless loops)
24750
+ * @param [options.repeatDelay] - delay in milliseconds before each repeat cycle
23263
24751
  * @param [options.interpolation] - interpolation function for array values
23264
24752
  * @param [options.autoStart] - start the tween immediately without calling `start()`
23265
24753
  * @returns this instance for object chaining
@@ -23282,6 +24770,9 @@ var Tween = class {
23282
24770
  if (options.delay !== void 0) {
23283
24771
  this.delay(options.delay);
23284
24772
  }
24773
+ if (options.repeatDelay !== void 0) {
24774
+ this.repeatDelay(options.repeatDelay);
24775
+ }
23285
24776
  if (options.interpolation !== void 0) {
23286
24777
  this.interpolation(options.interpolation);
23287
24778
  }
@@ -23348,6 +24839,15 @@ var Tween = class {
23348
24839
  this._repeat = times;
23349
24840
  return this;
23350
24841
  }
24842
+ /**
24843
+ * Set a delay before each repeat.
24844
+ * @param amount - delay in milliseconds before each repeat cycle
24845
+ * @returns this instance for object chaining
24846
+ */
24847
+ repeatDelay(amount) {
24848
+ this._repeatDelayTime = amount;
24849
+ return this;
24850
+ }
23351
24851
  /**
23352
24852
  * Allows the tween to bounce back to their original value when finished.
23353
24853
  * To be used together with repeat to create endless loops.
@@ -23471,7 +24971,7 @@ var Tween = class {
23471
24971
  if (this._yoyo) {
23472
24972
  this._reversed = !this._reversed;
23473
24973
  }
23474
- this._startTime = time + this._delayTime;
24974
+ this._startTime = time + (this._repeatDelayTime ?? this._delayTime);
23475
24975
  return true;
23476
24976
  } else {
23477
24977
  this._unsubscribe();
@@ -23764,7 +25264,7 @@ var Text = class extends Renderable {
23764
25264
  * @param {number} [settings.wordWrapWidth] - the maximum length in CSS pixel for a single segment of text
23765
25265
  * @param {(string|string[])} [settings.text=""] - a string, or an array of strings
23766
25266
  * @example
23767
- * let font = new me.Text(0, 0, {font: "Arial", size: 8, fillStyle: this.color});
25267
+ * let font = new Text(0, 0, {font: "Arial", size: 8, fillStyle: this.color});
23768
25268
  */
23769
25269
  constructor(x, y, settings) {
23770
25270
  super(x, y, settings.width || 0, settings.height || 0);
@@ -23825,6 +25325,7 @@ var Text = class extends Renderable {
23825
25325
  this.canvasTexture = new canvasrendertarget_default(2, 2, {
23826
25326
  offscreenCanvas: false
23827
25327
  });
25328
+ this._visibleCharacters = -1;
23828
25329
  this.metrics = new textmetrics_default(this);
23829
25330
  this.setText(settings.text);
23830
25331
  }
@@ -23923,6 +25424,53 @@ var Text = class extends Renderable {
23923
25424
  this.isDirty = true;
23924
25425
  return this;
23925
25426
  }
25427
+ /**
25428
+ * the number of characters to display (use -1 to show all).
25429
+ * Useful for typewriter effects combined with Tween.
25430
+ * @public
25431
+ * @type {number}
25432
+ * @default -1
25433
+ * @see Text#visibleRatio
25434
+ * @example
25435
+ * // typewriter effect
25436
+ * text.visibleCharacters = 0;
25437
+ * new Tween(text).to({ visibleRatio: 1.0 }, { duration: 2000 }).start();
25438
+ */
25439
+ get visibleCharacters() {
25440
+ return this._visibleCharacters;
25441
+ }
25442
+ set visibleCharacters(value) {
25443
+ if (this._visibleCharacters !== value) {
25444
+ this._visibleCharacters = value;
25445
+ this.isDirty = true;
25446
+ }
25447
+ }
25448
+ /**
25449
+ * the ratio of visible characters (0.0 to 1.0).
25450
+ * Setting this automatically updates {@link visibleCharacters}.
25451
+ * @public
25452
+ * @type {number}
25453
+ */
25454
+ get visibleRatio() {
25455
+ if (this._visibleCharacters === -1) {
25456
+ return 1;
25457
+ }
25458
+ const total = this._text.reduce((sum, line) => {
25459
+ return sum + line.length;
25460
+ }, 0);
25461
+ return total > 0 ? this._visibleCharacters / total : 1;
25462
+ }
25463
+ set visibleRatio(value) {
25464
+ const clamped = Math.max(0, Math.min(1, isFinite(value) ? value : 1));
25465
+ if (clamped >= 1) {
25466
+ this.visibleCharacters = -1;
25467
+ } else {
25468
+ const total = this._text.reduce((sum, line) => {
25469
+ return sum + line.length;
25470
+ }, 0);
25471
+ this.visibleCharacters = Math.floor(clamped * total);
25472
+ }
25473
+ }
23926
25474
  /**
23927
25475
  * update the bounding box for this Text, accounting for textAlign and textBaseline.
23928
25476
  * @param {boolean} [absolute=true] - update in absolute coordinates
@@ -23979,6 +25527,16 @@ var Text = class extends Renderable {
23979
25527
  * @param {CanvasRenderer|WebGLRenderer} renderer - Reference to the destination renderer instance
23980
25528
  */
23981
25529
  draw(renderer2) {
25530
+ if (this.isDirty) {
25531
+ this.canvasTexture.invalidate(renderer2);
25532
+ this.canvasTexture.clear();
25533
+ this._drawFont(
25534
+ this.canvasTexture.context,
25535
+ this._text,
25536
+ this.pos.x - this.metrics.x,
25537
+ this.pos.y - this.metrics.y
25538
+ );
25539
+ }
23982
25540
  let x = this.metrics.x;
23983
25541
  let y = this.metrics.y;
23984
25542
  if (renderer2.settings.subPixel === false) {
@@ -23992,8 +25550,16 @@ var Text = class extends Renderable {
23992
25550
  */
23993
25551
  _drawFont(context, text, x, y) {
23994
25552
  setContextStyle(context, this);
25553
+ let remaining = this.visibleCharacters;
23995
25554
  for (let i = 0; i < text.length; i++) {
23996
- const string = text[i].trimEnd();
25555
+ let string = text[i].trimEnd();
25556
+ if (remaining !== -1) {
25557
+ if (remaining <= 0) {
25558
+ break;
25559
+ }
25560
+ string = string.substring(0, remaining);
25561
+ remaining -= string.length;
25562
+ }
23997
25563
  if (this.fillStyle.alpha > 0) {
23998
25564
  context.fillText(string, x, y);
23999
25565
  }
@@ -24173,13 +25739,13 @@ var BitmapText = class extends Renderable {
24173
25739
  * @param {number} [settings.wordWrapWidth] - the maximum length in CSS pixel for a single segment of text
24174
25740
  * @param {(string|string[])} [settings.text] - a string, or an array of strings
24175
25741
  * @example
24176
- * // Use me.loader.preload or me.loader.load to load assets
24177
- * me.loader.preload([
24178
- * { name: "arial", type: "binary" src: "data/font/arial.fnt" },
24179
- * { name: "arial", type: "image" src: "data/font/arial.png" },
25742
+ * // Use loader.preload or loader.load to load assets
25743
+ * loader.preload([
25744
+ * { name: "arial", type: "binary", src: "data/font/arial.fnt" },
25745
+ * { name: "arial", type: "image", src: "data/font/arial.png" },
24180
25746
  * ])
24181
25747
  * // Then create an instance of your bitmap font:
24182
- * let myFont = new me.BitmapText(x, y, {font:"arial", text:"Hello"});
25748
+ * let myFont = new BitmapText(x, y, {font:"arial", text:"Hello"});
24183
25749
  * // two possibilities for using "myFont"
24184
25750
  * // either call the draw function from your Renderable draw function
24185
25751
  * myFont.draw(renderer, "Hello!", 0, 0);
@@ -24214,6 +25780,7 @@ var BitmapText = class extends Renderable {
24214
25780
  } else {
24215
25781
  this.anchorPoint.set(0, 0);
24216
25782
  }
25783
+ this._visibleCharacters = -1;
24217
25784
  this.metrics = new textmetrics_default(this);
24218
25785
  if (typeof settings.size === "number" && settings.size !== 1) {
24219
25786
  this.resize(settings.size);
@@ -24255,6 +25822,55 @@ var BitmapText = class extends Renderable {
24255
25822
  this.updateBounds();
24256
25823
  return this;
24257
25824
  }
25825
+ /**
25826
+ * the number of characters to display (use -1 to show all).
25827
+ * Useful for typewriter effects combined with Tween.
25828
+ * @public
25829
+ * @type {number}
25830
+ * @default -1
25831
+ * @see BitmapText#visibleRatio
25832
+ * @example
25833
+ * // show only the first 5 characters
25834
+ * bitmapText.visibleCharacters = 5;
25835
+ * // typewriter effect
25836
+ * bitmapText.visibleCharacters = 0;
25837
+ * new Tween(bitmapText).to({ visibleRatio: 1.0 }, { duration: 2000 }).start();
25838
+ */
25839
+ get visibleCharacters() {
25840
+ return this._visibleCharacters;
25841
+ }
25842
+ set visibleCharacters(value) {
25843
+ if (this._visibleCharacters !== value) {
25844
+ this._visibleCharacters = value;
25845
+ this.isDirty = true;
25846
+ }
25847
+ }
25848
+ /**
25849
+ * the ratio of visible characters (0.0 to 1.0).
25850
+ * Setting this automatically updates {@link visibleCharacters}.
25851
+ * @public
25852
+ * @type {number}
25853
+ */
25854
+ get visibleRatio() {
25855
+ if (this._visibleCharacters === -1) {
25856
+ return 1;
25857
+ }
25858
+ const total = this._text.reduce((sum, line) => {
25859
+ return sum + line.length;
25860
+ }, 0);
25861
+ return total > 0 ? this._visibleCharacters / total : 1;
25862
+ }
25863
+ set visibleRatio(value) {
25864
+ const clamped = Math.max(0, Math.min(1, isFinite(value) ? value : 1));
25865
+ if (clamped >= 1) {
25866
+ this.visibleCharacters = -1;
25867
+ } else {
25868
+ const total = this._text.reduce((sum, line) => {
25869
+ return sum + line.length;
25870
+ }, 0);
25871
+ this.visibleCharacters = Math.floor(clamped * total);
25872
+ }
25873
+ }
24258
25874
  /**
24259
25875
  * update the bounding box for this Bitmap Text.
24260
25876
  * @param {boolean} [absolute=true] - update the bounds size and position in (world) absolute coordinates
@@ -24357,6 +25973,8 @@ var BitmapText = class extends Renderable {
24357
25973
  default:
24358
25974
  break;
24359
25975
  }
25976
+ let charCount = 0;
25977
+ const maxChars = this.visibleCharacters;
24360
25978
  for (let i = 0; i < this._text.length; i++) {
24361
25979
  x = lX;
24362
25980
  const string = this._text[i].trimEnd();
@@ -24373,6 +25991,9 @@ var BitmapText = class extends Renderable {
24373
25991
  }
24374
25992
  let lastGlyph = null;
24375
25993
  for (let c = 0, len = string.length; c < len; c++) {
25994
+ if (maxChars !== -1 && charCount >= maxChars) {
25995
+ return;
25996
+ }
24376
25997
  const ch = string.charCodeAt(c);
24377
25998
  const glyph = this.fontData.glyphs[ch];
24378
25999
  if (typeof glyph !== "undefined") {
@@ -24401,6 +26022,7 @@ var BitmapText = class extends Renderable {
24401
26022
  "BitmapText: no defined Glyph in for " + String.fromCharCode(ch)
24402
26023
  );
24403
26024
  }
26025
+ charCount++;
24404
26026
  }
24405
26027
  y += stringHeight;
24406
26028
  }
@@ -24477,7 +26099,9 @@ function registerTiledObjectFactory(type, factory) {
24477
26099
  throw new Error("invalid factory function for " + type);
24478
26100
  }
24479
26101
  if (typeof factories.get(type) !== "undefined") {
24480
- warning("overriding Tiled object factory for " + type + " type");
26102
+ console.warn(
26103
+ "melonJS: overriding Tiled object factory for " + type + " type"
26104
+ );
24481
26105
  }
24482
26106
  factories.set(type, factory);
24483
26107
  }
@@ -24568,18 +26192,22 @@ var Particle = class extends Renderable {
24568
26192
  wind;
24569
26193
  followTrajectory;
24570
26194
  onlyInViewport;
26195
+ accurateBounds;
24571
26196
  _deltaInv;
26197
+ _halfW;
26198
+ _halfH;
24572
26199
  _angle;
24573
26200
  alive;
24574
26201
  /**
24575
26202
  * @param emitter - the particle emitter
24576
26203
  */
24577
26204
  constructor(emitter) {
26205
+ const image = emitter.settings.image;
24578
26206
  super(
24579
26207
  emitter.getRandomPointX(),
24580
26208
  emitter.getRandomPointY(),
24581
- emitter.settings.image.width,
24582
- emitter.settings.image.height
26209
+ image.width,
26210
+ image.height
24583
26211
  );
24584
26212
  this.vel = vector2dPool.get();
24585
26213
  this.onResetEvent(emitter, true);
@@ -24588,13 +26216,17 @@ var Particle = class extends Renderable {
24588
26216
  * @ignore
24589
26217
  */
24590
26218
  onResetEvent(emitter, newInstance = false) {
26219
+ const image = emitter.settings.image;
24591
26220
  if (!newInstance) {
24592
26221
  this.pos.set(emitter.getRandomPointX(), emitter.getRandomPointY());
24593
- this.resize(emitter.settings.image.width, emitter.settings.image.height);
26222
+ this.resize(image.width, image.height);
24594
26223
  this.currentTransform.identity();
24595
26224
  }
24596
- this.image = emitter.settings.image;
26225
+ this.image = image;
26226
+ this._halfW = this.width / 2;
26227
+ this._halfH = this.height / 2;
24597
26228
  this.alwaysUpdate = true;
26229
+ this.anchorPoint.set(0, 0);
24598
26230
  if (typeof emitter.settings.tint === "string") {
24599
26231
  this.tint.parseCSS(emitter.settings.tint);
24600
26232
  }
@@ -24602,21 +26234,16 @@ var Particle = class extends Renderable {
24602
26234
  if (emitter.settings.blendMode !== "normal") {
24603
26235
  this.blendMode = emitter.settings.blendMode;
24604
26236
  }
24605
- const angle = emitter.settings.angle + (emitter.settings.angleVariation > 0 ? (randomFloat(0, 2) - 1) * emitter.settings.angleVariation : 0);
24606
- const speed = emitter.settings.speed + (emitter.settings.speedVariation > 0 ? (randomFloat(0, 2) - 1) * emitter.settings.speedVariation : 0);
26237
+ const angle = emitter.settings.angle + (Math.random() * 2 - 1) * emitter.settings.angleVariation;
26238
+ const speed = emitter.settings.speed + (Math.random() * 2 - 1) * emitter.settings.speedVariation;
24607
26239
  this.vel.set(speed * Math.cos(angle), -speed * Math.sin(angle));
24608
26240
  this.life = randomFloat(emitter.settings.minLife, emitter.settings.maxLife);
24609
26241
  this.startLife = this.life;
24610
- this.startScale = clamp(
24611
- randomFloat(
24612
- emitter.settings.minStartScale,
24613
- emitter.settings.maxStartScale
24614
- ),
26242
+ this.startScale = randomFloat(
24615
26243
  emitter.settings.minStartScale,
24616
26244
  emitter.settings.maxStartScale
24617
26245
  );
24618
- this.endScale = clamp(
24619
- randomFloat(emitter.settings.minEndScale, emitter.settings.maxEndScale),
26246
+ this.endScale = randomFloat(
24620
26247
  emitter.settings.minEndScale,
24621
26248
  emitter.settings.maxEndScale
24622
26249
  );
@@ -24624,7 +26251,8 @@ var Particle = class extends Renderable {
24624
26251
  this.wind = emitter.settings.wind;
24625
26252
  this.followTrajectory = emitter.settings.followTrajectory;
24626
26253
  this.onlyInViewport = emitter.settings.onlyInViewport;
24627
- this._deltaInv = timer_default.maxfps / 1e3;
26254
+ this.accurateBounds = emitter.settings.accurateBounds;
26255
+ this._deltaInv = emitter._deltaInv;
24628
26256
  if (!emitter.settings.followTrajectory) {
24629
26257
  this._angle = randomFloat(
24630
26258
  emitter.settings.minRotation,
@@ -24664,24 +26292,33 @@ var Particle = class extends Renderable {
24664
26292
  const angle = this.followTrajectory ? Math.atan2(this.vel.y, this.vel.x) : this._angle;
24665
26293
  this.pos.x += this.vel.x * skew;
24666
26294
  this.pos.y += this.vel.y * skew;
26295
+ const halfW = this._halfW;
26296
+ const halfH = this._halfH;
26297
+ const cos = Math.cos(angle);
26298
+ const sin = Math.sin(angle);
26299
+ const sCos = scale2 * cos;
26300
+ const sSin = scale2 * sin;
24667
26301
  this.currentTransform.setTransform(
24668
- scale2,
24669
- 0,
26302
+ sCos,
26303
+ sSin,
24670
26304
  0,
24671
26305
  0,
24672
- 0,
24673
- scale2,
26306
+ -sSin,
26307
+ sCos,
24674
26308
  0,
24675
26309
  0,
24676
26310
  0,
24677
26311
  0,
24678
26312
  1,
24679
26313
  0,
24680
- this.pos.x,
24681
- this.pos.y,
26314
+ this.pos.x - scale2 * (halfW * cos - halfH * sin),
26315
+ this.pos.y - scale2 * (halfW * sin + halfH * cos),
24682
26316
  0,
24683
26317
  1
24684
- ).rotate(angle);
26318
+ );
26319
+ if (this.accurateBounds) {
26320
+ this.updateBounds();
26321
+ }
24685
26322
  this.isDirty = this.inViewport || !this.onlyInViewport;
24686
26323
  return super.update(dt);
24687
26324
  }
@@ -24691,7 +26328,7 @@ var Particle = class extends Renderable {
24691
26328
  draw(renderer2) {
24692
26329
  const w = this.width;
24693
26330
  const h = this.height;
24694
- renderer2.drawImage(this.image, 0, 0, w, h, -w / 2, -h / 2, w, h);
26331
+ renderer2.drawImage(this.image, 0, 0, w, h, 0, 0, w, h);
24695
26332
  }
24696
26333
  };
24697
26334
  var particlePool = createPool(
@@ -25093,13 +26730,27 @@ var ImageLayer = class extends Sprite {
25093
26730
  this.isDirty = true;
25094
26731
  }
25095
26732
  /**
25096
- * override the default predraw function
25097
- * as repeat and anchor are managed directly in the draw method
26733
+ * Override the default preDraw to skip the base class's anchor offset
26734
+ * translation and `autoTransform` application `ImageLayer.draw()`
26735
+ * computes its own world-space position from the viewport, the parallax
26736
+ * `ratio`, anchor, and zoom, so applying anchor/transform here would
26737
+ * double-translate the layer.
26738
+ *
26739
+ * Coordinate-sensitive setup (flip, stencil mask) is intentionally
26740
+ * deferred to `draw()` where it runs *after* the per-camera zoom
26741
+ * translate/scale — that way it stays correctly aligned at any
26742
+ * `viewport.zoom` and across multiple cameras. Coordinate-independent
26743
+ * setup (alpha, post-effects, tint, blend) stays here. The inherited
26744
+ * `Renderable.postDraw` cleans up symmetrically (`clearTint` /
26745
+ * `clearMask` / `endPostEffect` / `restore`).
25098
26746
  * @ignore
25099
26747
  */
25100
26748
  preDraw(renderer2) {
25101
26749
  renderer2.save();
25102
26750
  renderer2.setGlobalAlpha(renderer2.globalAlpha() * this.getOpacity());
26751
+ if (!this._postEffectManaged) {
26752
+ renderer2.beginPostEffect(this);
26753
+ }
25103
26754
  renderer2.setTint(this.tint);
25104
26755
  if (this.blendMode !== renderer2.getBlendMode()) {
25105
26756
  renderer2.setBlendMode(this.blendMode);
@@ -25137,6 +26788,16 @@ var ImageLayer = class extends Sprite {
25137
26788
  }
25138
26789
  renderer2.translate(x * vZoom, y * vZoom);
25139
26790
  renderer2.scale(vZoom, vZoom);
26791
+ if (this._flip.x || this._flip.y) {
26792
+ const px = viewport.width;
26793
+ const py = viewport.height;
26794
+ renderer2.translate(px, py);
26795
+ renderer2.scale(this._flip.x ? -1 : 1, this._flip.y ? -1 : 1);
26796
+ renderer2.translate(-px, -py);
26797
+ }
26798
+ if (this.mask) {
26799
+ renderer2.setMask(this.mask);
26800
+ }
25140
26801
  renderer2.drawPattern(
25141
26802
  this._pattern,
25142
26803
  0,
@@ -25559,8 +27220,10 @@ var Trigger = class extends Renderable {
25559
27220
  * @param {number} [settings.width] - width of the trigger area
25560
27221
  * @param {number} [settings.height] - height of the trigger area
25561
27222
  * @param {Rect[]|Polygon[]|Line[]|Ellipse[]} [settings.shapes] - collision shape(s) that will trigger the event
25562
- * @param {string} [settings.duration] - Fade duration (in ms)
25563
- * @param {string|Color} [settings.color] - Fade color
27223
+ * @param {number} [settings.duration] - Transition duration (in ms)
27224
+ * @param {string|Color} [settings.color] - Transition color (also accepts legacy `fade` property)
27225
+ * @param {string} [settings.transition="fade"] - Transition type: "fade" for a color fade, "mask" for a shape-based mask transition
27226
+ * @param {Ellipse|Polygon} [settings.shape] - Mask shape for "mask" transition type (e.g. an Ellipse for iris, a Polygon for diamond/star)
25564
27227
  * @param {string} [settings.event="level"] - the type of event to trigger (only "level" supported for now)
25565
27228
  * @param {string} [settings.to] - level to load if level trigger
25566
27229
  * @param {string|Container} [settings.container] - Target container. See {@link level.load}
@@ -25568,41 +27231,64 @@ var Trigger = class extends Renderable {
25568
27231
  * @param {boolean} [settings.flatten] - Flatten all objects into the target container. See {@link level.load}
25569
27232
  * @param {boolean} [settings.setViewportBounds] - Resize the viewport to match the level. See {@link level.load}
25570
27233
  * @example
25571
- * world.addChild(new me.Trigger(
25572
- * x, y, {
25573
- * shapes: [new me.Rect(0, 0, 100, 100)],
25574
- * "duration" : 250,
25575
- * "color" : "#000",
25576
- * "to" : "mymap2"
25577
- * }
25578
- * ));
27234
+ * // fade transition (default)
27235
+ * world.addChild(new Trigger(x, y, {
27236
+ * shapes: [new Rect(0, 0, 100, 100)],
27237
+ * color: "#000",
27238
+ * duration: 250,
27239
+ * to: "mymap2",
27240
+ * }));
27241
+ * @example
27242
+ * // mask transition with iris (ellipse) shape
27243
+ * world.addChild(new Trigger(x, y, {
27244
+ * shapes: [new Rect(0, 0, 100, 100)],
27245
+ * transition: "mask",
27246
+ * shape: new Ellipse(0, 0, 1, 1),
27247
+ * color: "#000",
27248
+ * duration: 500,
27249
+ * to: "mymap2",
27250
+ * }));
27251
+ * @example
27252
+ * // mask transition with diamond polygon
27253
+ * world.addChild(new Trigger(x, y, {
27254
+ * shapes: [new Rect(0, 0, 100, 100)],
27255
+ * transition: "mask",
27256
+ * shape: new Polygon(0, 0, [
27257
+ * { x: 0, y: -1 }, { x: 1, y: 0 },
27258
+ * { x: 0, y: 1 }, { x: -1, y: 0 },
27259
+ * ]),
27260
+ * color: "#000",
27261
+ * duration: 400,
27262
+ * to: "mymap2",
27263
+ * }));
25579
27264
  */
25580
27265
  constructor(x, y, settings) {
25581
27266
  super(x, y, settings.width || 0, settings.height || 0);
25582
27267
  this.anchorPoint.set(0, 0);
25583
- this.fade = settings.fade;
27268
+ this.color = settings.color || settings.fade;
25584
27269
  this.duration = settings.duration;
27270
+ this.transition = settings.transition || "fade";
27271
+ this.transitionShape = settings.shape;
25585
27272
  this.fading = false;
25586
27273
  this.name = "Trigger";
25587
27274
  this.type = settings.type;
25588
27275
  this.id = settings.id;
25589
27276
  this.gotolevel = settings.to;
25590
27277
  this.triggerSettings = {
25591
- // the default (and only for now) action
25592
27278
  event: "level"
25593
27279
  };
25594
- [
27280
+ for (const property of [
25595
27281
  "type",
25596
27282
  "container",
25597
27283
  "onLoaded",
25598
27284
  "flatten",
25599
27285
  "setViewportBounds",
25600
27286
  "to"
25601
- ].forEach((property) => {
27287
+ ]) {
25602
27288
  if (typeof settings[property] !== "undefined") {
25603
27289
  this.triggerSettings[property] = settings[property];
25604
27290
  }
25605
- });
27291
+ }
25606
27292
  let shape = settings.shapes;
25607
27293
  if (typeof shape === "undefined") {
25608
27294
  shape = polygonPool.get(0, 0, [
@@ -25630,28 +27316,73 @@ var Trigger = class extends Renderable {
25630
27316
  return this.triggerSettings;
25631
27317
  }
25632
27318
  /**
25633
- * @ignore
25634
- */
25635
- onFadeComplete() {
25636
- const world = this.ancestor.getRootAncestor();
25637
- level.load(this.gotolevel, this.getTriggerSettings());
25638
- world.app.viewport.fadeOut(this.fade, this.duration);
25639
- }
25640
- /**
25641
- * trigger this event
27319
+ * Trigger this event. Override in subclasses to customize behavior.
25642
27320
  * @protected
25643
27321
  */
25644
27322
  triggerEvent() {
25645
27323
  const triggerSettings = this.getTriggerSettings();
25646
27324
  const world = this.ancestor.getRootAncestor();
27325
+ const app = world.app;
27326
+ const viewport = app.viewport;
25647
27327
  if (triggerSettings.event === "level") {
25648
27328
  this.gotolevel = triggerSettings.to;
25649
- if (this.fade && this.duration) {
27329
+ if (this.color && this.duration) {
25650
27330
  if (!this.fading) {
25651
27331
  this.fading = true;
25652
- world.app.viewport.fadeIn(this.fade, this.duration, () => {
25653
- return this.onFadeComplete();
25654
- });
27332
+ const gotolevel = this.gotolevel;
27333
+ const settings = this.getTriggerSettings();
27334
+ const color = this.color;
27335
+ const duration = this.duration;
27336
+ const useMask = this.transition === "mask" && this.transitionShape;
27337
+ const shape = this.transitionShape;
27338
+ const userOnLoaded = settings.onLoaded;
27339
+ settings.onLoaded = function(levelId) {
27340
+ const vp = app.viewport;
27341
+ if (useMask) {
27342
+ vp.addCameraEffect(
27343
+ new MaskEffect(vp, {
27344
+ shape,
27345
+ color,
27346
+ duration,
27347
+ direction: "reveal"
27348
+ })
27349
+ );
27350
+ } else {
27351
+ vp.addCameraEffect(
27352
+ new FadeEffect(vp, {
27353
+ color,
27354
+ duration,
27355
+ direction: "out"
27356
+ })
27357
+ );
27358
+ }
27359
+ if (typeof userOnLoaded === "function") {
27360
+ userOnLoaded.call(this, levelId);
27361
+ }
27362
+ };
27363
+ const onComplete = () => {
27364
+ level.load(gotolevel, settings);
27365
+ };
27366
+ if (useMask) {
27367
+ viewport.addCameraEffect(
27368
+ new MaskEffect(viewport, {
27369
+ shape,
27370
+ color,
27371
+ duration,
27372
+ direction: "hide",
27373
+ onComplete
27374
+ })
27375
+ );
27376
+ } else {
27377
+ viewport.addCameraEffect(
27378
+ new FadeEffect(viewport, {
27379
+ color,
27380
+ duration,
27381
+ direction: "in",
27382
+ onComplete
27383
+ })
27384
+ );
27385
+ }
25655
27386
  }
25656
27387
  } else {
25657
27388
  level.load(this.gotolevel, triggerSettings);
@@ -25668,14 +27399,14 @@ var Trigger = class extends Renderable {
25668
27399
  */
25669
27400
  onCollision() {
25670
27401
  if (this.name === "Trigger") {
25671
- this.triggerEvent.apply(this);
27402
+ this.triggerEvent();
25672
27403
  }
25673
27404
  return false;
25674
27405
  }
25675
27406
  };
25676
27407
 
25677
27408
  // src/version.ts
25678
- var version = "19.0.0";
27409
+ var version = "19.2.0";
25679
27410
 
25680
27411
  // src/system/bootstrap.ts
25681
27412
  var initialized = false;
@@ -27539,7 +29270,7 @@ var QuadTree = class {
27539
29270
  * @param {Container} container - group of objects to be added
27540
29271
  */
27541
29272
  insertContainer(container) {
27542
- const children = container.children;
29273
+ const children = container.getChildren();
27543
29274
  const childrenLength = children.length;
27544
29275
  for (let i = childrenLength, child; i--, child = children[i]; ) {
27545
29276
  if (child.isKinematic !== true) {
@@ -27815,6 +29546,327 @@ var World = class extends Container {
27815
29546
  }
27816
29547
  };
27817
29548
 
29549
+ // src/video/rendertarget/render_target_pool.js
29550
+ var RenderTargetPool = class {
29551
+ /**
29552
+ * @param {function(number, number): RenderTarget} factory - creates a RenderTarget with the given width and height
29553
+ */
29554
+ constructor(factory) {
29555
+ this._factory = factory;
29556
+ this._pool = [];
29557
+ this._activeBase = -1;
29558
+ this._previousBase = -1;
29559
+ }
29560
+ /**
29561
+ * Get or create a render target at the given pool index, resized to the given dimensions.
29562
+ * @param {number} index - pool index
29563
+ * @param {number} width - desired width in pixels
29564
+ * @param {number} height - desired height in pixels
29565
+ * @returns {RenderTarget} the render target
29566
+ */
29567
+ get(index, width, height) {
29568
+ if (!this._pool[index]) {
29569
+ this._pool[index] = this._factory(width, height);
29570
+ } else {
29571
+ this._pool[index].resize(width, height);
29572
+ }
29573
+ return this._pool[index];
29574
+ }
29575
+ /**
29576
+ * Prepare render targets for a post-effect pass.
29577
+ * Allocates/resizes the capture target and optionally the ping-pong target.
29578
+ * @param {boolean} isCamera - true for camera effects (indices 0+1), false for sprite (indices 2+3)
29579
+ * @param {number} effectCount - number of enabled effects
29580
+ * @param {number} width - target width in pixels
29581
+ * @param {number} height - target height in pixels
29582
+ * @returns {RenderTarget} the capture target (ready to bind)
29583
+ */
29584
+ begin(isCamera, effectCount, width, height) {
29585
+ const newBase = isCamera ? 0 : 2;
29586
+ if (!isCamera && this._activeBase === newBase) {
29587
+ return this.get(this._activeBase, width, height);
29588
+ }
29589
+ this._previousBase = this._activeBase;
29590
+ this._activeBase = newBase;
29591
+ const rt = this.get(this._activeBase, width, height);
29592
+ if (effectCount > 1) {
29593
+ this.get(this._activeBase + 1, width, height);
29594
+ }
29595
+ return rt;
29596
+ }
29597
+ /**
29598
+ * Get the capture render target for the current active pass.
29599
+ * @returns {RenderTarget|undefined} the capture target, or undefined if no active pass
29600
+ */
29601
+ getCaptureTarget() {
29602
+ if (this._activeBase < 0) {
29603
+ return void 0;
29604
+ }
29605
+ return this._pool[this._activeBase];
29606
+ }
29607
+ /**
29608
+ * Get the ping-pong render target for the current active pass.
29609
+ * @returns {RenderTarget|undefined} the ping-pong target, or undefined if no active pass
29610
+ */
29611
+ getPingPongTarget() {
29612
+ if (this._activeBase < 0) {
29613
+ return void 0;
29614
+ }
29615
+ return this._pool[this._activeBase + 1];
29616
+ }
29617
+ /**
29618
+ * End the current pass and restore the previous active base.
29619
+ * Returns the parent render target to rebind (or null for screen).
29620
+ * @returns {RenderTarget|null} the parent target, or null if returning to screen
29621
+ */
29622
+ end() {
29623
+ this._activeBase = this._previousBase;
29624
+ this._previousBase = -1;
29625
+ if (this._activeBase >= 0 && this._pool[this._activeBase]) {
29626
+ return this._pool[this._activeBase];
29627
+ }
29628
+ return null;
29629
+ }
29630
+ /**
29631
+ * Resize all existing render targets in the pool to the given dimensions.
29632
+ * @param {number} width - new width in pixels
29633
+ * @param {number} height - new height in pixels
29634
+ */
29635
+ resizeAll(width, height) {
29636
+ for (const rt of this._pool) {
29637
+ if (rt) {
29638
+ rt.resize(width, height);
29639
+ }
29640
+ }
29641
+ }
29642
+ /**
29643
+ * Destroy all render targets and clear the pool.
29644
+ */
29645
+ destroy() {
29646
+ for (const rt of this._pool) {
29647
+ if (rt) {
29648
+ rt.destroy();
29649
+ }
29650
+ }
29651
+ this._pool.length = 0;
29652
+ this._activeBase = -1;
29653
+ this._previousBase = -1;
29654
+ }
29655
+ };
29656
+
29657
+ // src/video/rendertarget/webglrendertarget.js
29658
+ var DEPTH_STENCIL = 34041;
29659
+ var DEPTH_STENCIL_ATTACHMENT = 33306;
29660
+ function attachDepthStencil(gl, framebuffer, renderbuffer, width, height) {
29661
+ const depthStencil = gl.DEPTH_STENCIL ?? DEPTH_STENCIL;
29662
+ const depthStencilAttachment = gl.DEPTH_STENCIL_ATTACHMENT ?? DEPTH_STENCIL_ATTACHMENT;
29663
+ gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
29664
+ gl.renderbufferStorage(gl.RENDERBUFFER, depthStencil, width, height);
29665
+ gl.framebufferRenderbuffer(
29666
+ gl.FRAMEBUFFER,
29667
+ depthStencilAttachment,
29668
+ gl.RENDERBUFFER,
29669
+ renderbuffer
29670
+ );
29671
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
29672
+ return { hasStencil: true, isComplete: true };
29673
+ }
29674
+ gl.framebufferRenderbuffer(
29675
+ gl.FRAMEBUFFER,
29676
+ depthStencilAttachment,
29677
+ gl.RENDERBUFFER,
29678
+ null
29679
+ );
29680
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
29681
+ gl.framebufferRenderbuffer(
29682
+ gl.FRAMEBUFFER,
29683
+ gl.DEPTH_ATTACHMENT,
29684
+ gl.RENDERBUFFER,
29685
+ renderbuffer
29686
+ );
29687
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
29688
+ return {
29689
+ hasStencil: false,
29690
+ isComplete: status === gl.FRAMEBUFFER_COMPLETE
29691
+ };
29692
+ }
29693
+ var WebGLRenderTarget = class extends RenderTarget {
29694
+ /**
29695
+ * @param {WebGLRenderingContext|WebGL2RenderingContext} gl - the WebGL context
29696
+ * @param {number} width - initial width in pixels
29697
+ * @param {number} height - initial height in pixels
29698
+ */
29699
+ constructor(gl, width, height) {
29700
+ super();
29701
+ this.gl = gl;
29702
+ this.width = width;
29703
+ this.height = height;
29704
+ this.framebuffer = gl.createFramebuffer();
29705
+ const prevUnit = gl.getParameter(gl.ACTIVE_TEXTURE);
29706
+ this.texture = gl.createTexture();
29707
+ gl.activeTexture(gl.TEXTURE0);
29708
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
29709
+ gl.texImage2D(
29710
+ gl.TEXTURE_2D,
29711
+ 0,
29712
+ gl.RGBA,
29713
+ width,
29714
+ height,
29715
+ 0,
29716
+ gl.RGBA,
29717
+ gl.UNSIGNED_BYTE,
29718
+ null
29719
+ );
29720
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
29721
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
29722
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
29723
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
29724
+ this.depthStencilBuffer = gl.createRenderbuffer();
29725
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
29726
+ gl.framebufferTexture2D(
29727
+ gl.FRAMEBUFFER,
29728
+ gl.COLOR_ATTACHMENT0,
29729
+ gl.TEXTURE_2D,
29730
+ this.texture,
29731
+ 0
29732
+ );
29733
+ this._applyDepthStencil(width, height);
29734
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
29735
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
29736
+ gl.bindTexture(gl.TEXTURE_2D, null);
29737
+ gl.activeTexture(prevUnit);
29738
+ }
29739
+ /**
29740
+ * (Re)attach the depth/stencil renderbuffer at the given size and
29741
+ * update `_hasStencil` based on the resulting framebuffer status.
29742
+ * Falls back to depth-only when packed depth+stencil fails; warns
29743
+ * once if even depth-only is incomplete.
29744
+ * @ignore
29745
+ */
29746
+ _applyDepthStencil(width, height) {
29747
+ const result = attachDepthStencil(
29748
+ this.gl,
29749
+ this.framebuffer,
29750
+ this.depthStencilBuffer,
29751
+ width,
29752
+ height
29753
+ );
29754
+ this._hasStencil = result.hasStencil;
29755
+ if (!result.isComplete) {
29756
+ console.warn(
29757
+ "WebGLRenderTarget: framebuffer incomplete after depth-only fallback \u2014 rendering into this target may fail"
29758
+ );
29759
+ } else if (!result.hasStencil) {
29760
+ console.warn(
29761
+ "WebGLRenderTarget: depth+stencil attachment failed; using depth-only \u2014 stencil masking disabled for this target"
29762
+ );
29763
+ }
29764
+ }
29765
+ /**
29766
+ * Bind this FBO as the active render target.
29767
+ * All subsequent draw calls will render into this FBO's texture.
29768
+ */
29769
+ bind() {
29770
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
29771
+ }
29772
+ /**
29773
+ * Unbind this FBO and restore rendering to the screen.
29774
+ */
29775
+ unbind() {
29776
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
29777
+ }
29778
+ /**
29779
+ * Resize the FBO texture and renderbuffer.
29780
+ * @param {number} width - new width in pixels
29781
+ * @param {number} height - new height in pixels
29782
+ */
29783
+ resize(width, height) {
29784
+ if (this.width === width && this.height === height) {
29785
+ return;
29786
+ }
29787
+ const gl = this.gl;
29788
+ this.width = width;
29789
+ this.height = height;
29790
+ const prevUnit = gl.getParameter(gl.ACTIVE_TEXTURE);
29791
+ gl.activeTexture(gl.TEXTURE0);
29792
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
29793
+ gl.texImage2D(
29794
+ gl.TEXTURE_2D,
29795
+ 0,
29796
+ gl.RGBA,
29797
+ width,
29798
+ height,
29799
+ 0,
29800
+ gl.RGBA,
29801
+ gl.UNSIGNED_BYTE,
29802
+ null
29803
+ );
29804
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
29805
+ this._applyDepthStencil(width, height);
29806
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
29807
+ gl.bindTexture(gl.TEXTURE_2D, null);
29808
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
29809
+ gl.activeTexture(prevUnit);
29810
+ }
29811
+ /**
29812
+ * Clear the FBO contents to transparent black.
29813
+ */
29814
+ clear() {
29815
+ const gl = this.gl;
29816
+ this.bind();
29817
+ gl.clearColor(0, 0, 0, 0);
29818
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
29819
+ this.unbind();
29820
+ }
29821
+ /**
29822
+ * Returns an ImageData object representing the pixel contents of this render target.
29823
+ * @param {number} [x=0] - x coordinate of the top-left corner
29824
+ * @param {number} [y=0] - y coordinate of the top-left corner
29825
+ * @param {number} [width=this.width] - width of the area to read
29826
+ * @param {number} [height=this.height] - height of the area to read
29827
+ * @returns {ImageData} the pixel data
29828
+ */
29829
+ getImageData(x = 0, y = 0, width = this.width, height = this.height) {
29830
+ const gl = this.gl;
29831
+ x = Math.max(0, Math.min(Math.floor(x), this.width - 1));
29832
+ y = Math.max(0, Math.min(Math.floor(y), this.height - 1));
29833
+ width = Math.max(1, Math.min(width, this.width - x));
29834
+ height = Math.max(1, Math.min(height, this.height - y));
29835
+ const pixels = new Uint8ClampedArray(width * height * 4);
29836
+ this.bind();
29837
+ gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
29838
+ this.unbind();
29839
+ const rowSize = width * 4;
29840
+ const temp = new Uint8ClampedArray(rowSize);
29841
+ for (let i = 0; i < Math.floor(height / 2); i++) {
29842
+ const topOffset = i * rowSize;
29843
+ const bottomOffset = (height - 1 - i) * rowSize;
29844
+ temp.set(pixels.subarray(topOffset, topOffset + rowSize));
29845
+ pixels.copyWithin(topOffset, bottomOffset, bottomOffset + rowSize);
29846
+ pixels.set(temp, bottomOffset);
29847
+ }
29848
+ return new ImageData(pixels, width, height);
29849
+ }
29850
+ /**
29851
+ * Release all GPU resources.
29852
+ */
29853
+ destroy() {
29854
+ const gl = this.gl;
29855
+ if (this.framebuffer) {
29856
+ gl.deleteFramebuffer(this.framebuffer);
29857
+ this.framebuffer = null;
29858
+ }
29859
+ if (this.texture) {
29860
+ gl.deleteTexture(this.texture);
29861
+ this.texture = null;
29862
+ }
29863
+ if (this.depthStencilBuffer) {
29864
+ gl.deleteRenderbuffer(this.depthStencilBuffer);
29865
+ this.depthStencilBuffer = null;
29866
+ }
29867
+ }
29868
+ };
29869
+
27818
29870
  // src/video/utils/dash.js
27819
29871
  var _dashPathResult = [];
27820
29872
  function dashPath(pts, pattern) {
@@ -27962,96 +30014,7 @@ var mesh_default = "uniform sampler2D uSampler;\nvarying vec4 vColor;\nvarying v
27962
30014
  // src/video/webgl/shaders/mesh.vert
27963
30015
  var mesh_default2 = "// Current vertex point (3D position for mesh rendering)\nattribute vec3 aVertex;\nattribute vec2 aRegion;\nattribute vec4 aColor;\n\n// Projection matrix\nuniform mat4 uProjectionMatrix;\n\nvarying vec2 vRegion;\nvarying vec4 vColor;\n\nvoid main(void) {\n // Transform the vertex position by the projection matrix\n gl_Position = uProjectionMatrix * vec4(aVertex, 1.0);\n // Pass the remaining attributes to the fragment shader\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n}\n";
27964
30016
 
27965
- // src/video/webgl/buffer/index.js
27966
- var IndexBuffer = class {
27967
- /**
27968
- * @param {WebGLRenderingContext|WebGL2RenderingContext} gl - the WebGL context
27969
- * @param {number} maxIndices - maximum number of indices this buffer can hold
27970
- * @param {boolean} [useUint32=false] - use Uint32 indices (WebGL2) instead of Uint16 (WebGL1)
27971
- * @param {boolean} [dynamic=false] - if true, use STREAM_DRAW for frequent updates; if false, use STATIC_DRAW
27972
- */
27973
- constructor(gl, maxIndices, useUint32 = false, dynamic = false) {
27974
- this.gl = gl;
27975
- this.dynamic = dynamic;
27976
- if (useUint32) {
27977
- this.type = gl.UNSIGNED_INT;
27978
- this.data = new Uint32Array(maxIndices);
27979
- } else {
27980
- this.type = gl.UNSIGNED_SHORT;
27981
- this.data = new Uint16Array(maxIndices);
27982
- }
27983
- this.length = 0;
27984
- this.buffer = gl.createBuffer();
27985
- }
27986
- /**
27987
- * Fill the buffer with a repeating quad index pattern [0,1,2, 2,1,3, 4,5,6, ...]
27988
- * and upload as a static buffer.
27989
- * @param {number} maxQuads - number of quads to generate indices for
27990
- */
27991
- fillQuadPattern(maxQuads) {
27992
- for (let i = 0, vertex = 0; i < maxQuads * 6; i += 6, vertex += 4) {
27993
- this.data[i] = vertex;
27994
- this.data[i + 1] = vertex + 1;
27995
- this.data[i + 2] = vertex + 2;
27996
- this.data[i + 3] = vertex + 2;
27997
- this.data[i + 4] = vertex + 1;
27998
- this.data[i + 5] = vertex + 3;
27999
- }
28000
- this.length = maxQuads * 6;
28001
- this.bind();
28002
- this.gl.bufferData(
28003
- this.gl.ELEMENT_ARRAY_BUFFER,
28004
- this.data,
28005
- this.gl.STATIC_DRAW
28006
- );
28007
- }
28008
- /**
28009
- * Reset the index count (for dynamic buffers)
28010
- */
28011
- clear() {
28012
- this.length = 0;
28013
- }
28014
- /**
28015
- * Add indices to the buffer, rebased by the given vertex offset
28016
- * @param {number[]} indices - source indices to add
28017
- * @param {number} vertexOffset - value to add to each index (vertex count at time of insertion)
28018
- */
28019
- add(indices, vertexOffset) {
28020
- for (let i = 0; i < indices.length; i++) {
28021
- this.data[this.length + i] = indices[i] + vertexOffset;
28022
- }
28023
- this.length += indices.length;
28024
- }
28025
- /**
28026
- * Add pre-computed absolute indices to the buffer (no rebasing)
28027
- * @param {number[]} indices - absolute index values to add
28028
- */
28029
- addRaw(indices) {
28030
- for (let i = 0; i < indices.length; i++) {
28031
- this.data[this.length + i] = indices[i];
28032
- }
28033
- this.length += indices.length;
28034
- }
28035
- /**
28036
- * Upload the current index data to the GPU (for dynamic buffers)
28037
- */
28038
- upload() {
28039
- this.bind();
28040
- this.gl.bufferData(
28041
- this.gl.ELEMENT_ARRAY_BUFFER,
28042
- this.data.subarray(0, this.length),
28043
- this.gl.STREAM_DRAW
28044
- );
28045
- }
28046
- /**
28047
- * bind this index buffer
28048
- */
28049
- bind() {
28050
- this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffer);
28051
- }
28052
- };
28053
-
28054
- // src/video/webgl/buffer/vertex.js
30017
+ // src/video/buffer/vertex.js
28055
30018
  var VertexArrayBuffer = class {
28056
30019
  constructor(vertexSize, maxVertex) {
28057
30020
  this.vertexSize = vertexSize;
@@ -28078,16 +30041,25 @@ var VertexArrayBuffer = class {
28078
30041
  return this.vertexCount + vertex >= this.maxVertex;
28079
30042
  }
28080
30043
  /**
28081
- * push a new vertex to the buffer (quad format: x, y, u, v, tint)
30044
+ * push a new vertex to the buffer
30045
+ * @param {number} x - x position
30046
+ * @param {number} y - y position
30047
+ * @param {number} u - texture U coordinate
30048
+ * @param {number} v - texture V coordinate
30049
+ * @param {number} tint - tint color in UINT32 (argb) format
30050
+ * @param {number} [textureId] - texture unit index for multi-texture batching
28082
30051
  * @ignore
28083
30052
  */
28084
- push(x, y, u, v, tint) {
30053
+ push(x, y, u, v, tint, textureId) {
28085
30054
  const offset = this.vertexCount * this.vertexSize;
28086
30055
  this.bufferF32[offset] = x;
28087
30056
  this.bufferF32[offset + 1] = y;
28088
30057
  this.bufferF32[offset + 2] = u;
28089
30058
  this.bufferF32[offset + 3] = v;
28090
30059
  this.bufferU32[offset + 4] = tint;
30060
+ if (this.vertexSize > 5) {
30061
+ this.bufferF32[offset + 5] = textureId || 0;
30062
+ }
28091
30063
  this.vertexCount++;
28092
30064
  return this;
28093
30065
  }
@@ -28145,258 +30117,102 @@ var VertexArrayBuffer = class {
28145
30117
  }
28146
30118
  };
28147
30119
 
28148
- // src/video/webgl/utils/attributes.js
28149
- function extractAttributes(gl, shader) {
28150
- const attributes = {};
28151
- const attrRx = /attribute\s+\w+\s+(\w+)/g;
28152
- let match;
28153
- let i = 0;
28154
- while (match = attrRx.exec(shader.vertex)) {
28155
- attributes[match[1]] = i++;
28156
- }
28157
- return attributes;
28158
- }
28159
-
28160
- // src/video/webgl/utils/precision.js
28161
- function setPrecision(src, precision) {
28162
- if (src.substring(0, 9) !== "precision") {
28163
- return "precision " + precision + " float;\n" + src;
28164
- }
28165
- return src;
28166
- }
28167
- function getMaxShaderPrecision(gl) {
28168
- if (gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT).precision > 0 && gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT).precision > 0) {
28169
- return "highp";
28170
- }
28171
- if (gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT).precision > 0 && gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT).precision > 0) {
28172
- return "mediump";
28173
- }
28174
- return "lowp";
28175
- }
28176
-
28177
- // src/video/webgl/utils/program.js
28178
- function compileShader(gl, type, source) {
28179
- const shader = gl.createShader(type);
28180
- gl.shaderSource(shader, source);
28181
- gl.compileShader(shader);
28182
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
28183
- throw new Error(gl.getShaderInfoLog(shader));
28184
- }
28185
- return shader;
28186
- }
28187
- function compileProgram(gl, vertex, fragment, attributes) {
28188
- const vertShader = compileShader(gl, gl.VERTEX_SHADER, vertex);
28189
- const fragShader = compileShader(gl, gl.FRAGMENT_SHADER, fragment);
28190
- const program = gl.createProgram();
28191
- gl.attachShader(program, vertShader);
28192
- gl.attachShader(program, fragShader);
28193
- for (const location in attributes) {
28194
- gl.bindAttribLocation(program, attributes[location], location);
28195
- }
28196
- gl.linkProgram(program);
28197
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
28198
- const error_msg = "Error initializing Shader " + this + "\ngl.VALIDATE_STATUS: " + gl.getProgramParameter(program, gl.VALIDATE_STATUS) + "\ngl.getError()" + gl.getError() + "\ngl.getProgramInfoLog()" + gl.getProgramInfoLog(program);
28199
- gl.deleteProgram(program);
28200
- throw new Error(error_msg);
30120
+ // src/video/buffer/index.js
30121
+ var IndexBuffer = class {
30122
+ /**
30123
+ * @param {number} maxIndices - maximum number of indices this buffer can hold
30124
+ * @param {boolean} [useUint32=false] - use Uint32 indices instead of Uint16
30125
+ */
30126
+ constructor(maxIndices, useUint32 = false) {
30127
+ this.data = useUint32 ? new Uint32Array(maxIndices) : new Uint16Array(maxIndices);
30128
+ this.length = 0;
28201
30129
  }
28202
- gl.useProgram(program);
28203
- gl.deleteShader(vertShader);
28204
- gl.deleteShader(fragShader);
28205
- return program;
28206
- }
28207
-
28208
- // src/video/webgl/utils/string.js
28209
- function minify(src) {
28210
- src = src.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "$1");
28211
- src = src.replace(/(\\n\s+)|(\s+\\n)/g, "");
28212
- src = src.replace(/(\\r|\\n)+/g, "");
28213
- src = src.replace(/[ \t]*([;,[\](){}\\\/\-+*|^&!=<>?~%])[ \t]*/g, "$1");
28214
- return src;
28215
- }
28216
-
28217
- // src/video/webgl/utils/uniforms.js
28218
- var fnHash = {
28219
- bool: "1i",
28220
- int: "1i",
28221
- float: "1f",
28222
- vec2: "2fv",
28223
- vec3: "3fv",
28224
- vec4: "4fv",
28225
- bvec2: "2iv",
28226
- bvec3: "3iv",
28227
- bvec4: "4iv",
28228
- ivec2: "2iv",
28229
- ivec3: "3iv",
28230
- ivec4: "4iv",
28231
- mat2: "Matrix2fv",
28232
- mat3: "Matrix3fv",
28233
- mat4: "Matrix4fv",
28234
- sampler2D: "1i"
28235
- };
28236
- function extractUniforms(gl, shader) {
28237
- const uniforms = {};
28238
- const uniRx = /uniform\s+(\w+)\s+(\w+)/g;
28239
- const uniformsData = {};
28240
- const descriptor = {};
28241
- const locations = {};
28242
- let match;
28243
- [shader.vertex, shader.fragment].forEach((shader2) => {
28244
- while (match = uniRx.exec(shader2)) {
28245
- uniformsData[match[2]] = match[1];
28246
- }
28247
- });
28248
- Object.keys(uniformsData).forEach((name) => {
28249
- const type = uniformsData[name];
28250
- locations[name] = gl.getUniformLocation(shader.program, name);
28251
- descriptor[name] = {
28252
- get: /* @__PURE__ */ (function(name2) {
28253
- return function() {
28254
- return locations[name2];
28255
- };
28256
- })(name),
28257
- set: (function(name2, type2, fn) {
28258
- if (/^mat/.test(type2)) {
28259
- return function(val) {
28260
- gl[fn](locations[name2], false, val);
28261
- };
28262
- } else {
28263
- return function(val) {
28264
- let fnv = fn;
28265
- if (val.length && !/v$/.test(fn)) {
28266
- fnv += "v";
28267
- }
28268
- gl[fnv](locations[name2], val);
28269
- };
28270
- }
28271
- })(name, type, "uniform" + fnHash[type])
28272
- };
28273
- });
28274
- Object.defineProperties(uniforms, descriptor);
28275
- return uniforms;
28276
- }
28277
-
28278
- // src/video/webgl/glshader.js
28279
- var GLShader = class {
28280
30130
  /**
28281
- * @param {WebGLRenderingContext} gl - the current WebGL rendering context
28282
- * @param {string} vertex - a string containing the GLSL source code to set
28283
- * @param {string} fragment - a string containing the GLSL source code to set
28284
- * @param {string} [precision=auto detected] - float precision ('lowp', 'mediump' or 'highp').
28285
- * @see https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders
28286
- * @example
28287
- * // create a basic shader
28288
- * let myShader = new me.GLShader(
28289
- * // WebGL rendering context
28290
- * gl,
28291
- * // vertex shader
28292
- * [
28293
- * "void main() {",
28294
- * " gl_Position = doMathToMakeClipspaceCoordinates;",
28295
- * "}"
28296
- * ].join("\n"),
28297
- * // fragment shader
28298
- * [
28299
- * "void main() {",
28300
- * " gl_FragColor = doMathToMakeAColor;",
28301
- * "}"
28302
- * ].join("\n")
28303
- * )
28304
- * // use the shader
28305
- * myShader.bind();
30131
+ * Fill the buffer with a repeating quad index pattern [0,1,2, 2,1,3, 4,5,6, ...]
30132
+ * @param {number} maxQuads - number of quads to generate indices for
28306
30133
  */
28307
- constructor(gl, vertex, fragment, precision) {
28308
- this.gl = gl;
28309
- this.vertex = setPrecision(
28310
- minify(vertex),
28311
- precision || getMaxShaderPrecision(this.gl)
28312
- );
28313
- this.fragment = setPrecision(
28314
- minify(fragment),
28315
- precision || getMaxShaderPrecision(this.gl)
28316
- );
28317
- this.attributes = extractAttributes(this.gl, this);
28318
- this.program = compileProgram(
28319
- this.gl,
28320
- this.vertex,
28321
- this.fragment,
28322
- this.attributes
28323
- );
28324
- this.uniforms = extractUniforms(this.gl, this);
28325
- on(ONCONTEXT_LOST, this.destroy, this);
30134
+ fillQuadPattern(maxQuads) {
30135
+ for (let i = 0, vertex = 0; i < maxQuads * 6; i += 6, vertex += 4) {
30136
+ this.data[i] = vertex;
30137
+ this.data[i + 1] = vertex + 1;
30138
+ this.data[i + 2] = vertex + 2;
30139
+ this.data[i + 3] = vertex + 2;
30140
+ this.data[i + 4] = vertex + 1;
30141
+ this.data[i + 5] = vertex + 3;
30142
+ }
30143
+ this.length = maxQuads * 6;
28326
30144
  }
28327
30145
  /**
28328
- * Installs this shader program as part of current rendering state
30146
+ * Reset the index count
28329
30147
  */
28330
- bind() {
28331
- this.gl.useProgram(this.program);
30148
+ clear() {
30149
+ this.length = 0;
28332
30150
  }
28333
30151
  /**
28334
- * returns the location of an attribute variable in this shader program
28335
- * @param {string} name - the name of the attribute variable whose location to get.
28336
- * @returns {GLint} number indicating the location of the variable name if found. Returns -1 otherwise
30152
+ * Add indices to the buffer, rebased by the given vertex offset
30153
+ * @param {number[]} indices - source indices to add
30154
+ * @param {number} vertexOffset - value to add to each index
28337
30155
  */
28338
- getAttribLocation(name) {
28339
- const attr = this.attributes[name];
28340
- if (typeof attr !== "undefined") {
28341
- return attr;
28342
- } else {
28343
- return -1;
30156
+ add(indices, vertexOffset) {
30157
+ for (let i = 0; i < indices.length; i++) {
30158
+ this.data[this.length + i] = indices[i] + vertexOffset;
28344
30159
  }
30160
+ this.length += indices.length;
28345
30161
  }
28346
30162
  /**
28347
- * Set the uniform to the given value
28348
- * @param {string} name - the uniform name
28349
- * @param {object|Float32Array} value - the value to assign to that uniform
28350
- * @example
28351
- * myShader.setUniform("uProjectionMatrix", this.projectionMatrix);
30163
+ * Add pre-computed absolute indices to the buffer (no rebasing)
30164
+ * @param {number[]} indices - absolute index values to add
28352
30165
  */
28353
- setUniform(name, value) {
28354
- const uniforms = this.uniforms;
28355
- if (typeof uniforms[name] !== "undefined") {
28356
- this.bind();
28357
- if (typeof value === "object" && typeof value.toArray === "function") {
28358
- uniforms[name] = value.toArray();
28359
- } else {
28360
- uniforms[name] = value;
28361
- }
28362
- } else {
28363
- throw new Error("undefined (" + name + ") uniform for shader " + this);
30166
+ addRaw(indices) {
30167
+ for (let i = 0; i < indices.length; i++) {
30168
+ this.data[this.length + i] = indices[i];
28364
30169
  }
30170
+ this.length += indices.length;
28365
30171
  }
30172
+ };
30173
+
30174
+ // src/video/webgl/buffer/index.js
30175
+ var WebGLIndexBuffer = class extends IndexBuffer {
28366
30176
  /**
28367
- * activate the given vertex attribute for this shader
28368
- * @param {WebGLRenderingContext} gl - the current WebGL rendering context
28369
- * @param {object[]} attributes - an array of vertex attributes
28370
- * @param {number} stride - the size of a single vertex in bytes
30177
+ * @param {WebGLRenderingContext|WebGL2RenderingContext} gl - the WebGL context
30178
+ * @param {number} maxIndices - maximum number of indices this buffer can hold
30179
+ * @param {boolean} [useUint32=false] - use Uint32 indices (WebGL2) instead of Uint16 (WebGL1)
28371
30180
  */
28372
- setVertexAttributes(gl, attributes, stride) {
28373
- for (let index = 0; index < attributes.length; ++index) {
28374
- const element = attributes[index];
28375
- const location = this.getAttribLocation(element.name);
28376
- if (location !== -1) {
28377
- gl.enableVertexAttribArray(location);
28378
- gl.vertexAttribPointer(
28379
- location,
28380
- element.size,
28381
- element.type,
28382
- element.normalized,
28383
- stride,
28384
- element.offset
28385
- );
28386
- } else {
28387
- gl.disableVertexAttribArray(index);
28388
- }
28389
- }
30181
+ constructor(gl, maxIndices, useUint32 = false) {
30182
+ super(maxIndices, useUint32);
30183
+ this.gl = gl;
30184
+ this.type = useUint32 ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
30185
+ this.buffer = gl.createBuffer();
28390
30186
  }
28391
30187
  /**
28392
- * destroy this shader objects resources (program, attributes, uniforms)
30188
+ * Fill the buffer with a repeating quad index pattern and upload as static.
30189
+ * @param {number} maxQuads - number of quads to generate indices for
28393
30190
  */
28394
- destroy() {
28395
- this.uniforms = null;
28396
- this.attributes = null;
28397
- this.gl.deleteProgram(this.program);
28398
- this.vertex = null;
28399
- this.fragment = null;
30191
+ fillQuadPattern(maxQuads) {
30192
+ super.fillQuadPattern(maxQuads);
30193
+ this.bind();
30194
+ this.gl.bufferData(
30195
+ this.gl.ELEMENT_ARRAY_BUFFER,
30196
+ this.data,
30197
+ this.gl.STATIC_DRAW
30198
+ );
30199
+ }
30200
+ /**
30201
+ * Upload the current index data to the GPU (for dynamic buffers)
30202
+ */
30203
+ upload() {
30204
+ this.bind();
30205
+ this.gl.bufferData(
30206
+ this.gl.ELEMENT_ARRAY_BUFFER,
30207
+ this.data.subarray(0, this.length),
30208
+ this.gl.STREAM_DRAW
30209
+ );
30210
+ }
30211
+ /**
30212
+ * Bind this index buffer
30213
+ */
30214
+ bind() {
30215
+ this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffer);
28400
30216
  }
28401
30217
  };
28402
30218
 
@@ -28456,7 +30272,8 @@ var Batcher = class {
28456
30272
  this.defaultShader = new GLShader(
28457
30273
  this.gl,
28458
30274
  settings.shader.vertex,
28459
- settings.shader.fragment
30275
+ settings.shader.fragment,
30276
+ this.renderer.shaderPrecision
28460
30277
  );
28461
30278
  } else {
28462
30279
  throw new Error("shader definition missing");
@@ -28468,7 +30285,7 @@ var Batcher = class {
28468
30285
  if (this.useIndexBuffer) {
28469
30286
  const gl = this.gl;
28470
30287
  this.glVertexBuffer = gl.createBuffer();
28471
- this.indexBuffer = new IndexBuffer(gl, maxVertices * 3, false, true);
30288
+ this.indexBuffer = new WebGLIndexBuffer(gl, maxVertices * 3, false);
28472
30289
  }
28473
30290
  }
28474
30291
  /**
@@ -28672,7 +30489,7 @@ var MaterialBatcher = class extends Batcher {
28672
30489
  * @param {boolean} [mipmap=true] - Whether mipmap levels should be generated
28673
30490
  * @returns {WebGLTexture} a WebGL texture
28674
30491
  */
28675
- createTexture2D(unit, pixels = null, filter, repeat = "no-repeat", w = pixels.width, h = pixels.height, premultipliedAlpha = true, mipmap = true, texture) {
30492
+ createTexture2D(unit, pixels = null, filter, repeat = "no-repeat", w = pixels.width, h = pixels.height, premultipliedAlpha = true, mipmap = true, texture, flush = true) {
28676
30493
  const gl = this.gl;
28677
30494
  const isPOT = isPowerOfTwo(w) && isPowerOfTwo(h);
28678
30495
  const rs = repeat.search(/^repeat(-x)?$/) === 0 && (isPOT || this.renderer.WebGLVersion > 1) ? gl.REPEAT : gl.CLAMP_TO_EDGE;
@@ -28681,7 +30498,7 @@ var MaterialBatcher = class extends Batcher {
28681
30498
  if (!currentTexture) {
28682
30499
  currentTexture = gl.createTexture();
28683
30500
  }
28684
- this.bindTexture2D(currentTexture, unit);
30501
+ this.bindTexture2D(currentTexture, unit, flush);
28685
30502
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, rs);
28686
30503
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, rt);
28687
30504
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
@@ -28786,10 +30603,12 @@ var MaterialBatcher = class extends Batcher {
28786
30603
  * @param {WebGLTexture} texture - a WebGL texture
28787
30604
  * @param {number} unit - Texture unit to which the given texture is bound
28788
30605
  */
28789
- bindTexture2D(texture, unit) {
30606
+ bindTexture2D(texture, unit, flush = true) {
28790
30607
  const gl = this.gl;
28791
30608
  if (texture !== this.boundTextures[unit]) {
28792
- this.flush();
30609
+ if (flush) {
30610
+ this.flush();
30611
+ }
28793
30612
  if (this.currentTextureUnit !== unit) {
28794
30613
  this.currentTextureUnit = unit;
28795
30614
  gl.activeTexture(gl.TEXTURE0 + unit);
@@ -28797,7 +30616,9 @@ var MaterialBatcher = class extends Batcher {
28797
30616
  gl.bindTexture(gl.TEXTURE_2D, texture);
28798
30617
  this.boundTextures[unit] = texture;
28799
30618
  } else if (this.currentTextureUnit !== unit) {
28800
- this.flush();
30619
+ if (flush) {
30620
+ this.flush();
30621
+ }
28801
30622
  this.currentTextureUnit = unit;
28802
30623
  gl.activeTexture(gl.TEXTURE0 + unit);
28803
30624
  }
@@ -28823,7 +30644,7 @@ var MaterialBatcher = class extends Batcher {
28823
30644
  /**
28824
30645
  * @ignore
28825
30646
  */
28826
- uploadTexture(texture, w, h, force = false) {
30647
+ uploadTexture(texture, w, h, force = false, flush = true) {
28827
30648
  const unit = this.renderer.cache.getUnit(texture);
28828
30649
  const texture2D = this.boundTextures[unit];
28829
30650
  if (typeof texture2D === "undefined" || force) {
@@ -28836,12 +30657,13 @@ var MaterialBatcher = class extends Batcher {
28836
30657
  h,
28837
30658
  texture.premultipliedAlpha,
28838
30659
  void 0,
28839
- texture2D
30660
+ texture2D,
30661
+ flush
28840
30662
  );
28841
30663
  } else {
28842
- this.bindTexture2D(texture2D, unit);
30664
+ this.bindTexture2D(texture2D, unit, flush);
28843
30665
  }
28844
- return this.currentTextureUnit;
30666
+ return flush ? this.currentTextureUnit : unit;
28845
30667
  }
28846
30668
  };
28847
30669
 
@@ -29118,11 +30940,37 @@ var PrimitiveBatcher = class extends Batcher {
29118
30940
  }
29119
30941
  };
29120
30942
 
29121
- // src/video/webgl/shaders/quad.frag
29122
- var quad_default = "uniform sampler2D uSampler;\nvarying vec4 vColor;\nvarying vec2 vRegion;\n\nvoid main(void) {\n gl_FragColor = texture2D(uSampler, vRegion) * vColor;\n}\n";
30943
+ // src/video/webgl/shaders/multitexture.js
30944
+ function buildMultiTextureFragment(maxTextures) {
30945
+ const count = Math.max(maxTextures, 1);
30946
+ const lines = [];
30947
+ for (let i = 0; i < count; i++) {
30948
+ lines.push("uniform sampler2D uSampler" + i + ";");
30949
+ }
30950
+ lines.push("varying vec4 vColor;");
30951
+ lines.push("varying vec2 vRegion;");
30952
+ lines.push("varying float vTextureId;");
30953
+ lines.push("");
30954
+ lines.push("void main(void) {");
30955
+ lines.push(" vec4 color;");
30956
+ for (let i = 0; i < count; i++) {
30957
+ if (i === 0) {
30958
+ lines.push(" if (vTextureId < 0.5) {");
30959
+ } else {
30960
+ lines.push(" } else if (vTextureId < " + (i + 0.5) + ") {");
30961
+ }
30962
+ lines.push(" color = texture2D(uSampler" + i + ", vRegion);");
30963
+ }
30964
+ lines.push(" } else {");
30965
+ lines.push(" color = texture2D(uSampler0, vRegion);");
30966
+ lines.push(" }");
30967
+ lines.push(" gl_FragColor = color * vColor;");
30968
+ lines.push("}");
30969
+ return lines.join("\n");
30970
+ }
29123
30971
 
29124
- // src/video/webgl/shaders/quad.vert
29125
- var quad_default2 = "// Current vertex point\nattribute vec2 aVertex;\nattribute vec2 aRegion;\nattribute vec4 aColor;\n\n// Projection matrix\nuniform mat4 uProjectionMatrix;\n\nvarying vec2 vRegion;\nvarying vec4 vColor;\n\nvoid main(void) {\n // Transform the vertex position by the projection matrix\n gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0);\n // Pass the remaining attributes to the fragment shader\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n}\n";
30972
+ // src/video/webgl/shaders/quad-multi.vert
30973
+ var quad_multi_default = "// Current vertex point\nattribute vec2 aVertex;\nattribute vec2 aRegion;\nattribute vec4 aColor;\nattribute float aTextureId;\n\n// Projection matrix\nuniform mat4 uProjectionMatrix;\n\nvarying vec2 vRegion;\nvarying vec4 vColor;\nvarying float vTextureId;\n\nvoid main(void) {\n // Transform the vertex position by the projection matrix\n gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0);\n // Pass the remaining attributes to the fragment shader\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n vTextureId = aTextureId;\n}\n";
29126
30974
 
29127
30975
  // src/video/webgl/batchers/quad_batcher.js
29128
30976
  var V_ARRAY = [
@@ -29137,6 +30985,7 @@ var QuadBatcher = class extends MaterialBatcher {
29137
30985
  * @ignore
29138
30986
  */
29139
30987
  init(renderer2) {
30988
+ this.maxBatchTextures = Math.min(renderer2.maxTextures, 16);
29140
30989
  super.init(renderer2, {
29141
30990
  attributes: [
29142
30991
  {
@@ -29159,21 +31008,44 @@ var QuadBatcher = class extends MaterialBatcher {
29159
31008
  type: renderer2.gl.UNSIGNED_BYTE,
29160
31009
  normalized: true,
29161
31010
  offset: 4 * Float32Array.BYTES_PER_ELEMENT
31011
+ },
31012
+ {
31013
+ name: "aTextureId",
31014
+ size: 1,
31015
+ type: renderer2.gl.FLOAT,
31016
+ normalized: false,
31017
+ offset: 5 * Float32Array.BYTES_PER_ELEMENT
29162
31018
  }
29163
31019
  ],
29164
31020
  shader: {
29165
- vertex: quad_default2,
29166
- fragment: quad_default
31021
+ vertex: quad_multi_default,
31022
+ fragment: buildMultiTextureFragment(this.maxBatchTextures)
29167
31023
  }
29168
31024
  });
31025
+ for (let i = 0; i < this.maxBatchTextures; i++) {
31026
+ this.defaultShader.setUniform("uSampler" + i, i);
31027
+ }
31028
+ this.useMultiTexture = true;
29169
31029
  const maxQuads = this.vertexData.maxVertex / 4;
29170
- this.indexBuffer = new IndexBuffer(
31030
+ this.indexBuffer = new WebGLIndexBuffer(
29171
31031
  this.gl,
29172
31032
  maxQuads * 6,
29173
31033
  this.renderer.WebGLVersion > 1
29174
31034
  );
29175
31035
  this.indexBuffer.fillQuadPattern(maxQuads);
29176
31036
  }
31037
+ /**
31038
+ * Select the shader to use for compositing.
31039
+ * Multi-texture batching is automatically enabled when the default
31040
+ * shader is active, and disabled for custom ShaderEffect shaders.
31041
+ * @see GLShader
31042
+ * @see ShaderEffect
31043
+ * @param {GLShader|ShaderEffect} shader - a reference to a GLShader or ShaderEffect instance
31044
+ */
31045
+ useShader(shader) {
31046
+ super.useShader(shader);
31047
+ this.useMultiTexture = shader === this.defaultShader;
31048
+ }
29177
31049
  /**
29178
31050
  * Reset compositor internal state
29179
31051
  * @ignore
@@ -29181,12 +31053,16 @@ var QuadBatcher = class extends MaterialBatcher {
29181
31053
  reset() {
29182
31054
  super.reset();
29183
31055
  const maxQuads = this.vertexData.maxVertex / 4;
29184
- this.indexBuffer = new IndexBuffer(
31056
+ this.indexBuffer = new WebGLIndexBuffer(
29185
31057
  this.gl,
29186
31058
  maxQuads * 6,
29187
31059
  this.renderer.WebGLVersion > 1
29188
31060
  );
29189
31061
  this.indexBuffer.fillQuadPattern(maxQuads);
31062
+ for (let i = 0; i < this.maxBatchTextures; i++) {
31063
+ this.defaultShader.setUniform("uSampler" + i, i);
31064
+ }
31065
+ this.useMultiTexture = true;
29190
31066
  }
29191
31067
  /**
29192
31068
  * Flush batched texture data to the GPU using indexed drawing.
@@ -29219,6 +31095,34 @@ var QuadBatcher = class extends MaterialBatcher {
29219
31095
  vertex.clear();
29220
31096
  }
29221
31097
  }
31098
+ /**
31099
+ * Draw a screen-aligned quad with the given raw WebGL texture through the given shader.
31100
+ * Binds the texture to unit 0, pushes 4 vertices (Y-flipped UVs), flushes,
31101
+ * then unbinds the texture.
31102
+ * @param {WebGLTexture} source - the raw GL texture to blit
31103
+ * @param {number} x - destination x
31104
+ * @param {number} y - destination y
31105
+ * @param {number} width - destination width
31106
+ * @param {number} height - destination height
31107
+ * @param {GLShader|ShaderEffect} shader - the shader effect to apply
31108
+ */
31109
+ blitTexture(source, x, y, width, height, shader) {
31110
+ const gl = this.gl;
31111
+ this.useShader(shader);
31112
+ gl.activeTexture(gl.TEXTURE0);
31113
+ gl.bindTexture(gl.TEXTURE_2D, source);
31114
+ shader.setUniform("uSampler", 0);
31115
+ const tint = 4294967295;
31116
+ this.vertexData.push(x, y, 0, 1, tint, 0);
31117
+ this.vertexData.push(x + width, y, 1, 1, tint, 0);
31118
+ this.vertexData.push(x, y + height, 0, 0, tint, 0);
31119
+ this.vertexData.push(x + width, y + height, 1, 0, tint, 0);
31120
+ this.flush();
31121
+ gl.activeTexture(gl.TEXTURE0);
31122
+ gl.bindTexture(gl.TEXTURE_2D, null);
31123
+ delete this.boundTextures[0];
31124
+ this.useShader(this.defaultShader);
31125
+ }
29222
31126
  /**
29223
31127
  * Add a textured quad
29224
31128
  * @param {TextureAtlas} texture - Source texture atlas
@@ -29238,10 +31142,20 @@ var QuadBatcher = class extends MaterialBatcher {
29238
31142
  if (vertexData.isFull(4)) {
29239
31143
  this.flush();
29240
31144
  }
29241
- const unit = this.uploadTexture(texture, w, h, reupload);
29242
- if (unit !== this.currentSamplerUnit) {
29243
- this.currentShader.setUniform("uSampler", unit);
29244
- this.currentSamplerUnit = unit;
31145
+ let unit;
31146
+ if (this.useMultiTexture) {
31147
+ unit = this.uploadTexture(texture, w, h, reupload, false);
31148
+ if (unit >= this.maxBatchTextures) {
31149
+ this.flush();
31150
+ this.renderer.cache.resetUnitAssignments();
31151
+ unit = this.uploadTexture(texture, w, h, reupload, false);
31152
+ }
31153
+ } else {
31154
+ unit = this.uploadTexture(texture, w, h, reupload);
31155
+ if (unit !== this.currentSamplerUnit) {
31156
+ this.currentShader.setUniform("uSampler", unit);
31157
+ this.currentSamplerUnit = unit;
31158
+ }
29245
31159
  }
29246
31160
  const m = this.viewMatrix;
29247
31161
  const vec0 = V_ARRAY[0].set(x, y);
@@ -29254,15 +31168,18 @@ var QuadBatcher = class extends MaterialBatcher {
29254
31168
  m.apply(vec2);
29255
31169
  m.apply(vec3);
29256
31170
  }
29257
- vertexData.push(vec0.x, vec0.y, u0, v0, tint);
29258
- vertexData.push(vec1.x, vec1.y, u1, v0, tint);
29259
- vertexData.push(vec2.x, vec2.y, u0, v1, tint);
29260
- vertexData.push(vec3.x, vec3.y, u1, v1, tint);
31171
+ const textureId = this.useMultiTexture ? unit : 0;
31172
+ vertexData.push(vec0.x, vec0.y, u0, v0, tint, textureId);
31173
+ vertexData.push(vec1.x, vec1.y, u1, v0, tint, textureId);
31174
+ vertexData.push(vec2.x, vec2.y, u0, v1, tint, textureId);
31175
+ vertexData.push(vec3.x, vec3.y, u1, v1, tint, textureId);
29261
31176
  }
29262
31177
  };
29263
31178
 
29264
31179
  // src/video/webgl/webgl_renderer.js
29265
31180
  var _tempMatrix = new Matrix3d();
31181
+ var _savedTransform = new Matrix3d();
31182
+ var _savedProjection = new Matrix3d();
29266
31183
  var supportedCompressedTextureFormats;
29267
31184
  var WebGLRenderer = class extends Renderer {
29268
31185
  /**
@@ -29273,10 +31190,18 @@ var WebGLRenderer = class extends Renderer {
29273
31190
  this.GPUVendor = void 0;
29274
31191
  this.GPURenderer = void 0;
29275
31192
  this.gl = this.renderTarget.context;
31193
+ this._renderTargetPool = new RenderTargetPool((w, h) => {
31194
+ return new WebGLRenderTarget(this.gl, w, h);
31195
+ });
31196
+ this._savedEffectProjection = new Matrix3d();
29276
31197
  this.lineWidth = 1;
29277
31198
  this.lineJoin = "round";
29278
31199
  this.vertexBuffer = this.gl.createBuffer();
29279
31200
  this.maxTextures = this.gl.getParameter(this.gl.MAX_TEXTURE_IMAGE_UNITS);
31201
+ this.shaderPrecision = getMaxShaderPrecision(
31202
+ this.gl,
31203
+ this.settings.highPrecisionShader !== false
31204
+ );
29280
31205
  this._rectTriangles = Array.from({ length: 6 }, () => {
29281
31206
  return { x: 0, y: 0 };
29282
31207
  });
@@ -29294,6 +31219,7 @@ var WebGLRenderer = class extends Renderer {
29294
31219
  this.gl.disable(this.gl.DEPTH_TEST);
29295
31220
  this.gl.depthMask(false);
29296
31221
  this.gl.disable(this.gl.SCISSOR_TEST);
31222
+ this._scissorActive = false;
29297
31223
  this.gl.enable(this.gl.BLEND);
29298
31224
  this.setBlendMode(this.settings.blendMode);
29299
31225
  const debugInfo = this.gl.getExtension("WEBGL_debug_renderer_info");
@@ -29346,6 +31272,9 @@ var WebGLRenderer = class extends Renderer {
29346
31272
  getSupportedCompressedTextureFormats() {
29347
31273
  if (typeof supportedCompressedTextureFormats === "undefined") {
29348
31274
  const gl = this.gl;
31275
+ if (typeof gl === "undefined" || gl === null) {
31276
+ return super.getSupportedCompressedTextureFormats();
31277
+ }
29349
31278
  supportedCompressedTextureFormats = {
29350
31279
  astc: gl.getExtension("WEBGL_compressed_texture_astc") || this._gl.getExtension("WEBKIT_WEBGL_compressed_texture_astc"),
29351
31280
  bptc: gl.getExtension("EXT_texture_compression_bptc") || this._gl.getExtension("WEBKIT_EXT_texture_compression_bptc"),
@@ -29401,6 +31330,8 @@ var WebGLRenderer = class extends Renderer {
29401
31330
  });
29402
31331
  this.setBatcher("quad");
29403
31332
  this.gl.disable(this.gl.SCISSOR_TEST);
31333
+ this._scissorActive = false;
31334
+ this._renderTargetPool.destroy();
29404
31335
  }
29405
31336
  /**
29406
31337
  * add a new batcher to this renderer
@@ -29487,6 +31418,148 @@ var WebGLRenderer = class extends Renderer {
29487
31418
  flush() {
29488
31419
  this.currentBatcher.flush();
29489
31420
  }
31421
+ /**
31422
+ * Begin capturing rendering to an offscreen FBO for post-effect processing.
31423
+ * @param {Renderable} renderable - the renderable requesting post-effect processing
31424
+ * @returns {boolean} true if FBO capture started, false if skipped
31425
+ * @ignore
31426
+ */
31427
+ beginPostEffect(renderable) {
31428
+ const effects = renderable.postEffects.filter((fx) => {
31429
+ return fx.enabled !== false;
31430
+ });
31431
+ if (effects.length === 0) {
31432
+ this.customShader = void 0;
31433
+ return false;
31434
+ }
31435
+ if (effects.length === 1 && !renderable._postEffectManaged) {
31436
+ this.customShader = effects[0];
31437
+ return false;
31438
+ }
31439
+ this.customShader = void 0;
31440
+ const isCamera = renderable._postEffectManaged;
31441
+ const canvas = this.getCanvas();
31442
+ const w = canvas.width;
31443
+ const h = canvas.height;
31444
+ this.flush();
31445
+ this.save();
31446
+ this._savedEffectProjection.copy(this.projectionMatrix);
31447
+ const rt = this._renderTargetPool.begin(isCamera, effects.length, w, h);
31448
+ if (this.currentBatcher && this.currentBatcher.boundTextures) {
31449
+ delete this.currentBatcher.boundTextures[0];
31450
+ }
31451
+ rt.bind();
31452
+ this.setViewport(0, 0, w, h);
31453
+ this.disableScissor();
31454
+ this.setGlobalAlpha(1);
31455
+ this.setBlendMode("normal");
31456
+ if (isCamera) {
31457
+ this.clear();
31458
+ } else {
31459
+ this.clearRenderTarget();
31460
+ }
31461
+ return true;
31462
+ }
31463
+ /** @ignore */
31464
+ endPostEffect(renderable) {
31465
+ const effects = renderable.postEffects.filter((fx) => {
31466
+ return fx.enabled !== false;
31467
+ });
31468
+ if (effects.length === 0) {
31469
+ return;
31470
+ }
31471
+ if (effects.length === 1 && !renderable._postEffectManaged) {
31472
+ return;
31473
+ }
31474
+ const isCamera = renderable._postEffectManaged;
31475
+ const rt1 = this._renderTargetPool.getCaptureTarget();
31476
+ const rt2 = this._renderTargetPool.getPingPongTarget();
31477
+ const keepBlend = !isCamera;
31478
+ const canvas = this.getCanvas();
31479
+ const w = canvas.width;
31480
+ const h = canvas.height;
31481
+ this.flush();
31482
+ rt1.unbind();
31483
+ const parentRT = this._renderTargetPool.end();
31484
+ if (isCamera && renderable.isDefault === false) {
31485
+ this.clipRect(
31486
+ renderable.screenX,
31487
+ renderable.screenY,
31488
+ renderable.width,
31489
+ renderable.height
31490
+ );
31491
+ }
31492
+ if (parentRT) {
31493
+ parentRT.bind();
31494
+ }
31495
+ this.setViewport(0, 0, w, h);
31496
+ if (effects.length === 1) {
31497
+ this.blitEffect(rt1.texture, 0, 0, w, h, effects[0], keepBlend);
31498
+ } else {
31499
+ let src = rt1;
31500
+ let dst = rt2;
31501
+ for (let i = 0; i < effects.length - 1; i++) {
31502
+ dst.bind();
31503
+ this.setViewport(0, 0, w, h);
31504
+ this.clearRenderTarget();
31505
+ this.blitEffect(src.texture, 0, 0, w, h, effects[i]);
31506
+ dst.unbind();
31507
+ const tmp = src;
31508
+ src = dst;
31509
+ dst = tmp;
31510
+ }
31511
+ if (parentRT) {
31512
+ parentRT.bind();
31513
+ }
31514
+ this.setViewport(0, 0, w, h);
31515
+ this.blitEffect(
31516
+ src.texture,
31517
+ 0,
31518
+ 0,
31519
+ w,
31520
+ h,
31521
+ effects[effects.length - 1],
31522
+ keepBlend
31523
+ );
31524
+ }
31525
+ if (isCamera && renderable.isDefault === false) {
31526
+ this.disableScissor();
31527
+ }
31528
+ this.restore();
31529
+ this.projectionMatrix.copy(this._savedEffectProjection);
31530
+ this.currentBatcher.setProjection(this.projectionMatrix);
31531
+ }
31532
+ /**
31533
+ * Blit a texture to the screen through a shader effect.
31534
+ * Draws a screen-aligned quad using the given texture as source
31535
+ * and the given shader for post-processing (e.g. scanlines, desaturation).
31536
+ * @param {WebGLTexture} source - the source texture to blit
31537
+ * @param {number} x - destination x position
31538
+ * @param {number} y - destination y position
31539
+ * @param {number} width - destination width
31540
+ * @param {number} height - destination height
31541
+ * @param {ShaderEffect} shader - the shader effect to apply
31542
+ * @param {boolean} [keepBlend=false] - if true, keep current blend mode (for sprite compositing)
31543
+ */
31544
+ blitEffect(source, x, y, width, height, shader, keepBlend = false) {
31545
+ this.flush();
31546
+ const batcher = this.setBatcher("quad");
31547
+ _savedTransform.copy(this.currentTransform);
31548
+ _savedProjection.copy(this.projectionMatrix);
31549
+ this.currentTransform.identity();
31550
+ this.projectionMatrix.ortho(0, width, height, 0, -1, 1);
31551
+ batcher.setProjection(this.projectionMatrix);
31552
+ if (!keepBlend) {
31553
+ this.setBlendEnabled(false);
31554
+ }
31555
+ batcher.blitTexture(source, x, y, width, height, shader);
31556
+ if (!keepBlend) {
31557
+ this.setBlendEnabled(true);
31558
+ }
31559
+ this.currentTransform.copy(_savedTransform);
31560
+ this.projectionMatrix.copy(_savedProjection);
31561
+ batcher.setProjection(this.projectionMatrix);
31562
+ }
29490
31563
  /**
29491
31564
  * set/change the current projection matrix (WebGL only)
29492
31565
  * @param {Matrix3d} matrix - the new projection matrix
@@ -29506,6 +31579,61 @@ var WebGLRenderer = class extends Renderer {
29506
31579
  setViewport(x = 0, y = 0, w = this.getCanvas().width, h = this.getCanvas().height) {
29507
31580
  this.gl.viewport(x, y, w, h);
29508
31581
  }
31582
+ /**
31583
+ * Clear the current render target to transparent black.
31584
+ *
31585
+ * Only clears the color buffer — `setMask()` clears `STENCIL_BUFFER_BIT`
31586
+ * itself when starting a mask, and including it here would emit a WebGL
31587
+ * `Clear called for non-existing buffers` warning when the active FBO
31588
+ * has no stencil attachment (rare, but happens on drivers that fail
31589
+ * the depth+stencil renderbuffer attachment for the FBO).
31590
+ */
31591
+ clearRenderTarget() {
31592
+ const gl = this.gl;
31593
+ gl.clearColor(0, 0, 0, 0);
31594
+ gl.clear(gl.COLOR_BUFFER_BIT);
31595
+ }
31596
+ /**
31597
+ * Enable the scissor test with the given rectangle.
31598
+ * @param {number} x - x coordinate of the scissor rectangle
31599
+ * @param {number} y - y coordinate of the scissor rectangle
31600
+ * @param {number} width - width of the scissor rectangle
31601
+ * @param {number} height - height of the scissor rectangle
31602
+ */
31603
+ enableScissor(x, y, width, height) {
31604
+ const gl = this.gl;
31605
+ this.flush();
31606
+ gl.enable(gl.SCISSOR_TEST);
31607
+ this._scissorActive = true;
31608
+ gl.scissor(
31609
+ x + this.currentTransform.tx,
31610
+ this.getCanvas().height - height - y - this.currentTransform.ty,
31611
+ width,
31612
+ height
31613
+ );
31614
+ this.currentScissor[0] = x;
31615
+ this.currentScissor[1] = y;
31616
+ this.currentScissor[2] = width;
31617
+ this.currentScissor[3] = height;
31618
+ }
31619
+ /**
31620
+ * Disable the scissor test, allowing rendering to the full viewport.
31621
+ */
31622
+ disableScissor() {
31623
+ this.gl.disable(this.gl.SCISSOR_TEST);
31624
+ this._scissorActive = false;
31625
+ }
31626
+ /**
31627
+ * Enable or disable alpha blending.
31628
+ * @param {boolean} enable - true to enable blending, false to disable
31629
+ */
31630
+ setBlendEnabled(enable2) {
31631
+ if (enable2) {
31632
+ this.gl.enable(this.gl.BLEND);
31633
+ } else {
31634
+ this.gl.disable(this.gl.BLEND);
31635
+ }
31636
+ }
29509
31637
  /**
29510
31638
  * Clear the frame buffer
29511
31639
  */
@@ -29920,6 +32048,7 @@ var WebGLRenderer = class extends Renderer {
29920
32048
  const gl = this.gl;
29921
32049
  const s = this.currentScissor;
29922
32050
  gl.enable(gl.SCISSOR_TEST);
32051
+ this._scissorActive = true;
29923
32052
  gl.scissor(
29924
32053
  s[0] + this.currentTransform.tx,
29925
32054
  canvas.height - s[3] - s[1] - this.currentTransform.ty,
@@ -29928,9 +32057,11 @@ var WebGLRenderer = class extends Renderer {
29928
32057
  );
29929
32058
  } else {
29930
32059
  this.gl.disable(this.gl.SCISSOR_TEST);
32060
+ this._scissorActive = false;
29931
32061
  }
29932
32062
  }
29933
32063
  this._currentGradient = this.renderState.currentGradient;
32064
+ this.customShader = this.renderState.currentShader;
29934
32065
  }
29935
32066
  /**
29936
32067
  * saves the entire state of the renderer by pushing the current state onto a stack.
@@ -29946,7 +32077,8 @@ var WebGLRenderer = class extends Renderer {
29946
32077
  * renderer.restore();
29947
32078
  */
29948
32079
  save() {
29949
- this.renderState.save(this.gl.isEnabled(this.gl.SCISSOR_TEST));
32080
+ this.renderState.currentShader = this.customShader;
32081
+ this.renderState.save(this._scissorActive === true);
29950
32082
  }
29951
32083
  /**
29952
32084
  * adds a rotation to the transformation matrix.
@@ -30596,13 +32728,14 @@ var WebGLRenderer = class extends Renderer {
30596
32728
  const gl = this.gl;
30597
32729
  if (x !== 0 || y !== 0 || width !== canvas.width || height !== canvas.height) {
30598
32730
  const currentScissor = this.currentScissor;
30599
- if (gl.isEnabled(gl.SCISSOR_TEST)) {
32731
+ if (this._scissorActive) {
30600
32732
  if (currentScissor[0] === x && currentScissor[1] === y && currentScissor[2] === width && currentScissor[3] === height) {
30601
32733
  return;
30602
32734
  }
30603
32735
  }
30604
32736
  this.flush();
30605
32737
  gl.enable(this.gl.SCISSOR_TEST);
32738
+ this._scissorActive = true;
30606
32739
  gl.scissor(
30607
32740
  // scissor does not account for currentTransform, so manually adjust
30608
32741
  x + this.currentTransform.tx,
@@ -30616,6 +32749,7 @@ var WebGLRenderer = class extends Renderer {
30616
32749
  currentScissor[3] = height;
30617
32750
  } else {
30618
32751
  gl.disable(gl.SCISSOR_TEST);
32752
+ this._scissorActive = false;
30619
32753
  }
30620
32754
  }
30621
32755
  /**
@@ -31107,6 +33241,45 @@ var Application = class {
31107
33241
  repaint() {
31108
33242
  this.isDirty = true;
31109
33243
  }
33244
+ /**
33245
+ * Pause the current stage. Convenience proxy for {@link state.pause}.
33246
+ * @param [music=false] - also pause the current music track
33247
+ * @example
33248
+ * app.pause(); // pause game updates, keep music playing
33249
+ * app.pause(true); // pause game updates and music
33250
+ */
33251
+ pause(music = false) {
33252
+ state_default.pause(music);
33253
+ }
33254
+ /**
33255
+ * Resume the current stage. Convenience proxy for {@link state.resume}.
33256
+ * @param [music=false] - also resume the current music track
33257
+ */
33258
+ resume(music = false) {
33259
+ state_default.resume(music);
33260
+ }
33261
+ /**
33262
+ * Freeze the current stage for a fixed duration, then automatically resume.
33263
+ * Useful for hit-stop / hit-pause effects on impact.
33264
+ *
33265
+ * Convenience proxy for {@link state.freeze}; see that method's
33266
+ * documentation for the full behaviour matrix (extend-not-stack semantics,
33267
+ * interaction with manual `state.pause()` / `state.resume()`, automatic
33268
+ * cancellation on window blur, etc.).
33269
+ * @param duration - duration of the freeze in milliseconds
33270
+ * @param [music=false] - also pause the current music track during the freeze
33271
+ * @returns a Promise that resolves once the freeze ends (or is cancelled)
33272
+ * @example
33273
+ * // simple hit-stop on impact
33274
+ * app.freeze(80);
33275
+ *
33276
+ * // chain VFX after the freeze
33277
+ * await app.freeze(120);
33278
+ * spawnImpactParticles();
33279
+ */
33280
+ freeze(duration, music = false) {
33281
+ return state_default.freeze(duration, music);
33282
+ }
31110
33283
  /** @ignore */
31111
33284
  _tick(time) {
31112
33285
  this.update(time);
@@ -31179,197 +33352,42 @@ function setDefaultGame(app) {
31179
33352
  game = app;
31180
33353
  }
31181
33354
 
31182
- // src/particles/settings.js
31183
- var ParticleEmitterSettings = {
31184
- /**
31185
- * Width of the particle spawn area.
31186
- * @type {number}
31187
- * @default 1
31188
- */
33355
+ // src/particles/settings.ts
33356
+ var defaultParticleEmitterSettings = {
31189
33357
  width: 1,
31190
- /**
31191
- * Height of the particle spawn area
31192
- * @type {number}
31193
- * @default 1
31194
- */
31195
33358
  height: 1,
31196
- /**
31197
- * image used for particles texture
31198
- * (by default melonJS will create a white 8x8 texture image)
31199
- * @type {HTMLCanvasElement}
31200
- * @default undefined
31201
- * @see {@link textureSize}
31202
- */
31203
33359
  image: void 0,
31204
- /**
31205
- * default texture size used for particles if no image is specified
31206
- * (by default melonJS will create a white 8x8 texture image)
31207
- * @type {number}
31208
- * @default 8
31209
- * @see {@link image}
31210
- */
31211
33360
  textureSize: 8,
31212
- /**
31213
- * tint to be applied to particles
31214
- * @type {string}
31215
- * @default "#fff"
31216
- */
31217
33361
  tint: "#fff",
31218
- /**
31219
- * Total number of particles in the emitter
31220
- * @type {number}
31221
- * @default 50
31222
- */
31223
33362
  totalParticles: 50,
31224
- /**
31225
- * Start angle for particle launch in Radians
31226
- * @type {number}
31227
- * @default Math.PI / 2
31228
- */
31229
33363
  angle: Math.PI / 2,
31230
- /**
31231
- * letiation in the start angle for particle launch in Radians.
31232
- * @type {number}
31233
- * @default 0
31234
- */
31235
33364
  angleVariation: 0,
31236
- /**
31237
- * Minimum time each particle lives once it is emitted in ms.
31238
- * @type {number}
31239
- * @default 1000
31240
- */
31241
33365
  minLife: 1e3,
31242
- /**
31243
- * Maximum time each particle lives once it is emitted in ms.
31244
- * @type {number}
31245
- * @default 3000
31246
- */
31247
33366
  maxLife: 3e3,
31248
- /**
31249
- * Start speed of particles.<br>
31250
- * @type {number}
31251
- * @default 2
31252
- */
31253
33367
  speed: 2,
31254
- /**
31255
- * letiation in the start speed of particles
31256
- * @type {number}
31257
- * @default 1
31258
- */
31259
33368
  speedVariation: 1,
31260
- /**
31261
- * Minimum start rotation for particles sprites in Radians
31262
- * @type {number}
31263
- * @default 0
31264
- */
31265
33369
  minRotation: 0,
31266
- /**
31267
- * Maximum start rotation for particles sprites in Radians
31268
- * @type {number}
31269
- * @default 0
31270
- */
31271
33370
  maxRotation: 0,
31272
- /**
31273
- * Minimum start scale ratio for particles (1 = no scaling)
31274
- * @type {number}
31275
- * @default 1
31276
- */
31277
33371
  minStartScale: 1,
31278
- /**
31279
- * Maximum start scale ratio for particles (1 = no scaling)
31280
- * @type {number}
31281
- * @default 1
31282
- */
31283
33372
  maxStartScale: 1,
31284
- /**
31285
- * Minimum end scale ratio for particles
31286
- * @type {number}
31287
- * @default 0
31288
- */
31289
33373
  minEndScale: 0,
31290
- /**
31291
- * Maximum end scale ratio for particles
31292
- * @type {number}
31293
- * @default 0
31294
- */
31295
33374
  maxEndScale: 0,
31296
- /**
31297
- * Vertical force (Gravity) for each particle
31298
- * @type {number}
31299
- * @default 0
31300
- * @see {@link World.gravity}
31301
- */
31302
33375
  gravity: 0,
31303
- /**
31304
- * Horizontal force (like a Wind) for each particle
31305
- * @type {number}
31306
- * @default 0
31307
- */
31308
33376
  wind: 0,
31309
- /**
31310
- * Update the rotation of particle in accordance the particle trajectory.<br>
31311
- * The particle sprite should aim at zero angle (draw from left to right).<br>
31312
- * Override the particle minRotation and maxRotation.<br>
31313
- * @type {boolean}
31314
- * @default false
31315
- */
31316
33377
  followTrajectory: false,
31317
- /**
31318
- * Enable the Texture Additive by composite operation ("additive" blendMode)
31319
- * @type {boolean}
31320
- * @default false
31321
- * @see {@link blendMode}
31322
- */
31323
33378
  textureAdditive: false,
31324
- /**
31325
- * the blend mode to be applied when rendering particles.
31326
- * (note: this will superseed the `textureAdditive` setting if different than "normal")
31327
- * @type {string}
31328
- * @default normal
31329
- * @see {@link CanvasRenderer#setBlendMode}
31330
- * @see {@link WebGLRenderer#setBlendMode}
31331
- */
31332
33379
  blendMode: "normal",
31333
- /**
31334
- * Update particles only in the viewport, remove it when out of viewport.
31335
- * @type {boolean}
31336
- * @default true
31337
- */
31338
33380
  onlyInViewport: true,
31339
- /**
31340
- * Render particles in screen space.
31341
- * @type {boolean}
31342
- * @default false
31343
- */
31344
33381
  floating: false,
31345
- /**
31346
- * Maximum number of particles launched each time in this emitter (used only if emitter is Stream).
31347
- * @type {number}
31348
- * @default 10
31349
- */
31350
33382
  maxParticles: 10,
31351
- /**
31352
- * How often a particle is emitted in ms (used only if emitter is a Stream).
31353
- * @type {number}
31354
- * @default 100
31355
- */
31356
33383
  frequency: 100,
31357
- /**
31358
- * Duration that the emitter releases particles in ms (used only if emitter is Stream).
31359
- * After this period, the emitter stop the launch of particles.
31360
- * @type {number}
31361
- * @default Infinity
31362
- */
31363
33384
  duration: Infinity,
31364
- /**
31365
- * Skip n frames after updating the particle system once.
31366
- * This can be used to reduce the performance impact of emitters with many particles.
31367
- * @type {number}
31368
- * @default 0
31369
- */
31370
- framesToSkip: 0
33385
+ framesToSkip: 0,
33386
+ accurateBounds: false,
33387
+ autoDestroyOnComplete: false,
33388
+ onComplete: void 0
31371
33389
  };
31372
- var settings_default = ParticleEmitterSettings;
33390
+ var settings_default = defaultParticleEmitterSettings;
31373
33391
 
31374
33392
  // src/particles/emitter.ts
31375
33393
  function createDefaultParticleTexture(w = 8, h = 8) {
@@ -31380,9 +33398,14 @@ function createDefaultParticleTexture(w = 8, h = 8) {
31380
33398
  defaultParticleTexture.context.fillRect(0, 0, w, h);
31381
33399
  return defaultParticleTexture;
31382
33400
  }
33401
+ function clampMinToMax(settings, minKey, maxKey) {
33402
+ if (settings[minKey] > settings[maxKey]) {
33403
+ settings[minKey] = settings[maxKey];
33404
+ }
33405
+ }
31383
33406
  var ParticleEmitter = class extends Container {
31384
33407
  /**
31385
- * the current (active) emitter settings
33408
+ * the current (active) emitter settings (with defaults merged in)
31386
33409
  */
31387
33410
  settings;
31388
33411
  /** @ignore */
@@ -31399,6 +33422,19 @@ var ParticleEmitter = class extends Container {
31399
33422
  _dt;
31400
33423
  /** @ignore */
31401
33424
  _defaultParticle;
33425
+ /**
33426
+ * whether at least one particle has been spawned by this emitter — used as
33427
+ * the precondition for completion detection (a brand-new emitter with zero
33428
+ * children must not count as "complete")
33429
+ * @ignore
33430
+ */
33431
+ _hasSpawned;
33432
+ /**
33433
+ * cached `timer.maxfps / 1000` — particles read this directly instead of
33434
+ * recomputing it on every spawn.
33435
+ * @ignore
33436
+ */
33437
+ _deltaInv;
31402
33438
  /**
31403
33439
  * @param x - x position of the particle emitter
31404
33440
  * @param y - y position of the particle emitter
@@ -31430,8 +33466,8 @@ var ParticleEmitter = class extends Container {
31430
33466
  * app.world.removeChild(emitter);
31431
33467
  */
31432
33468
  constructor(x, y, settings = {}) {
31433
- super(x, y, settings.width | 1, settings.height | 1);
31434
- this.settings = {};
33469
+ super(x, y, settings.width ?? 1, settings.height ?? 1);
33470
+ this.settings = { ...settings_default };
31435
33471
  this.centerOn(x, y);
31436
33472
  this._stream = false;
31437
33473
  this._frequencyTimer = 0;
@@ -31441,18 +33477,29 @@ var ParticleEmitter = class extends Container {
31441
33477
  this.autoSort = false;
31442
33478
  this._updateCount = 0;
31443
33479
  this._dt = 0;
33480
+ this._hasSpawned = false;
33481
+ this._deltaInv = timer_default.maxfps / 1e3;
31444
33482
  this.reset(settings);
31445
33483
  }
31446
33484
  /**
31447
33485
  * Reset the emitter with particle emitter settings.
31448
- * @param settings - [optional] object with emitter settings. See {@link ParticleEmitterSettings}
33486
+ * @param settings - object with emitter settings. See {@link ParticleEmitterSettings}
31449
33487
  */
31450
33488
  reset(settings = {}) {
31451
33489
  Object.assign(this.settings, settings_default, settings);
33490
+ clampMinToMax(this.settings, "minLife", "maxLife");
33491
+ clampMinToMax(this.settings, "minStartScale", "maxStartScale");
33492
+ clampMinToMax(this.settings, "minEndScale", "maxEndScale");
33493
+ clampMinToMax(this.settings, "minRotation", "maxRotation");
33494
+ this._deltaInv = timer_default.maxfps / 1e3;
33495
+ if (typeof this._defaultParticle !== "undefined") {
33496
+ this._defaultParticle.destroy();
33497
+ this._defaultParticle = void 0;
33498
+ }
31452
33499
  if (typeof this.settings.image === "undefined") {
31453
33500
  this._defaultParticle = createDefaultParticleTexture(
31454
- settings.textureSize,
31455
- settings.textureSize
33501
+ this.settings.textureSize,
33502
+ this.settings.textureSize
31456
33503
  );
31457
33504
  this.settings.image = this._defaultParticle.canvas;
31458
33505
  }
@@ -31476,8 +33523,12 @@ var ParticleEmitter = class extends Container {
31476
33523
  // Add count particles in the game world
31477
33524
  /** @ignore */
31478
33525
  addParticles(count) {
33526
+ const z = this.pos.z;
31479
33527
  for (let i = 0; i < count; i++) {
31480
- this.addChild(particlePool.get(this), this.pos.z);
33528
+ this.addChild(particlePool.get(this), z);
33529
+ }
33530
+ if (count > 0) {
33531
+ this._hasSpawned = true;
31481
33532
  }
31482
33533
  this.isDirty = true;
31483
33534
  }
@@ -31520,15 +33571,17 @@ var ParticleEmitter = class extends Container {
31520
33571
  * @ignore
31521
33572
  */
31522
33573
  update(dt) {
31523
- if (++this._updateCount > this.settings.framesToSkip) {
31524
- this._updateCount = 0;
31525
- }
31526
- if (this._updateCount > 0) {
31527
- this._dt += dt;
31528
- return this.isDirty;
33574
+ if (this.settings.framesToSkip > 0) {
33575
+ if (++this._updateCount > this.settings.framesToSkip) {
33576
+ this._updateCount = 0;
33577
+ }
33578
+ if (this._updateCount > 0) {
33579
+ this._dt += dt;
33580
+ return this.isDirty;
33581
+ }
33582
+ dt += this._dt;
33583
+ this._dt = 0;
31529
33584
  }
31530
- dt += this._dt;
31531
- this._dt = 0;
31532
33585
  this.isDirty = this.isDirty || super.update(dt);
31533
33586
  if (this._enabled && this._stream) {
31534
33587
  if (this._durationTimer !== Infinity) {
@@ -31539,17 +33592,27 @@ var ParticleEmitter = class extends Container {
31539
33592
  }
31540
33593
  }
31541
33594
  this._frequencyTimer += dt;
31542
- const particlesCount = this.children?.length ?? 0;
33595
+ const particlesCount = this.getChildren().length;
31543
33596
  if (particlesCount < this.settings.totalParticles && this._frequencyTimer >= this.settings.frequency) {
31544
- if (particlesCount + this.settings.maxParticles <= this.settings.totalParticles) {
31545
- this.addParticles(this.settings.maxParticles);
31546
- } else {
31547
- this.addParticles(this.settings.totalParticles - particlesCount);
31548
- }
33597
+ this.addParticles(
33598
+ Math.min(
33599
+ this.settings.maxParticles,
33600
+ this.settings.totalParticles - particlesCount
33601
+ )
33602
+ );
31549
33603
  this._frequencyTimer = 0;
31550
33604
  this.isDirty = true;
31551
33605
  }
31552
33606
  }
33607
+ if (this._hasSpawned && !this._enabled && this.getChildren().length === 0) {
33608
+ this._hasSpawned = false;
33609
+ if (typeof this.settings.onComplete === "function") {
33610
+ this.settings.onComplete.call(this);
33611
+ }
33612
+ if (this.settings.autoDestroyOnComplete && this.ancestor) {
33613
+ this.ancestor.removeChild(this);
33614
+ }
33615
+ }
31553
33616
  return this.isDirty;
31554
33617
  }
31555
33618
  /**
@@ -32957,98 +35020,6 @@ var UITextButton = class extends UIBaseElement {
32957
35020
  }
32958
35021
  };
32959
35022
 
32960
- // src/video/webgl/shadereffect.js
32961
- var ShaderEffect = class {
32962
- /**
32963
- * whether this effect is active (false in Canvas mode)
32964
- * @type {boolean}
32965
- */
32966
- enabled = false;
32967
- /**
32968
- * @param {WebGLRenderer|CanvasRenderer} renderer - the current renderer instance
32969
- * @param {string} fragmentBody - GLSL code containing a `vec4 apply(vec4 color, vec2 uv)` function
32970
- * that receives the sampled pixel color and UV coordinates, and returns the modified color.
32971
- * You can declare additional `uniform` variables before the `apply()` function.
32972
- * @param {string} [precision=auto detected] - float precision ('lowp', 'mediump' or 'highp')
32973
- */
32974
- constructor(renderer2, fragmentBody, precision) {
32975
- if (typeof renderer2.gl === "undefined") {
32976
- console.warn(
32977
- "ShaderEffect requires WebGL and is disabled in Canvas mode"
32978
- );
32979
- return;
32980
- }
32981
- const fragment = [
32982
- "uniform sampler2D uSampler;",
32983
- "varying vec4 vColor;",
32984
- "varying vec2 vRegion;",
32985
- // user-provided fragment body (uniforms + apply function)
32986
- fragmentBody,
32987
- "void main(void) {",
32988
- " vec4 texColor = texture2D(uSampler, vRegion) * vColor;",
32989
- " gl_FragColor = apply(texColor, vRegion);",
32990
- "}"
32991
- ].join("\n");
32992
- this._shader = new GLShader(renderer2.gl, quad_default2, fragment, precision);
32993
- this.enabled = true;
32994
- }
32995
- /**
32996
- * Set the uniform to the given value
32997
- * @param {string} name - the uniform name
32998
- * @param {object|Float32Array} value - the value to assign to that uniform
32999
- */
33000
- setUniform(name, value) {
33001
- if (this.enabled) {
33002
- this._shader.setUniform(name, value);
33003
- }
33004
- }
33005
- /** @ignore */
33006
- bind() {
33007
- if (this.enabled) {
33008
- this._shader.bind();
33009
- }
33010
- }
33011
- /** @ignore */
33012
- getAttribLocation(name) {
33013
- return this.enabled ? this._shader.getAttribLocation(name) : -1;
33014
- }
33015
- /** @ignore */
33016
- setVertexAttributes(gl, attributes, stride) {
33017
- if (this.enabled) {
33018
- this._shader.setVertexAttributes(gl, attributes, stride);
33019
- }
33020
- }
33021
- /** @ignore */
33022
- get program() {
33023
- return this.enabled ? this._shader.program : null;
33024
- }
33025
- /** @ignore */
33026
- get vertex() {
33027
- return this.enabled ? this._shader.vertex : null;
33028
- }
33029
- /** @ignore */
33030
- get fragment() {
33031
- return this.enabled ? this._shader.fragment : null;
33032
- }
33033
- /** @ignore */
33034
- get attributes() {
33035
- return this.enabled ? this._shader.attributes : {};
33036
- }
33037
- /** @ignore */
33038
- get uniforms() {
33039
- return this.enabled ? this._shader.uniforms : {};
33040
- }
33041
- /**
33042
- * destroy this shader effect
33043
- */
33044
- destroy() {
33045
- if (this.enabled) {
33046
- this._shader.destroy();
33047
- this.enabled = false;
33048
- }
33049
- }
33050
- };
33051
-
33052
35023
  // src/video/webgl/effects/blur.js
33053
35024
  var BlurEffect = class extends ShaderEffect {
33054
35025
  /**
@@ -33151,25 +35122,16 @@ var ChromaticAberrationEffect = class extends ShaderEffect {
33151
35122
  };
33152
35123
 
33153
35124
  // src/video/webgl/effects/desaturate.js
33154
- var DesaturateEffect = class extends ShaderEffect {
35125
+ var DesaturateEffect = class extends ColorMatrixEffect {
33155
35126
  /**
33156
35127
  * @param {import("../webgl_renderer.js").default} renderer - the current renderer instance
33157
35128
  * @param {object} [options] - effect options
33158
35129
  * @param {number} [options.intensity=1.0] - desaturation intensity (0.0 = full color, 1.0 = grayscale)
33159
35130
  */
33160
35131
  constructor(renderer2, options = {}) {
33161
- super(
33162
- renderer2,
33163
- `
33164
- uniform float uDesatIntensity;
33165
- vec4 apply(vec4 color, vec2 uv) {
33166
- float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
33167
- return vec4(mix(color.rgb, vec3(gray), uDesatIntensity), color.a);
33168
- }
33169
- `
33170
- );
35132
+ super(renderer2);
33171
35133
  this.intensity = typeof options.intensity === "number" ? options.intensity : 1;
33172
- this.setUniform("uDesatIntensity", this.intensity);
35134
+ this.saturate(1 - this.intensity);
33173
35135
  }
33174
35136
  /**
33175
35137
  * set the desaturation intensity
@@ -33177,7 +35139,7 @@ var DesaturateEffect = class extends ShaderEffect {
33177
35139
  */
33178
35140
  setIntensity(value) {
33179
35141
  this.intensity = Math.max(0, Math.min(1, value));
33180
- this.setUniform("uDesatIntensity", this.intensity);
35142
+ this.reset().saturate(1 - this.intensity);
33181
35143
  }
33182
35144
  };
33183
35145
 
@@ -33488,25 +35450,16 @@ var HologramEffect = class extends ShaderEffect {
33488
35450
  };
33489
35451
 
33490
35452
  // src/video/webgl/effects/invert.js
33491
- var InvertEffect = class extends ShaderEffect {
35453
+ var InvertEffect = class extends ColorMatrixEffect {
33492
35454
  /**
33493
35455
  * @param {import("../webgl_renderer.js").default} renderer - the current renderer instance
33494
35456
  * @param {object} [options] - effect options
33495
35457
  * @param {number} [options.intensity=1.0] - inversion intensity (0.0 = original, 1.0 = fully inverted)
33496
35458
  */
33497
35459
  constructor(renderer2, options = {}) {
33498
- super(
33499
- renderer2,
33500
- `
33501
- uniform float uInvertIntensity;
33502
- vec4 apply(vec4 color, vec2 uv) {
33503
- vec3 inverted = vec3(color.a) - color.rgb;
33504
- return vec4(mix(color.rgb, inverted, uInvertIntensity), color.a);
33505
- }
33506
- `
33507
- );
35460
+ super(renderer2);
33508
35461
  this.intensity = typeof options.intensity === "number" ? options.intensity : 1;
33509
- this.setUniform("uInvertIntensity", this.intensity);
35462
+ this.invertColors(this.intensity);
33510
35463
  }
33511
35464
  /**
33512
35465
  * set the inversion intensity
@@ -33514,7 +35467,7 @@ var InvertEffect = class extends ShaderEffect {
33514
35467
  */
33515
35468
  setIntensity(value) {
33516
35469
  this.intensity = Math.max(0, Math.min(1, value));
33517
- this.setUniform("uInvertIntensity", this.intensity);
35470
+ this.reset().invertColors(this.intensity);
33518
35471
  }
33519
35472
  };
33520
35473
 
@@ -33704,28 +35657,16 @@ var ScanlineEffect = class extends ShaderEffect {
33704
35657
  };
33705
35658
 
33706
35659
  // src/video/webgl/effects/sepia.js
33707
- var SepiaEffect = class extends ShaderEffect {
35660
+ var SepiaEffect = class extends ColorMatrixEffect {
33708
35661
  /**
33709
35662
  * @param {import("../webgl_renderer.js").default} renderer - the current renderer instance
33710
35663
  * @param {object} [options] - effect options
33711
35664
  * @param {number} [options.intensity=1.0] - sepia intensity (0.0 = original, 1.0 = full sepia)
33712
35665
  */
33713
35666
  constructor(renderer2, options = {}) {
33714
- super(
33715
- renderer2,
33716
- `
33717
- uniform float uSepiaIntensity;
33718
- vec4 apply(vec4 color, vec2 uv) {
33719
- vec3 sepia;
33720
- sepia.r = dot(color.rgb, vec3(0.393, 0.769, 0.189));
33721
- sepia.g = dot(color.rgb, vec3(0.349, 0.686, 0.168));
33722
- sepia.b = dot(color.rgb, vec3(0.272, 0.534, 0.131));
33723
- return vec4(mix(color.rgb, sepia, uSepiaIntensity), color.a);
33724
- }
33725
- `
33726
- );
35667
+ super(renderer2);
33727
35668
  this.intensity = typeof options.intensity === "number" ? options.intensity : 1;
33728
- this.setUniform("uSepiaIntensity", this.intensity);
35669
+ this.sepia(this.intensity);
33729
35670
  }
33730
35671
  /**
33731
35672
  * set the sepia intensity
@@ -33733,7 +35674,7 @@ var SepiaEffect = class extends ShaderEffect {
33733
35674
  */
33734
35675
  setIntensity(value) {
33735
35676
  this.intensity = Math.max(0, Math.min(1, value));
33736
- this.setUniform("uSepiaIntensity", this.intensity);
35677
+ this.reset().sepia(this.intensity);
33737
35678
  }
33738
35679
  };
33739
35680
 
@@ -33782,6 +35723,50 @@ var TintPulseEffect = class extends ShaderEffect {
33782
35723
  }
33783
35724
  };
33784
35725
 
35726
+ // src/video/webgl/effects/vignette.js
35727
+ var VignetteEffect = class extends ShaderEffect {
35728
+ /**
35729
+ * @param {import("../webgl_renderer.js").default} renderer - the current renderer instance
35730
+ * @param {object} [options] - effect options
35731
+ * @param {number} [options.strength=0.15] - edge darkening power (lower = stronger darkening, higher = softer falloff)
35732
+ * @param {number} [options.size=25.0] - vignette spread multiplier (higher = smaller dark area)
35733
+ */
35734
+ constructor(renderer2, options = {}) {
35735
+ super(
35736
+ renderer2,
35737
+ `
35738
+ uniform float uStrength;
35739
+ uniform float uSize;
35740
+ vec4 apply(vec4 color, vec2 uv) {
35741
+ vec2 vig = uv * (1.0 - uv);
35742
+ float v = clamp(pow(vig.x * vig.y * uSize, uStrength), 0.0, 1.0);
35743
+ return vec4(color.rgb * v, color.a);
35744
+ }
35745
+ `
35746
+ );
35747
+ this.strength = options.strength ?? 0.15;
35748
+ this.size = options.size ?? 25;
35749
+ this.setUniform("uStrength", this.strength);
35750
+ this.setUniform("uSize", this.size);
35751
+ }
35752
+ /**
35753
+ * set the vignette strength
35754
+ * @param {number} strength - edge darkening power (lower = stronger, higher = softer)
35755
+ */
35756
+ setStrength(strength) {
35757
+ this.strength = Math.max(0, strength);
35758
+ this.setUniform("uStrength", this.strength);
35759
+ }
35760
+ /**
35761
+ * set the vignette size
35762
+ * @param {number} size - spread multiplier (higher = smaller dark area)
35763
+ */
35764
+ setSize(size) {
35765
+ this.size = Math.max(0, size);
35766
+ this.setUniform("uSize", this.size);
35767
+ }
35768
+ };
35769
+
33785
35770
  // src/video/webgl/effects/wave.js
33786
35771
  var WaveEffect = class extends ShaderEffect {
33787
35772
  /**
@@ -34366,6 +36351,7 @@ export {
34366
36351
  Bounds,
34367
36352
  CANVAS,
34368
36353
  Camera2d,
36354
+ CameraEffect,
34369
36355
  canvasrendertarget_default as CanvasRenderTarget,
34370
36356
  CanvasRenderer,
34371
36357
  CanvasTexture,
@@ -34373,6 +36359,8 @@ export {
34373
36359
  Collectable,
34374
36360
  Color,
34375
36361
  ColorLayer,
36362
+ ColorMatrix,
36363
+ ColorMatrixEffect,
34376
36364
  Compositor,
34377
36365
  Container,
34378
36366
  DesaturateEffect,
@@ -34382,6 +36370,7 @@ export {
34382
36370
  DropTarget,
34383
36371
  Ellipse,
34384
36372
  Entity,
36373
+ FadeEffect,
34385
36374
  FlashEffect,
34386
36375
  GLShader,
34387
36376
  GlowEffect,
@@ -34391,6 +36380,7 @@ export {
34391
36380
  InvertEffect,
34392
36381
  Light2d,
34393
36382
  Line,
36383
+ MaskEffect,
34394
36384
  math_exports as Math,
34395
36385
  Matrix2d,
34396
36386
  Matrix3d,
@@ -34414,6 +36404,7 @@ export {
34414
36404
  QuadTree,
34415
36405
  Rect,
34416
36406
  RenderState,
36407
+ RenderTarget,
34417
36408
  Renderable,
34418
36409
  Renderer,
34419
36410
  RoundRect,
@@ -34421,6 +36412,7 @@ export {
34421
36412
  ScanlineEffect,
34422
36413
  SepiaEffect,
34423
36414
  ShaderEffect,
36415
+ ShakeEffect,
34424
36416
  Sprite,
34425
36417
  Stage,
34426
36418
  TMXHexagonalRenderer,
@@ -34445,6 +36437,7 @@ export {
34445
36437
  UITextButton,
34446
36438
  Vector2d,
34447
36439
  Vector3d,
36440
+ VignetteEffect,
34448
36441
  WEBGL,
34449
36442
  WaveEffect,
34450
36443
  WebGLRenderer,