melonjs 19.3.0 → 19.4.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 (47) hide show
  1. package/README.md +1 -0
  2. package/build/application/application.d.ts.map +1 -1
  3. package/build/application/defaultApplicationSettings.d.ts +1 -0
  4. package/build/application/defaultApplicationSettings.d.ts.map +1 -1
  5. package/build/application/settings.d.ts +12 -0
  6. package/build/application/settings.d.ts.map +1 -1
  7. package/build/index.js +1063 -121
  8. package/build/index.js.map +4 -4
  9. package/build/input/pointerevent.d.ts.map +1 -1
  10. package/build/level/tiled/TMXLayer.d.ts +72 -5
  11. package/build/level/tiled/TMXLayer.d.ts.map +1 -1
  12. package/build/level/tiled/TMXTile.d.ts +19 -1
  13. package/build/level/tiled/TMXTile.d.ts.map +1 -1
  14. package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
  15. package/build/level/tiled/TMXTileset.d.ts +15 -0
  16. package/build/level/tiled/TMXTileset.d.ts.map +1 -1
  17. package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts +7 -1
  18. package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
  19. package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts +7 -1
  20. package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
  21. package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
  22. package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts +7 -1
  23. package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
  24. package/build/physics/sat.d.ts.map +1 -1
  25. package/build/physics/world.d.ts +16 -0
  26. package/build/physics/world.d.ts.map +1 -1
  27. package/build/renderable/imagelayer.d.ts.map +1 -1
  28. package/build/utils/function.d.ts +1 -1
  29. package/build/utils/function.d.ts.map +1 -1
  30. package/build/video/renderer.d.ts +15 -1
  31. package/build/video/renderer.d.ts.map +1 -1
  32. package/build/video/texture/cache.d.ts.map +1 -1
  33. package/build/video/texture/resource.d.ts +113 -0
  34. package/build/video/texture/resource.d.ts.map +1 -0
  35. package/build/video/webgl/batchers/material_batcher.d.ts +8 -1
  36. package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
  37. package/build/video/webgl/renderers/tmxlayer/orthogonal.d.ts +108 -0
  38. package/build/video/webgl/renderers/tmxlayer/orthogonal.d.ts.map +1 -0
  39. package/build/video/webgl/utils/attributes.d.ts +7 -0
  40. package/build/video/webgl/utils/attributes.d.ts.map +1 -1
  41. package/build/video/webgl/utils/precision.d.ts +1 -1
  42. package/build/video/webgl/utils/precision.d.ts.map +1 -1
  43. package/build/video/webgl/utils/uniforms.d.ts +13 -0
  44. package/build/video/webgl/utils/uniforms.d.ts.map +1 -1
  45. package/build/video/webgl/webgl_renderer.d.ts +19 -0
  46. package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
  47. package/package.json +1 -1
package/build/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * melonJS Game Engine - 19.3.0
2
+ * melonJS Game Engine - 19.4.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
@@ -6249,6 +6249,7 @@ var defaultApplicationSettings = {
6249
6249
  consoleHeader: true,
6250
6250
  blendMode: "normal",
6251
6251
  physic: "builtin",
6252
+ gpuTilemap: true,
6252
6253
  failIfMajorPerformanceCaveat: true,
6253
6254
  highPrecisionShader: true,
6254
6255
  subPixel: false,
@@ -15792,6 +15793,41 @@ var Renderer = class {
15792
15793
  // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
15793
15794
  drawLight(light) {
15794
15795
  }
15796
+ /**
15797
+ * Draw a TMX tile layer. Default behavior:
15798
+ * - if `layer.canvasRenderer` is set (preRender bake), blit the cached
15799
+ * offscreen canvas in a single `drawImage` call;
15800
+ * - otherwise delegate to the layer's TMX orientation renderer for
15801
+ * the per-tile loop.
15802
+ *
15803
+ * `WebGLRenderer` overrides this to add the procedural shader fast
15804
+ * path on top (when `layer.renderMode === "shader"`) and fall through
15805
+ * to this base behavior for all other layers.
15806
+ * @param {object} layer - the TMXLayer to draw
15807
+ * @param {Rect} rect - the visible region in world coords
15808
+ */
15809
+ drawTileLayer(layer, rect) {
15810
+ if (layer.canvasRenderer) {
15811
+ const width = Math.min(rect.width, layer.width - rect.pos.x);
15812
+ const height = Math.min(rect.height, layer.height - rect.pos.y);
15813
+ if (width <= 0 || height <= 0) {
15814
+ return;
15815
+ }
15816
+ this.drawImage(
15817
+ layer.canvasRenderer.getCanvas(),
15818
+ rect.pos.x,
15819
+ rect.pos.y,
15820
+ width,
15821
+ height,
15822
+ rect.pos.x,
15823
+ rect.pos.y,
15824
+ width,
15825
+ height
15826
+ );
15827
+ return;
15828
+ }
15829
+ layer.getRenderer().drawTileLayer(this, layer, rect);
15830
+ }
15795
15831
  /**
15796
15832
  * Set the current fill & stroke style color.
15797
15833
  * By default, or upon reset, the value is set to #000000.
@@ -16890,14 +16926,6 @@ var TextureCache = class {
16890
16926
  * cache the textureAltas for the given image
16891
16927
  */
16892
16928
  set(image, textureAtlas) {
16893
- const width = image.width || image.videoWidth;
16894
- const height = image.height || image.videoHeight;
16895
- if (this.renderer.WebGLVersion === 1 && (!isPowerOfTwo(width) || !isPowerOfTwo(height))) {
16896
- const src = typeof image.src !== "undefined" ? image.src : image;
16897
- console.warn(
16898
- "[Texture] " + src + " is not a POT texture (" + width + "x" + height + ")"
16899
- );
16900
- }
16901
16929
  return this.cache.put(image, textureAtlas);
16902
16930
  }
16903
16931
  /**
@@ -18073,6 +18101,30 @@ var CanvasRenderer = class extends Renderer {
18073
18101
  };
18074
18102
 
18075
18103
  // src/level/tiled/TMXTile.js
18104
+ var FLIP_H_BIT = 1 << 0;
18105
+ var FLIP_V_BIT = 1 << 1;
18106
+ var FLIP_AD_BIT = 1 << 2;
18107
+ function buildFlipTransform(transform, flipMask, width, height) {
18108
+ const halfW = width / 2;
18109
+ const halfH = height / 2;
18110
+ const flippedH = (flipMask & FLIP_H_BIT) !== 0;
18111
+ const flippedV = (flipMask & FLIP_V_BIT) !== 0;
18112
+ const flippedAD = (flipMask & FLIP_AD_BIT) !== 0;
18113
+ transform.identity();
18114
+ transform.translate(halfW, halfH);
18115
+ if (flippedAD) {
18116
+ transform.rotate(degToRad(-90));
18117
+ transform.scale(-1, 1);
18118
+ }
18119
+ if (flippedH) {
18120
+ transform.scale(flippedAD ? 1 : -1, flippedAD ? -1 : 1);
18121
+ }
18122
+ if (flippedV) {
18123
+ transform.scale(flippedAD ? -1 : 1, flippedAD ? 1 : -1);
18124
+ }
18125
+ transform.translate(-halfW, -halfH);
18126
+ return transform;
18127
+ }
18076
18128
  var Tile = class extends Bounds {
18077
18129
  /**
18078
18130
  * @param {number} x - x index of the Tile in the map
@@ -18623,12 +18675,17 @@ function applyTMXProperties(obj, data2) {
18623
18675
  }
18624
18676
 
18625
18677
  // src/level/tiled/TMXLayer.js
18626
- function initArray(rows, cols) {
18627
- const array = new Array(cols);
18628
- for (let col = 0; col < cols; col++) {
18629
- array[col] = new Array(rows).fill(null);
18630
- }
18631
- return array;
18678
+ var FLIP_H_BIT2 = 1 << 0;
18679
+ var FLIP_V_BIT2 = 1 << 1;
18680
+ var FLIP_AD_BIT2 = 1 << 2;
18681
+ function flipMaskFromGid(gid) {
18682
+ return (gid & TMX_FLIP_H ? FLIP_H_BIT2 : 0) | (gid & TMX_FLIP_V ? FLIP_V_BIT2 : 0) | (gid & TMX_FLIP_AD ? FLIP_AD_BIT2 : 0);
18683
+ }
18684
+ function flipMaskFromTile(tile) {
18685
+ return (tile.flippedX ? FLIP_H_BIT2 : 0) | (tile.flippedY ? FLIP_V_BIT2 : 0) | (tile.flippedAD ? FLIP_AD_BIT2 : 0);
18686
+ }
18687
+ function gidWithFlips(gid, flipMask) {
18688
+ return gid | (flipMask & FLIP_H_BIT2 ? TMX_FLIP_H : 0) | (flipMask & FLIP_V_BIT2 ? TMX_FLIP_V : 0) | (flipMask & FLIP_AD_BIT2 ? TMX_FLIP_AD : 0);
18632
18689
  }
18633
18690
  function setLayerData(layer, bounds, data2) {
18634
18691
  let idx = 0;
@@ -18641,18 +18698,30 @@ function setLayerData(layer, bounds, data2) {
18641
18698
  width = bounds.cols;
18642
18699
  height = bounds.rows;
18643
18700
  }
18701
+ const cols = layer.cols;
18702
+ const layerData = layer.layerData;
18703
+ const offsetX = bounds.x;
18704
+ const offsetY = bounds.y;
18705
+ let overflowedGid = 0;
18644
18706
  for (let y = 0; y < height; y++) {
18645
18707
  for (let x = 0; x < width; x++) {
18646
- const gid = data2[idx++];
18647
- if (gid !== 0) {
18648
- layer.layerData[x + bounds.x][y + bounds.y] = layer.getTileById(
18649
- gid,
18650
- x + bounds.x,
18651
- y + bounds.y
18652
- );
18708
+ const rawGid = data2[idx++];
18709
+ if (rawGid !== 0) {
18710
+ const flatIdx = ((y + offsetY) * cols + (x + offsetX)) * 2;
18711
+ const cleanGid = rawGid & TMX_CLEAR_BIT_MASK;
18712
+ if (cleanGid > 65535 && overflowedGid === 0) {
18713
+ overflowedGid = cleanGid;
18714
+ }
18715
+ layerData[flatIdx] = cleanGid;
18716
+ layerData[flatIdx + 1] = flipMaskFromGid(rawGid);
18653
18717
  }
18654
18718
  }
18655
18719
  }
18720
+ if (overflowedGid !== 0) {
18721
+ console.warn(
18722
+ "melonJS: TMX layer contains GID " + overflowedGid + " which exceeds the 16-bit cell capacity (max 65535). Tiles will be truncated and render incorrectly."
18723
+ );
18724
+ }
18656
18725
  }
18657
18726
  var TMXLayer = class extends Renderable {
18658
18727
  /**
@@ -18712,7 +18781,10 @@ var TMXLayer = class extends Renderable {
18712
18781
  }
18713
18782
  applyTMXProperties(this, data2);
18714
18783
  this.setRenderer(map.getRenderer());
18715
- this.layerData = initArray(this.rows, this.cols);
18784
+ this.layerData = new Uint16Array(this.cols * this.rows * 2);
18785
+ this.cachedTile = null;
18786
+ this.dataVersion = 0;
18787
+ this.renderMode = "auto";
18716
18788
  if (map.infinite === 0) {
18717
18789
  setLayerData(
18718
18790
  this,
@@ -18742,12 +18814,8 @@ var TMXLayer = class extends Renderable {
18742
18814
  }
18743
18815
  }
18744
18816
  this.isAnimated = this.animatedTilesets.length > 0;
18745
- if (typeof this.preRender === "undefined" && this.isAnimated === false) {
18746
- this.preRender = this.ancestor.getRootAncestor().preRender;
18747
- } else {
18748
- this.preRender = false;
18749
- }
18750
- if (this.preRender === true && !this.canvasRenderer) {
18817
+ this._resolveRenderMode();
18818
+ if (this.renderMode === "prerender" && !this.canvasRenderer) {
18751
18819
  this.canvasRenderer = new CanvasRenderer({
18752
18820
  canvas: createCanvas(this.width, this.height),
18753
18821
  width: this.width,
@@ -18756,8 +18824,109 @@ var TMXLayer = class extends Renderable {
18756
18824
  });
18757
18825
  this.getRenderer().drawTileLayer(this.canvasRenderer, this, this);
18758
18826
  }
18827
+ this.preRender = this.renderMode === "prerender";
18759
18828
  this.isDirty = true;
18760
18829
  }
18830
+ /**
18831
+ * Resolve `this.renderMode` to one of "shader" / "prerender" / "perTile"
18832
+ * based on eligibility checks and user/world hints. Emits a single
18833
+ * `console.warn` at activation when a forced mode is ineligible, or
18834
+ * when an auto-eligible mode falls back due to a layer feature the GPU
18835
+ * path doesn't support (orientation, collection-of-image tileset, etc.).
18836
+ * @ignore
18837
+ */
18838
+ _resolveRenderMode() {
18839
+ const root = this.ancestor?.getRootAncestor?.();
18840
+ const renderer2 = this.parentApp?.renderer;
18841
+ const gpuAllowed = root?.gpuTilemap !== false;
18842
+ const preRenderHint = typeof this.preRender === "boolean" ? this.preRender : root?.preRender;
18843
+ const elig = this._checkShaderEligibility(renderer2, gpuAllowed);
18844
+ const requested = this.renderMode;
18845
+ if (requested === "shader") {
18846
+ if (elig.ok) {
18847
+ return;
18848
+ }
18849
+ console.warn(
18850
+ `melonJS: layer "${this.name}" forced renderMode "shader" not available (${elig.reason}) \u2014 falling back to perTile`
18851
+ );
18852
+ this.renderMode = "perTile";
18853
+ return;
18854
+ }
18855
+ if (requested === "prerender") {
18856
+ if (this.isAnimated) {
18857
+ console.warn(
18858
+ `melonJS: layer "${this.name}" forced renderMode "prerender" disabled (layer has animated tiles) \u2014 falling back to perTile`
18859
+ );
18860
+ this.renderMode = "perTile";
18861
+ }
18862
+ return;
18863
+ }
18864
+ if (requested === "perTile") {
18865
+ return;
18866
+ }
18867
+ if (elig.ok) {
18868
+ this.renderMode = "shader";
18869
+ return;
18870
+ }
18871
+ if (gpuAllowed && elig.reason !== "no-webgl2-renderer") {
18872
+ console.warn(
18873
+ `melonJS: layer "${this.name}" using legacy tile renderer (${elig.reason})`
18874
+ );
18875
+ }
18876
+ if (preRenderHint && !this.isAnimated) {
18877
+ this.renderMode = "prerender";
18878
+ return;
18879
+ }
18880
+ this.renderMode = "perTile";
18881
+ }
18882
+ /**
18883
+ * Check whether this layer is eligible for the WebGL2 shader path.
18884
+ * @param {object} renderer
18885
+ * @param {boolean} gpuAllowed - whether `gpuTilemap` is enabled at the world level
18886
+ * @returns {{ok: boolean, reason?: string}}
18887
+ * @ignore
18888
+ */
18889
+ _checkShaderEligibility(renderer2, gpuAllowed) {
18890
+ if (!gpuAllowed) {
18891
+ return { ok: false, reason: "gpuTilemap disabled" };
18892
+ }
18893
+ if (!renderer2 || renderer2.WebGLVersion !== 2) {
18894
+ return { ok: false, reason: "no-webgl2-renderer" };
18895
+ }
18896
+ if (this.orientation !== "orthogonal") {
18897
+ return {
18898
+ ok: false,
18899
+ reason: `no gpu renderer supported yet for "${this.orientation}" orientation`
18900
+ };
18901
+ }
18902
+ if (!this.tilesets || this.tilesets.tilesets.length === 0) {
18903
+ return { ok: false, reason: "no tilesets" };
18904
+ }
18905
+ const MAX_OVERFLOW_CELLS = 4;
18906
+ for (const ts of this.tilesets.tilesets) {
18907
+ if (ts.isCollection) {
18908
+ return { ok: false, reason: "collection-of-image tileset" };
18909
+ }
18910
+ if (ts.tilerendersize !== "tile") {
18911
+ return {
18912
+ ok: false,
18913
+ reason: `tilerendersize "${ts.tilerendersize}" not supported`
18914
+ };
18915
+ }
18916
+ if (ts.tileoffset.x !== 0 || ts.tileoffset.y !== 0) {
18917
+ return { ok: false, reason: "non-zero tileoffset" };
18918
+ }
18919
+ const overflowX = Math.ceil(ts.tilewidth / this.tilewidth) - 1;
18920
+ const overflowY = Math.ceil(ts.tileheight / this.tileheight) - 1;
18921
+ if (overflowX > MAX_OVERFLOW_CELLS || overflowY > MAX_OVERFLOW_CELLS) {
18922
+ return {
18923
+ ok: false,
18924
+ reason: `tile overflow exceeds shader limit (${MAX_OVERFLOW_CELLS} cells)`
18925
+ };
18926
+ }
18927
+ }
18928
+ return { ok: true };
18929
+ }
18761
18930
  // called when the layer is removed from the game world or a container
18762
18931
  onDeactivateEvent() {
18763
18932
  this.animatedTilesets = void 0;
@@ -18818,12 +18987,29 @@ var TMXLayer = class extends Renderable {
18818
18987
  /**
18819
18988
  * assign the given Tile object to the specified position
18820
18989
  * @param {Tile} tile - the tile object to be assigned
18821
- * @param {number} x - x coordinate (in world/pixels coordinates)
18822
- * @param {number} y - y coordinate (in world/pixels coordinates)
18990
+ * @param {number} x - x coordinate (in tile/column coordinates)
18991
+ * @param {number} y - y coordinate (in tile/row coordinates)
18823
18992
  * @returns {Tile} the tile object
18824
18993
  */
18825
18994
  setTile(tile, x, y) {
18826
- this.layerData[x][y] = tile;
18995
+ if (x < 0 || x >= this.cols || y < 0 || y >= this.rows) {
18996
+ return tile;
18997
+ }
18998
+ const slot = y * this.cols + x;
18999
+ const idx = slot * 2;
19000
+ const cleanGid = tile.tileId & TMX_CLEAR_BIT_MASK;
19001
+ if (cleanGid > 65535 && !this._truncationWarned) {
19002
+ this._truncationWarned = true;
19003
+ console.warn(
19004
+ "melonJS: setTile received GID " + cleanGid + " which exceeds the 16-bit cell capacity (max 65535). Tile will be truncated and render incorrectly."
19005
+ );
19006
+ }
19007
+ this.layerData[idx] = cleanGid;
19008
+ this.layerData[idx + 1] = flipMaskFromTile(tile);
19009
+ if (this.cachedTile !== null) {
19010
+ this.cachedTile[slot] = tile;
19011
+ }
19012
+ this.dataVersion++;
18827
19013
  this.isDirty = true;
18828
19014
  return tile;
18829
19015
  }
@@ -18853,12 +19039,27 @@ var TMXLayer = class extends Renderable {
18853
19039
  cellAt(x, y, boundsCheck) {
18854
19040
  const _x = ~~x;
18855
19041
  const _y = ~~y;
18856
- const renderer2 = this.getRenderer();
18857
- if (boundsCheck === false || _x >= 0 && _x < renderer2.cols && _y >= 0 && _y < renderer2.rows) {
18858
- return this.layerData[_x][_y];
18859
- } else {
19042
+ if (boundsCheck !== false && (_x < 0 || _x >= this.cols || _y < 0 || _y >= this.rows)) {
19043
+ return null;
19044
+ }
19045
+ const slot = _y * this.cols + _x;
19046
+ const idx = slot * 2;
19047
+ const gid = this.layerData[idx];
19048
+ if (!gid) {
18860
19049
  return null;
18861
19050
  }
19051
+ if (this.cachedTile === null) {
19052
+ this.cachedTile = new Array(this.cols * this.rows).fill(null);
19053
+ } else {
19054
+ const cached = this.cachedTile[slot];
19055
+ if (cached !== null) {
19056
+ return cached;
19057
+ }
19058
+ }
19059
+ const flipMask = this.layerData[idx + 1];
19060
+ const tile = this.getTileById(gidWithFlips(gid, flipMask), _x, _y);
19061
+ this.cachedTile[slot] = tile;
19062
+ return tile;
18862
19063
  }
18863
19064
  /**
18864
19065
  * clear the tile at the specified position
@@ -18871,7 +19072,16 @@ var TMXLayer = class extends Renderable {
18871
19072
  * });
18872
19073
  */
18873
19074
  clearTile(x, y) {
18874
- this.layerData[x][y] = null;
19075
+ if (x < 0 || x >= this.cols || y < 0 || y >= this.rows) {
19076
+ return;
19077
+ }
19078
+ const slot = y * this.cols + x;
19079
+ const idx = slot * 2;
19080
+ this.layerData[idx] = 0;
19081
+ this.layerData[idx + 1] = 0;
19082
+ if (this.cachedTile !== null) {
19083
+ this.cachedTile[slot] = null;
19084
+ }
18875
19085
  if (this.preRender) {
18876
19086
  this.canvasRenderer.clearRect(
18877
19087
  x * this.tilewidth,
@@ -18880,6 +19090,7 @@ var TMXLayer = class extends Renderable {
18880
19090
  this.tileheight
18881
19091
  );
18882
19092
  }
19093
+ this.dataVersion++;
18883
19094
  this.isDirty = true;
18884
19095
  }
18885
19096
  /**
@@ -18900,27 +19111,7 @@ var TMXLayer = class extends Renderable {
18900
19111
  * @ignore
18901
19112
  */
18902
19113
  draw(renderer2, rect) {
18903
- if (this.preRender) {
18904
- const width = Math.min(rect.width, this.width);
18905
- const height = Math.min(rect.height, this.height);
18906
- renderer2.drawImage(
18907
- this.canvasRenderer.getCanvas(),
18908
- rect.pos.x,
18909
- rect.pos.y,
18910
- // sx,sy
18911
- width,
18912
- height,
18913
- // sw,sh
18914
- rect.pos.x,
18915
- rect.pos.y,
18916
- // dx,dy
18917
- width,
18918
- height
18919
- // dw,dh
18920
- );
18921
- } else {
18922
- this.getRenderer().drawTileLayer(renderer2, this, rect);
18923
- }
19114
+ renderer2.drawTileLayer(this, rect);
18924
19115
  }
18925
19116
  };
18926
19117
 
@@ -19284,7 +19475,7 @@ var TMXHexagonalRenderer = class extends TMXRenderer {
19284
19475
  return ret;
19285
19476
  }
19286
19477
  /**
19287
- * draw the tile map
19478
+ * draw the tile map (legacy entry point — accepts a fully-constructed Tile)
19288
19479
  * @ignore
19289
19480
  */
19290
19481
  drawTile(renderer2, x, y, tmxTile) {
@@ -19298,12 +19489,27 @@ var TMXHexagonalRenderer = class extends TMXRenderer {
19298
19489
  );
19299
19490
  vector2dPool.release(point);
19300
19491
  }
19492
+ /**
19493
+ * draw a tile from raw (gid, flipMask, tileset) data — used by the hot
19494
+ * rendering loop to bypass Tile construction
19495
+ * @ignore
19496
+ */
19497
+ drawTileRaw(renderer2, x, y, gid, flipMask, tileset) {
19498
+ const point = this.tileToPixelCoords(x, y, vector2dPool.get());
19499
+ tileset.drawTileRaw(
19500
+ renderer2,
19501
+ tileset.tileoffset.x + point.x,
19502
+ tileset.tileoffset.y + point.y + (this.tileheight - tileset.tileheight),
19503
+ gid,
19504
+ flipMask
19505
+ );
19506
+ vector2dPool.release(point);
19507
+ }
19301
19508
  /**
19302
19509
  * draw the tile map
19303
19510
  * @ignore
19304
19511
  */
19305
19512
  drawTileLayer(renderer2, layer, rect) {
19306
- let tile;
19307
19513
  const startTile = this.pixelToTileCoords(
19308
19514
  rect.pos.x,
19309
19515
  rect.pos.y,
@@ -19327,6 +19533,15 @@ var TMXHexagonalRenderer = class extends TMXRenderer {
19327
19533
  }
19328
19534
  const endX = layer.cols;
19329
19535
  const endY = layer.rows;
19536
+ const layerCols = layer.cols;
19537
+ const data2 = layer.layerData;
19538
+ const tilesets = layer.tilesets;
19539
+ let tilesetCache = layer.tileset;
19540
+ if (tilesetCache === null) {
19541
+ vector2dPool.release(startTile);
19542
+ vector2dPool.release(startPos);
19543
+ return;
19544
+ }
19330
19545
  if (this.staggerX) {
19331
19546
  startTile.x = Math.max(0, startTile.x);
19332
19547
  startTile.y = Math.max(0, startTile.y);
@@ -19340,9 +19555,22 @@ var TMXHexagonalRenderer = class extends TMXRenderer {
19340
19555
  rowTile.setV(startTile);
19341
19556
  rowPos.setV(startPos);
19342
19557
  for (; rowPos.x < rect.right && rowTile.x < endX; rowTile.x += 2) {
19343
- tile = layer.cellAt(rowTile.x, rowTile.y, false);
19344
- if (tile) {
19345
- tile.tileset.drawTile(renderer2, rowPos.x, rowPos.y, tile);
19558
+ if (rowTile.x >= 0 && rowTile.y >= 0 && rowTile.y < layer.rows) {
19559
+ const idx = (rowTile.y * layerCols + rowTile.x) * 2;
19560
+ const gid = data2[idx];
19561
+ if (gid) {
19562
+ const flipMask = data2[idx + 1];
19563
+ if (!tilesetCache.contains(gid)) {
19564
+ tilesetCache = tilesets.getTilesetByGid(gid);
19565
+ }
19566
+ tilesetCache.drawTileRaw(
19567
+ renderer2,
19568
+ rowPos.x,
19569
+ rowPos.y,
19570
+ gid,
19571
+ flipMask
19572
+ );
19573
+ }
19346
19574
  }
19347
19575
  rowPos.x += this.tilewidth + this.sidelengthx;
19348
19576
  }
@@ -19378,9 +19606,22 @@ var TMXHexagonalRenderer = class extends TMXRenderer {
19378
19606
  rowPos.x += this.columnwidth;
19379
19607
  }
19380
19608
  for (; rowPos.x < rect.right && rowTile.x < endX; rowTile.x++) {
19381
- tile = layer.cellAt(rowTile.x, rowTile.y, false);
19382
- if (tile) {
19383
- tile.tileset.drawTile(renderer2, rowPos.x, rowPos.y, tile);
19609
+ if (rowTile.x >= 0 && rowTile.y >= 0 && rowTile.y < layer.rows) {
19610
+ const idx = (rowTile.y * layerCols + rowTile.x) * 2;
19611
+ const gid = data2[idx];
19612
+ if (gid) {
19613
+ const flipMask = data2[idx + 1];
19614
+ if (!tilesetCache.contains(gid)) {
19615
+ tilesetCache = tilesets.getTilesetByGid(gid);
19616
+ }
19617
+ tilesetCache.drawTileRaw(
19618
+ renderer2,
19619
+ rowPos.x,
19620
+ rowPos.y,
19621
+ gid,
19622
+ flipMask
19623
+ );
19624
+ }
19384
19625
  }
19385
19626
  rowPos.x += this.tilewidth + this.sidelengthx;
19386
19627
  }
@@ -19463,7 +19704,7 @@ var TMXIsometricRenderer = class extends TMXRenderer {
19463
19704
  vector2dPool.release(isoPos);
19464
19705
  }
19465
19706
  /**
19466
- * draw the tile map
19707
+ * draw the tile map (legacy entry point — accepts a fully-constructed Tile)
19467
19708
  * @ignore
19468
19709
  */
19469
19710
  drawTile(renderer2, x, y, tmxTile) {
@@ -19475,6 +19716,20 @@ var TMXIsometricRenderer = class extends TMXRenderer {
19475
19716
  tmxTile
19476
19717
  );
19477
19718
  }
19719
+ /**
19720
+ * draw a tile from raw (gid, flipMask, tileset) data — used by the hot
19721
+ * rendering loop to bypass Tile construction
19722
+ * @ignore
19723
+ */
19724
+ drawTileRaw(renderer2, x, y, gid, flipMask, tileset) {
19725
+ tileset.drawTileRaw(
19726
+ renderer2,
19727
+ (this.cols - 1) * tileset.tilewidth + (x - y) * tileset.tilewidth >> 1,
19728
+ -tileset.tilewidth + (x + y) * tileset.tileheight >> 2,
19729
+ gid,
19730
+ flipMask
19731
+ );
19732
+ }
19478
19733
  /**
19479
19734
  * draw the tile map
19480
19735
  * @ignore
@@ -19517,19 +19772,41 @@ var TMXIsometricRenderer = class extends TMXRenderer {
19517
19772
  }
19518
19773
  let shifted = inUpperHalf ^ inLeftHalf;
19519
19774
  const columnItr = vector2dPool.get().setV(rowItr);
19775
+ const layerCols = layer.cols;
19776
+ const layerRows = layer.rows;
19777
+ const data2 = layer.layerData;
19778
+ const tilesets = layer.tilesets;
19779
+ let tilesetCache = tileset;
19780
+ if (tilesetCache === null) {
19781
+ vector2dPool.release(columnItr);
19782
+ vector2dPool.release(rowItr);
19783
+ vector2dPool.release(tileEnd);
19784
+ vector2dPool.release(rectEnd);
19785
+ vector2dPool.release(startPos);
19786
+ return;
19787
+ }
19520
19788
  for (let y = startPos.y * 2; y - this.tileheight * 2 < rectEnd.y * 2; y += this.tileheight) {
19521
19789
  columnItr.setV(rowItr);
19522
19790
  for (let x = startPos.x; x < rectEnd.x; x += this.tilewidth) {
19523
- const tmxTile = layer.cellAt(columnItr.x, columnItr.y);
19524
- if (tmxTile) {
19525
- tileset = tmxTile.tileset;
19526
- const offset = tileset.tileoffset;
19527
- tileset.drawTile(
19528
- renderer2,
19529
- offset.x + x,
19530
- offset.y + y / 2 - tileset.tileheight,
19531
- tmxTile
19532
- );
19791
+ const cx = columnItr.x;
19792
+ const cy = columnItr.y;
19793
+ if (cx >= 0 && cx < layerCols && cy >= 0 && cy < layerRows) {
19794
+ const idx = (cy * layerCols + cx) * 2;
19795
+ const gid = data2[idx];
19796
+ if (gid !== 0) {
19797
+ const flipMask = data2[idx + 1];
19798
+ if (!tilesetCache.contains(gid)) {
19799
+ tilesetCache = tilesets.getTilesetByGid(gid);
19800
+ }
19801
+ const offset = tilesetCache.tileoffset;
19802
+ tilesetCache.drawTileRaw(
19803
+ renderer2,
19804
+ offset.x + x,
19805
+ offset.y + y / 2 - tilesetCache.tileheight,
19806
+ gid,
19807
+ flipMask
19808
+ );
19809
+ }
19533
19810
  }
19534
19811
  columnItr.x++;
19535
19812
  columnItr.y--;
@@ -19584,7 +19861,7 @@ var TMXOrthogonalRenderer = class extends TMXRenderer {
19584
19861
  return ret.set(x * this.tilewidth, y * this.tileheight);
19585
19862
  }
19586
19863
  /**
19587
- * draw the tile map
19864
+ * draw the tile map (legacy entry point — accepts a fully-constructed Tile)
19588
19865
  * @ignore
19589
19866
  */
19590
19867
  drawTile(renderer2, x, y, tmxTile) {
@@ -19596,6 +19873,20 @@ var TMXOrthogonalRenderer = class extends TMXRenderer {
19596
19873
  tmxTile
19597
19874
  );
19598
19875
  }
19876
+ /**
19877
+ * draw a tile from raw (gid, flipMask, tileset) data — used by the hot
19878
+ * rendering loop to bypass Tile construction
19879
+ * @ignore
19880
+ */
19881
+ drawTileRaw(renderer2, x, y, gid, flipMask, tileset) {
19882
+ tileset.drawTileRaw(
19883
+ renderer2,
19884
+ tileset.tileoffset.x + x * this.tilewidth,
19885
+ tileset.tileoffset.y + (y + 1) * this.tileheight - tileset.tileheight,
19886
+ gid,
19887
+ flipMask
19888
+ );
19889
+ }
19599
19890
  /**
19600
19891
  * draw the tile map
19601
19892
  * @ignore
@@ -19633,12 +19924,27 @@ var TMXOrthogonalRenderer = class extends TMXRenderer {
19633
19924
  default:
19634
19925
  break;
19635
19926
  }
19927
+ const cols = layer.cols;
19928
+ const data2 = layer.layerData;
19929
+ const tilesets = layer.tilesets;
19930
+ let tilesetCache = layer.tileset;
19931
+ if (tilesetCache === null) {
19932
+ vector2dPool.release(start);
19933
+ vector2dPool.release(end);
19934
+ return;
19935
+ }
19636
19936
  for (let y = start.y; y !== end.y; y += incY) {
19637
19937
  for (let x = start.x; x !== end.x; x += incX) {
19638
- const tmxTile = layer.cellAt(x, y, false);
19639
- if (tmxTile) {
19640
- this.drawTile(renderer2, x, y, tmxTile);
19938
+ const idx = (y * cols + x) * 2;
19939
+ const gid = data2[idx];
19940
+ if (!gid) {
19941
+ continue;
19641
19942
  }
19943
+ const flipMask = data2[idx + 1];
19944
+ if (!tilesetCache.contains(gid)) {
19945
+ tilesetCache = tilesets.getTilesetByGid(gid);
19946
+ }
19947
+ this.drawTileRaw(renderer2, x, y, gid, flipMask, tilesetCache);
19642
19948
  }
19643
19949
  }
19644
19950
  vector2dPool.release(start);
@@ -19707,7 +20013,7 @@ var TMXObliqueRenderer = class extends TMXOrthogonalRenderer {
19707
20013
  return ret.set(px + this.shearX * py, this.shearY * px + py);
19708
20014
  }
19709
20015
  /**
19710
- * draw the tile map
20016
+ * draw the tile map (legacy entry point — accepts a fully-constructed Tile)
19711
20017
  * @ignore
19712
20018
  */
19713
20019
  drawTile(renderer2, x, y, tmxTile) {
@@ -19716,6 +20022,16 @@ var TMXObliqueRenderer = class extends TMXOrthogonalRenderer {
19716
20022
  const dy = tileset.tileoffset.y + (y + 1) * this.tileheight - tileset.tileheight + this.skewY * x;
19717
20023
  tileset.drawTile(renderer2, dx, dy, tmxTile);
19718
20024
  }
20025
+ /**
20026
+ * draw a tile from raw (gid, flipMask, tileset) data — used by the hot
20027
+ * rendering loop to bypass Tile construction
20028
+ * @ignore
20029
+ */
20030
+ drawTileRaw(renderer2, x, y, gid, flipMask, tileset) {
20031
+ const dx = tileset.tileoffset.x + x * this.tilewidth + this.skewX * y;
20032
+ const dy = tileset.tileoffset.y + (y + 1) * this.tileheight - tileset.tileheight + this.skewY * x;
20033
+ tileset.drawTileRaw(renderer2, dx, dy, gid, flipMask);
20034
+ }
19719
20035
  /**
19720
20036
  * draw the given TMX Layer for the given area
19721
20037
  * @ignore
@@ -19786,12 +20102,25 @@ var TMXObliqueRenderer = class extends TMXOrthogonalRenderer {
19786
20102
  default:
19787
20103
  break;
19788
20104
  }
20105
+ const cols = layer.cols;
20106
+ const data2 = layer.layerData;
20107
+ const tilesets = layer.tilesets;
20108
+ let tilesetCache = layer.tileset;
20109
+ if (tilesetCache === null) {
20110
+ return;
20111
+ }
19789
20112
  for (let y = startY; y !== endY; y += incY) {
19790
20113
  for (let x = startX; x !== endX; x += incX) {
19791
- const tmxTile = layer.cellAt(x, y, false);
19792
- if (tmxTile) {
19793
- this.drawTile(renderer2, x, y, tmxTile);
20114
+ const idx = (y * cols + x) * 2;
20115
+ const gid = data2[idx];
20116
+ if (!gid) {
20117
+ continue;
20118
+ }
20119
+ const flipMask = data2[idx + 1];
20120
+ if (!tilesetCache.contains(gid)) {
20121
+ tilesetCache = tilesets.getTilesetByGid(gid);
19794
20122
  }
20123
+ this.drawTileRaw(renderer2, x, y, gid, flipMask, tilesetCache);
19795
20124
  }
19796
20125
  }
19797
20126
  }
@@ -20147,6 +20476,7 @@ var TMXGroup = class {
20147
20476
  };
20148
20477
 
20149
20478
  // src/level/tiled/TMXTileset.js
20479
+ var SCRATCH_MATRIX = new Matrix2d();
20150
20480
  var TMXTileset = class {
20151
20481
  /**
20152
20482
  * @param {object} tileset - tileset data in JSON format ({@link http://docs.mapeditor.org/en/stable/reference/tmx-map-format/#tileset})
@@ -20477,6 +20807,96 @@ var TMXTileset = class {
20477
20807
  renderer2.restore();
20478
20808
  }
20479
20809
  }
20810
+ /**
20811
+ * draw a tile at the specified position from raw (gid, flipMask) data
20812
+ *
20813
+ * Like {@link drawTile} but bypasses the {@link Tile} object entirely:
20814
+ * the renderer hot loop can pass the GID and flip mask straight from
20815
+ * `layer.layerData` without ever allocating a Tile instance.
20816
+ *
20817
+ * @param {CanvasRenderer|WebGLRenderer} renderer - a renderer instance
20818
+ * @param {number} dx - destination x position
20819
+ * @param {number} dy - destination y position
20820
+ * @param {number} gid - the tile's global id (with flip bits already stripped)
20821
+ * @param {number} flipMask - 3-bit packed flip mask (H=1, V=2, AD=4)
20822
+ * @ignore
20823
+ */
20824
+ drawTileRaw(renderer2, dx, dy, gid, flipMask) {
20825
+ let dw, dh;
20826
+ let tileImage;
20827
+ if (this.isCollection) {
20828
+ tileImage = this.imageCollection.get(gid);
20829
+ const tileWidth = tileImage.width;
20830
+ const tileHeight = tileImage.height;
20831
+ if (this.tilerendersize === "grid") {
20832
+ let scaleX = this.mapTilewidth / tileWidth;
20833
+ let scaleY = this.mapTileheight / tileHeight;
20834
+ if (this.fillmode === "preserve-aspect-fit") {
20835
+ const scale2 = Math.min(scaleX, scaleY);
20836
+ scaleX = scale2;
20837
+ scaleY = scale2;
20838
+ }
20839
+ dw = tileWidth * scaleX;
20840
+ dh = tileHeight * scaleY;
20841
+ dy += this.tileheight - dh;
20842
+ if (this.fillmode === "preserve-aspect-fit") {
20843
+ dx += (this.mapTilewidth - dw) / 2;
20844
+ dy -= (this.mapTileheight - dh) / 2;
20845
+ }
20846
+ } else {
20847
+ dw = tileWidth;
20848
+ dh = tileHeight;
20849
+ }
20850
+ } else {
20851
+ dw = this._renderDw;
20852
+ dh = this._renderDh;
20853
+ dy += this._renderDyOffset;
20854
+ dx += this._renderDxCenter;
20855
+ dy += this._renderDyCenter;
20856
+ }
20857
+ if (flipMask !== 0) {
20858
+ renderer2.save();
20859
+ renderer2.translate(dx, dy);
20860
+ renderer2.transform(
20861
+ buildFlipTransform(
20862
+ SCRATCH_MATRIX,
20863
+ flipMask,
20864
+ this.isCollection ? tileImage.width : this.tilewidth,
20865
+ this.isCollection ? tileImage.height : this.tileheight
20866
+ )
20867
+ );
20868
+ dx = dy = 0;
20869
+ }
20870
+ if (this.isCollection) {
20871
+ renderer2.drawImage(
20872
+ tileImage,
20873
+ 0,
20874
+ 0,
20875
+ tileImage.width,
20876
+ tileImage.height,
20877
+ dx,
20878
+ dy,
20879
+ dw,
20880
+ dh
20881
+ );
20882
+ } else {
20883
+ const offset = this.atlas[this.getViewTileId(gid)].offset;
20884
+ renderer2.drawImage(
20885
+ this.image,
20886
+ offset.x,
20887
+ offset.y,
20888
+ this.tilewidth,
20889
+ this.tileheight,
20890
+ dx,
20891
+ dy,
20892
+ dw + renderer2.uvOffset,
20893
+ dh + renderer2.uvOffset
20894
+ );
20895
+ }
20896
+ if (flipMask !== 0) {
20897
+ renderer2.restore();
20898
+ }
20899
+ }
20480
20900
  };
20481
20901
 
20482
20902
  // src/level/tiled/TMXTilesetGroup.js
@@ -20595,6 +21015,14 @@ function readTileset(data2, mapTilewidth, mapTileheight) {
20595
21015
  function readObjectGroup(map, data2, z) {
20596
21016
  return new TMXGroup(map, data2, z);
20597
21017
  }
21018
+ function refreshAbsoluteBounds(container) {
21019
+ container.forEach((child) => {
21020
+ child.updateBounds(true);
21021
+ if (child instanceof Container) {
21022
+ refreshAbsoluteBounds(child);
21023
+ }
21024
+ });
21025
+ }
20598
21026
  var TMXTileMap = class {
20599
21027
  /**
20600
21028
  * @param {string} levelId - name of TMX map
@@ -20756,12 +21184,12 @@ var TMXTileMap = class {
20756
21184
  Math.max(levelBounds.width, width),
20757
21185
  Math.max(levelBounds.height, height)
20758
21186
  );
20759
- container.pos.set(
20760
- Math.max(0, ~~((width - levelBounds.width) / 2)),
20761
- Math.max(0, ~~((height - levelBounds.height) / 2)),
20762
- // don't change the container z position if defined
20763
- container.pos.z
20764
- );
21187
+ const newX = Math.max(0, ~~((width - levelBounds.width) / 2));
21188
+ const newY = Math.max(0, ~~((height - levelBounds.height) / 2));
21189
+ if (container.pos.x !== newX || container.pos.y !== newY) {
21190
+ container.pos.set(newX, newY, container.pos.z);
21191
+ refreshAbsoluteBounds(container);
21192
+ }
20765
21193
  };
20766
21194
  var _setBounds = _setBounds2;
20767
21195
  const app = container.getRootAncestor().app;
@@ -22401,7 +22829,7 @@ var ColorMatrix = class extends Matrix3d {
22401
22829
  // src/video/webgl/utils/attributes.js
22402
22830
  function extractAttributes(gl, shader) {
22403
22831
  const attributes = {};
22404
- const attrRx = /attribute\s+\w+\s+(\w+)/g;
22832
+ const attrRx = /(?:^|\n)[ \t]*(?:attribute|in)[ \t]+(?:\w+[ \t]+)+(\w+)/g;
22405
22833
  let match;
22406
22834
  let i = 0;
22407
22835
  while (match = attrRx.exec(shader.vertex)) {
@@ -22412,10 +22840,18 @@ function extractAttributes(gl, shader) {
22412
22840
 
22413
22841
  // src/video/webgl/utils/precision.js
22414
22842
  function setPrecision(src, precision) {
22415
- if (src.substring(0, 9) !== "precision") {
22416
- return "precision " + precision + " float;\n" + src;
22843
+ if (/^\s*(?:#version[^\n]*\n)?\s*precision\b/.test(src)) {
22844
+ return src;
22417
22845
  }
22418
- return src;
22846
+ if (src.substring(0, 8) === "#version") {
22847
+ const inject = "\nprecision " + precision + " float;\nprecision " + precision + " int;";
22848
+ const nl = src.indexOf("\n");
22849
+ if (nl < 0) {
22850
+ return src + inject;
22851
+ }
22852
+ return src.substring(0, nl) + inject + src.substring(nl);
22853
+ }
22854
+ return "precision " + precision + " float;\n" + src;
22419
22855
  }
22420
22856
  function getMaxShaderPrecision(gl, highPrecision = true) {
22421
22857
  if (highPrecision && gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT).precision > 0 && gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT).precision > 0) {
@@ -22481,17 +22917,56 @@ var fnHash = {
22481
22917
  ivec2: "2iv",
22482
22918
  ivec3: "3iv",
22483
22919
  ivec4: "4iv",
22920
+ uvec2: "2uiv",
22921
+ uvec3: "3uiv",
22922
+ uvec4: "4uiv",
22484
22923
  mat2: "Matrix2fv",
22485
22924
  mat3: "Matrix3fv",
22486
22925
  mat4: "Matrix4fv",
22487
- sampler2D: "1i"
22926
+ sampler2D: "1i",
22927
+ // WebGL2 integer-typed samplers — bound to a unit just like a
22928
+ // `sampler2D`; the GLSL `usampler2D` / `isampler2D` types let the
22929
+ // shader read raw integer values via `texelFetch` instead of the
22930
+ // normalized-float `texture()` path.
22931
+ usampler2D: "1i",
22932
+ isampler2D: "1i"
22488
22933
  };
22934
+ function valuesMatch(cached, val) {
22935
+ if (cached === void 0) {
22936
+ return false;
22937
+ }
22938
+ if (val !== null && typeof val === "object" && typeof val.length === "number") {
22939
+ if (cached.length !== val.length) {
22940
+ return false;
22941
+ }
22942
+ for (let i = 0; i < val.length; i++) {
22943
+ if (cached[i] !== val[i]) {
22944
+ return false;
22945
+ }
22946
+ }
22947
+ return true;
22948
+ }
22949
+ return cached === val;
22950
+ }
22951
+ function captureValue(prev, val) {
22952
+ if (val === null || typeof val !== "object" || typeof val.length !== "number") {
22953
+ return val;
22954
+ }
22955
+ if (prev !== void 0 && typeof prev === "object" && typeof prev.length === "number" && prev.length === val.length) {
22956
+ for (let i = 0; i < val.length; i++) {
22957
+ prev[i] = val[i];
22958
+ }
22959
+ return prev;
22960
+ }
22961
+ return typeof val.slice === "function" ? val.slice() : Array.from(val);
22962
+ }
22489
22963
  function extractUniforms(gl, shader) {
22490
22964
  const uniforms = {};
22491
22965
  const uniRx = /uniform\s+(\w+)\s+(\w+)/g;
22492
22966
  const uniformsData = {};
22493
22967
  const descriptor = {};
22494
22968
  const locations = {};
22969
+ const cache2 = {};
22495
22970
  let match;
22496
22971
  [shader.vertex, shader.fragment].forEach((shader2) => {
22497
22972
  while (match = uniRx.exec(shader2)) {
@@ -22510,6 +22985,10 @@ function extractUniforms(gl, shader) {
22510
22985
  set: (function(name2, type2, fn) {
22511
22986
  if (/^mat/.test(type2)) {
22512
22987
  return function(val) {
22988
+ if (valuesMatch(cache2[name2], val)) {
22989
+ return;
22990
+ }
22991
+ cache2[name2] = captureValue(cache2[name2], val);
22513
22992
  gl[fn](locations[name2], false, val);
22514
22993
  };
22515
22994
  } else {
@@ -22518,6 +22997,10 @@ function extractUniforms(gl, shader) {
22518
22997
  if (val.length && !/v$/.test(fn)) {
22519
22998
  fnv += "v";
22520
22999
  }
23000
+ if (valuesMatch(cache2[name2], val)) {
23001
+ return;
23002
+ }
23003
+ cache2[name2] = captureValue(cache2[name2], val);
22521
23004
  gl[fnv](locations[name2], val);
22522
23005
  };
22523
23006
  }
@@ -27168,13 +27651,9 @@ var ImageLayer = class extends Sprite {
27168
27651
  if (this.mask) {
27169
27652
  renderer2.setMask(this.mask);
27170
27653
  }
27171
- renderer2.drawPattern(
27172
- this._pattern,
27173
- 0,
27174
- 0,
27175
- viewport.width * 2,
27176
- viewport.height * 2
27177
- );
27654
+ const drawW = this.repeatX ? viewport.width * 2 : width;
27655
+ const drawH = this.repeatY ? viewport.height * 2 : height;
27656
+ renderer2.drawPattern(this._pattern, 0, 0, drawW, drawH);
27178
27657
  }
27179
27658
  // called when the layer is removed from the game world or a container
27180
27659
  onDeactivateEvent() {
@@ -27812,7 +28291,7 @@ var Trigger = class extends Renderable {
27812
28291
  };
27813
28292
 
27814
28293
  // src/version.ts
27815
- var version = "19.3.0";
28294
+ var version = "19.4.0";
27816
28295
 
27817
28296
  // src/system/bootstrap.ts
27818
28297
  var initialized = false;
@@ -28673,10 +29152,7 @@ function enablePointerEvent() {
28673
29152
  if (activeEventList.indexOf(events[i]) !== -1) {
28674
29153
  pointerEventTarget.addEventListener(
28675
29154
  events[i],
28676
- throttle(
28677
- onMoveEvent,
28678
- throttlingInterval
28679
- ),
29155
+ throttle(onMoveEvent, throttlingInterval),
28680
29156
  { passive: true }
28681
29157
  // do not preventDefault on Move events
28682
29158
  );
@@ -29189,7 +29665,7 @@ function testEllipseEllipse(a, ellipseA, b, ellipseB, response) {
29189
29665
  return testEllipsePolygon(a, ellipseA, b, ellipseB.toPolygon(), response);
29190
29666
  }
29191
29667
  }
29192
- const differenceV = T_VECTORS[--T_VECTORS_IDX].copy(b.pos).add(b.ancestor.getAbsolutePosition()).add(ellipseB.pos).sub(a.pos).add(a.ancestor.getAbsolutePosition()).sub(ellipseA.pos);
29668
+ const differenceV = T_VECTORS[--T_VECTORS_IDX].copy(b.pos).add(b.ancestor.getAbsolutePosition()).add(ellipseB.pos).sub(a.pos).sub(a.ancestor.getAbsolutePosition()).sub(ellipseA.pos);
29193
29669
  const radiusA = ellipseA.radius;
29194
29670
  const radiusB = ellipseB.radius;
29195
29671
  const totalRadius = radiusA + radiusB;
@@ -29216,7 +29692,7 @@ function testPolygonEllipse(a, polyA, b, ellipseB, response) {
29216
29692
  if (ellipseB.radiusV.x !== ellipseB.radiusV.y) {
29217
29693
  return testPolygonPolygon(a, polyA, b, ellipseB.toPolygon(), response);
29218
29694
  }
29219
- const circlePos = T_VECTORS[--T_VECTORS_IDX].copy(b.pos).add(b.ancestor.getAbsolutePosition()).add(ellipseB.pos).sub(a.pos).add(a.ancestor.getAbsolutePosition()).sub(polyA.pos);
29695
+ const circlePos = T_VECTORS[--T_VECTORS_IDX].copy(b.pos).add(b.ancestor.getAbsolutePosition()).add(ellipseB.pos).sub(a.pos).sub(a.ancestor.getAbsolutePosition()).sub(polyA.pos);
29220
29696
  const radius = ellipseB.radius;
29221
29697
  const radius2 = radius * radius;
29222
29698
  const points = polyA.points;
@@ -29842,6 +30318,7 @@ var World = class extends Container {
29842
30318
  this.fps = 60;
29843
30319
  this.gravity = new Vector2d(0, 0.98);
29844
30320
  this.preRender = false;
30321
+ this.gpuTilemap = true;
29845
30322
  this.bodies = /* @__PURE__ */ new Set();
29846
30323
  this.broadphase = new QuadTree(
29847
30324
  this,
@@ -31037,8 +31514,15 @@ var MaterialBatcher = class extends Batcher {
31037
31514
  createTexture2D(unit, pixels = null, filter, repeat = "no-repeat", w = pixels.width, h = pixels.height, premultipliedAlpha = true, mipmap = true, texture, flush = true) {
31038
31515
  const gl = this.gl;
31039
31516
  const isPOT = isPowerOfTwo(w) && isPowerOfTwo(h);
31040
- const rs = repeat.search(/^repeat(-x)?$/) === 0 && (isPOT || this.renderer.WebGLVersion > 1) ? gl.REPEAT : gl.CLAMP_TO_EDGE;
31041
- const rt = repeat.search(/^repeat(-y)?$/) === 0 && (isPOT || this.renderer.WebGLVersion > 1) ? gl.REPEAT : gl.CLAMP_TO_EDGE;
31517
+ const wantsRepeat = repeat !== "no-repeat";
31518
+ const canRepeat = isPOT || this.renderer.WebGLVersion > 1;
31519
+ const rs = repeat.search(/^repeat(-x)?$/) === 0 && canRepeat ? gl.REPEAT : gl.CLAMP_TO_EDGE;
31520
+ const rt = repeat.search(/^repeat(-y)?$/) === 0 && canRepeat ? gl.REPEAT : gl.CLAMP_TO_EDGE;
31521
+ if (wantsRepeat && !canRepeat) {
31522
+ console.warn(
31523
+ "melonJS: repeat wrap (" + repeat + ") requested on a non-power-of-two texture (" + w + "x" + h + ") under WebGL 1 \u2014 downgrading to clamp-to-edge"
31524
+ );
31525
+ }
31042
31526
  let currentTexture = texture;
31043
31527
  if (!currentTexture) {
31044
31528
  currentTexture = gl.createTexture();
@@ -31049,7 +31533,9 @@ var MaterialBatcher = class extends Batcher {
31049
31533
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
31050
31534
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
31051
31535
  gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultipliedAlpha);
31052
- if (pixels !== null && pixels.compressed === true) {
31536
+ if (pixels !== null && typeof pixels.upload === "function") {
31537
+ pixels.upload(gl, gl.TEXTURE_2D);
31538
+ } else if (pixels !== null && pixels.compressed === true) {
31053
31539
  const mipmaps = pixels.mipmaps;
31054
31540
  for (let i = 0; i < mipmaps.length; i++) {
31055
31541
  gl.compressedTexImage2D(
@@ -31110,7 +31596,9 @@ var MaterialBatcher = class extends Batcher {
31110
31596
  pixels
31111
31597
  );
31112
31598
  }
31113
- if (isPOT && mipmap === true && (pixels === null || pixels.compressed !== true)) {
31599
+ if (isPOT && mipmap === true && pixels !== null && pixels.compressed !== true && typeof pixels.upload !== "function") {
31600
+ gl.generateMipmap(gl.TEXTURE_2D);
31601
+ } else if (pixels === null && isPOT && mipmap === true) {
31114
31602
  gl.generateMipmap(gl.TEXTURE_2D);
31115
31603
  }
31116
31604
  return currentTexture;
@@ -31188,18 +31676,29 @@ var MaterialBatcher = class extends Batcher {
31188
31676
  }
31189
31677
  /**
31190
31678
  * @ignore
31679
+ * @param {TextureAtlas|TextureResource} texture
31680
+ * @param {number} [w] - ignored when the source has its own `width` (the
31681
+ * common case); kept for the legacy signature where callers passed a
31682
+ * destination size. Forwarded only as a last-resort default.
31683
+ * @param {number} [h] - same as `w`.
31684
+ * @param {boolean} [force=false]
31685
+ * @param {boolean} [flush=true]
31191
31686
  */
31192
31687
  uploadTexture(texture, w, h, force = false, flush = true) {
31193
31688
  const unit = this.renderer.cache.getUnit(texture);
31194
31689
  const texture2D = this.boundTextures[unit];
31195
31690
  if (typeof texture2D === "undefined" || force) {
31691
+ const filter = typeof texture.filter !== "undefined" ? texture.filter : this.renderer.settings.antiAlias ? this.gl.LINEAR : this.gl.NEAREST;
31692
+ const source = texture.getTexture();
31693
+ const texW = source.width || source.videoWidth || w;
31694
+ const texH = source.height || source.videoHeight || h;
31196
31695
  this.createTexture2D(
31197
31696
  unit,
31198
- texture.getTexture(),
31199
- this.renderer.settings.antiAlias ? this.gl.LINEAR : this.gl.NEAREST,
31697
+ source,
31698
+ filter,
31200
31699
  texture.repeat,
31201
- w,
31202
- h,
31700
+ texW,
31701
+ texH,
31203
31702
  texture.premultipliedAlpha,
31204
31703
  void 0,
31205
31704
  texture2D,
@@ -32110,6 +32609,395 @@ function packLights(lights, ambient, translateX, translateY, scratch) {
32110
32609
  };
32111
32610
  }
32112
32611
 
32612
+ // src/video/texture/resource.js
32613
+ var TextureResource = class {
32614
+ /**
32615
+ * @param {object} options
32616
+ * @param {number} options.width - pixel width of the texture
32617
+ * @param {number} options.height - pixel height of the texture
32618
+ * @param {boolean} [options.premultipliedAlpha=false]
32619
+ * @param {string} [options.repeat="no-repeat"] - "no-repeat" | "repeat" | "repeat-x" | "repeat-y"
32620
+ * @param {number} [options.filter] - `gl.NEAREST` or `gl.LINEAR`; when
32621
+ * omitted the batcher falls back to the renderer's `antiAlias` setting
32622
+ */
32623
+ constructor({
32624
+ width,
32625
+ height,
32626
+ premultipliedAlpha = false,
32627
+ repeat = "no-repeat",
32628
+ filter
32629
+ } = {}) {
32630
+ this.width = width;
32631
+ this.height = height;
32632
+ this.premultipliedAlpha = premultipliedAlpha;
32633
+ this.repeat = repeat;
32634
+ this.filter = filter;
32635
+ this.sources = /* @__PURE__ */ new Map([["default", this]]);
32636
+ this.activeAtlas = "default";
32637
+ }
32638
+ /**
32639
+ * Returns the upload "source" the batcher hands to `createTexture2D`.
32640
+ * For a resource this is the resource itself — `createTexture2D`
32641
+ * dispatches to `resource.upload(gl, target)`.
32642
+ * @ignore
32643
+ */
32644
+ getTexture() {
32645
+ return this;
32646
+ }
32647
+ /**
32648
+ * Issue the `gl.texImage2D` (or equivalent) call that uploads this
32649
+ * resource's data into the currently-bound `TEXTURE_2D` slot.
32650
+ * Subclasses MUST override.
32651
+ * @abstract
32652
+ * @param {WebGLRenderingContext|WebGL2RenderingContext} gl
32653
+ * @param {number} target - `gl.TEXTURE_2D` (or future cube-map targets)
32654
+ */
32655
+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
32656
+ upload(gl, target) {
32657
+ throw new Error("TextureResource subclasses must implement upload()");
32658
+ }
32659
+ };
32660
+ var BufferTextureResource = class extends TextureResource {
32661
+ /**
32662
+ * @param {ArrayBufferView} data - the pixel data; size must be
32663
+ * `width * height * 4` bytes for the default RGBA / UNSIGNED_BYTE
32664
+ * format
32665
+ * @param {object} options
32666
+ * @param {number} options.width
32667
+ * @param {number} options.height
32668
+ * @param {boolean} [options.premultipliedAlpha=false]
32669
+ * @param {string} [options.repeat="no-repeat"]
32670
+ * @param {number} [options.filter]
32671
+ * @param {"rgba8"|"rgba8ui"} [options.format="rgba8"] - storage format.
32672
+ * `"rgba8"` (default): normalized RGBA, sampled via `sampler2D` /
32673
+ * `texture()`. `"rgba8ui"`: unsigned-integer RGBA, sampled via
32674
+ * `usampler2D` / `texelFetch()` — requires WebGL2. Use the integer
32675
+ * form for raw-data lookups (GID tables, palette indices, etc.) to
32676
+ * skip the float-decode round trip and gain exact integer reads.
32677
+ */
32678
+ constructor(data2, options) {
32679
+ super(options);
32680
+ this.data = data2;
32681
+ this.format = options.format || "rgba8";
32682
+ }
32683
+ /** @ignore */
32684
+ upload(gl, target) {
32685
+ if (this.format === "rgba8ui") {
32686
+ if (typeof gl.RGBA8UI === "undefined") {
32687
+ throw new Error(
32688
+ 'BufferTextureResource: format "rgba8ui" requires a WebGL 2 context'
32689
+ );
32690
+ }
32691
+ gl.texImage2D(
32692
+ target,
32693
+ 0,
32694
+ gl.RGBA8UI,
32695
+ this.width,
32696
+ this.height,
32697
+ 0,
32698
+ gl.RGBA_INTEGER,
32699
+ gl.UNSIGNED_BYTE,
32700
+ this.data
32701
+ );
32702
+ } else {
32703
+ gl.texImage2D(
32704
+ target,
32705
+ 0,
32706
+ gl.RGBA,
32707
+ this.width,
32708
+ this.height,
32709
+ 0,
32710
+ gl.RGBA,
32711
+ gl.UNSIGNED_BYTE,
32712
+ this.data
32713
+ );
32714
+ }
32715
+ }
32716
+ };
32717
+
32718
+ // src/video/webgl/shaders/orthogonal-tmxlayer.frag
32719
+ var orthogonal_tmxlayer_default = "#version 300 es\n\n// Fragment shader for the orthogonal TMX layer GPU renderer (WebGL2 /\n// GLSL ES 3.00).\n//\n// Per fragment the shader:\n// 1. recovers the world-pixel position from the host UV,\n// 2. walks candidate cells (geometric cell + cells whose oversized,\n// bottom-aligned tiles could reach this fragment),\n// 3. fetches GIDs from the per-layer index texture, and\n// 4. samples the tileset atlas at the correct sub-region.\n//\n// Fast path: when the tileset has no oversized tiles (`uOverflow == (0, 0)`)\n// \u2014 the common case for grid-aligned maps \u2014 only the geometric cell can\n// hold this fragment's tile, so we skip the candidate loop entirely and\n// run a single `tryRenderCell` call. The loop branch is on a uniform\n// value, coherent across the wave, and trims 25 worst-case loop\n// iterations + their guard checks to a single inlined function call.\n//\n// Slow path: tiles drawing larger than the cell are bottom-aligned\n// vertically and left-aligned horizontally. Render order is \"right-down\":\n// later cells end up on top, so the candidate loop scans dy high\u2192low,\n// dx low\u2192high and picks the FIRST match.\n//\n// Index texture encoding (`RGBA8`, one cell per texel):\n// R = GID low byte\n// G = GID high byte (combined: R | (G << 8) = 16-bit GID)\n// B = flip mask (bit 0 = H, bit 1 = V, bit 2 = AD)\n// A = unused\n//\n// Animation lookup (`RGBA8`, 1 row, `tileCount` texels wide): per local\n// tile id, the CURRENT frame's local id, same R/G byte-pair encoding.\n//\n// Why `sampler2D` + float decode rather than `usampler2D`: the engine's\n// multi-texture default shader declares `uSampler0..uSamplerN-1` as\n// `sampler2D` \u2014 all of them are active for WebGL's draw-time validation.\n// A `usampler2D`-backed `RGBA8UI` texture cached at any of those units\n// (units 0..15 on a typical 16-unit fragment stage) would mismatch when\n// the default shader next draws sprites, killing every quad with\n// `GL_INVALID_OPERATION`. Staying on regular RGBA8 keeps the cache\n// path coherent \u2014 the cost is one `floor(c * 255 + 0.5)` per fetch.\n//\n// `texelFetch` is still used (vs `texture()`) for byte-exact reads \u2014\n// it bypasses interpolation, so the integer byte values come out\n// unmolested even on a normalized-float sampler.\n\n// The engine's `setPrecision` step injects precision declarations for\n// float and int after the `#version` line, using whatever precision the\n// renderer was configured with (`highPrecisionShader` setting on the\n// Application). Individual shader files don't hardcode precision so the\n// engine-wide preference applies.\n\nin vec2 vRegion;\nin vec4 vColor;\n\nuniform sampler2D uSampler; // tileset atlas (RGBA)\nuniform sampler2D uTileIndex; // per-layer GID index (RGBA8)\nuniform sampler2D uAnimLookup; // per-tileset animation table (RGBA8)\n\nuniform vec2 uMapSize;\nuniform vec2 uCellSize;\nuniform vec2 uTileSize;\nuniform vec2 uOverflow;\nuniform vec2 uTilesetCols;\nuniform vec2 uInvTilesetSize;\nuniform vec4 uTilesetMargin; // (marginX, marginY, spacingX, spacingY)\nuniform vec2 uGidRange; // (firstgid, lastgid)\nuniform vec2 uVisibleStart;\nuniform vec2 uVisibleSize;\nuniform int uAnimSize; // number of entries in uAnimLookup, 0 if disabled\nuniform float uOpacity;\nuniform vec4 uTint;\n\nout vec4 fragColor;\n\nconst int MAX_OVERFLOW = 4;\n\n// Try to render the tile at cell (cx, cy) for the current fragment.\n// Returns true and writes the sampled color to `outColor` when the cell\n// contains a visible, in-range tile whose pixel covers this fragment.\n// Identical logic for both fast and slow paths \u2014 GLSL inlines this\n// trivially so there's no function-call overhead at runtime.\nbool tryRenderCell(int cx, int cy, vec2 worldPx, out vec4 outColor) {\n int mapW = int(uMapSize.x);\n int mapH = int(uMapSize.y);\n if (cx < 0 || cx >= mapW || cy < 0 || cy >= mapH) {\n return false;\n }\n\n // `texelFetch` skips filtering \u2014 the 8-bit channel values come back\n // as normalized floats, decoded to byte ints below\n vec4 cellF = texelFetch(uTileIndex, ivec2(cx, cy), 0);\n uvec4 cell = uvec4(cellF * 255.0 + 0.5);\n int firstGid = int(uGidRange.x);\n int gid = int(cell.r) | (int(cell.g) << 8);\n if (gid < firstGid || gid > int(uGidRange.y)) {\n return false;\n }\n\n vec2 tileWorldOrigin = vec2(\n float(cx) * uCellSize.x,\n (float(cy) + 1.0) * uCellSize.y - uTileSize.y\n );\n vec2 inTile = (worldPx - tileWorldOrigin) / uTileSize;\n if (inTile.x < 0.0 || inTile.x >= 1.0 || inTile.y < 0.0 || inTile.y >= 1.0) {\n return false;\n }\n\n // flip mask + axis-swap trick: see TMX shader-path flip spec.\n // AD performs a transpose; with AD set, H and V swap their effective\n // axes (matches the legacy `buildFlipTransform`).\n int flipMask = int(cell.b);\n float flipH = float(flipMask & 1);\n float flipV = float((flipMask >> 1) & 1);\n float flipAD = float((flipMask >> 2) & 1);\n inTile = mix(inTile, inTile.yx, flipAD);\n float effH = mix(flipH, flipV, flipAD);\n float effV = mix(flipV, flipH, flipAD);\n inTile.x = mix(inTile.x, 1.0 - inTile.x, effH);\n inTile.y = mix(inTile.y, 1.0 - inTile.y, effV);\n\n int localId = gid - firstGid;\n\n // animation: if the tileset has animated tiles, swap the local id for\n // its current frame's id via the lookup texture (CPU updates the\n // lookup in lockstep with `tileset.update(dt)`).\n if (uAnimSize > 0) {\n vec4 animF = texelFetch(uAnimLookup, ivec2(localId, 0), 0);\n uvec4 animTexel = uvec4(animF * 255.0 + 0.5);\n localId = int(animTexel.r) | (int(animTexel.g) << 8);\n }\n\n float row = floor(float(localId) / uTilesetCols.x);\n float col = float(localId) - row * uTilesetCols.x;\n vec2 tileOriginPx = uTilesetMargin.xy\n + vec2(col, row) * (uTileSize + uTilesetMargin.zw);\n vec2 texelPx = tileOriginPx + inTile * uTileSize;\n vec2 texelUV = texelPx * uInvTilesetSize;\n\n vec4 sampled = texture(uSampler, texelUV);\n if (sampled.a <= 0.0) {\n return false;\n }\n outColor = sampled;\n return true;\n}\n\nvoid main(void) {\n vec2 tileCoord = uVisibleStart + vRegion * uVisibleSize;\n vec2 geomCell = floor(tileCoord);\n vec2 worldPx = tileCoord * uCellSize;\n\n int gx = int(geomCell.x);\n int gy = int(geomCell.y);\n int overflowX = int(uOverflow.x + 0.5);\n int overflowY = int(uOverflow.y + 0.5);\n\n vec4 result;\n\n // Fast path: tiles fit the cell exactly (the common case) \u2014 only the\n // geometric cell can contain this fragment's tile.\n if (overflowX == 0 && overflowY == 0) {\n if (!tryRenderCell(gx, gy, worldPx, result)) {\n discard;\n }\n result.a *= uOpacity;\n fragColor = result * uTint;\n return;\n }\n\n // Slow path: oversized tiles. Walk candidates dy high\u2192low, dx low\u2192high\n // and pick the FIRST match \u2014 render order is \"right-down\" so later\n // cells go on top.\n bool found = false;\n for (int idy = 0; idy <= MAX_OVERFLOW; idy++) {\n int dy = MAX_OVERFLOW - idy;\n if (dy > overflowY) continue;\n for (int dx = 0; dx <= MAX_OVERFLOW; dx++) {\n if (dx > overflowX) break;\n if (tryRenderCell(gx - dx, gy + dy, worldPx, result)) {\n found = true;\n break;\n }\n }\n if (found) break;\n }\n if (!found) discard;\n result.a *= uOpacity;\n fragColor = result * uTint;\n}\n";
32720
+
32721
+ // src/video/webgl/shaders/orthogonal-tmxlayer.vert
32722
+ var orthogonal_tmxlayer_default2 = '#version 300 es\n\n// Vertex shader for the orthogonal TMX layer GPU renderer.\n//\n// Matches the quad batcher\'s vertex layout (`aVertex`, `aRegion`, `aColor`,\n// `uProjectionMatrix`) so the standard `setBatcher("quad", this.shader)` +\n// `addQuad()` flow drives it like any other quad. Same attribute names,\n// same uniforms \u2014 just expressed in GLSL ES 3.00 (`in`/`out` in place of\n// `attribute`/`varying`) so the program can pair with the 3.00 fragment\n// shader that uses `usampler2D` / `texelFetch` for integer-typed lookups.\n\nin vec2 aVertex;\nin vec2 aRegion;\nin vec4 aColor;\n\nuniform mat4 uProjectionMatrix;\n\nout vec2 vRegion;\nout vec4 vColor;\n\nvoid main(void) {\n gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0);\n // premultiplied-alpha + bgra \u2192 rgba swap, same convention the batcher\n // uses for its default sprite shader\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n}\n';
32723
+
32724
+ // src/video/webgl/renderers/tmxlayer/orthogonal.js
32725
+ var DEFAULT_TINT = new Float32Array([1, 1, 1, 1]);
32726
+ var OrthogonalTMXLayerGPURenderer = class {
32727
+ /**
32728
+ * @param {WebGLRenderer} renderer - the WebGL renderer instance
32729
+ */
32730
+ constructor(renderer2) {
32731
+ this.renderer = renderer2;
32732
+ this.gl = renderer2.gl;
32733
+ this.shader = new GLShader(
32734
+ renderer2.gl,
32735
+ orthogonal_tmxlayer_default2,
32736
+ orthogonal_tmxlayer_default,
32737
+ renderer2.shaderPrecision
32738
+ );
32739
+ this.resources = /* @__PURE__ */ new Map();
32740
+ this.animLookups = /* @__PURE__ */ new Map();
32741
+ this._v2 = new Float32Array(2);
32742
+ this._v4 = new Float32Array(4);
32743
+ }
32744
+ /**
32745
+ * Free every cached per-layer index texture and empty the local
32746
+ * resource map. Called from `WebGLRenderer.reset()` (which
32747
+ * `GAME_RESET` triggers) so each level transition starts clean.
32748
+ * @ignore
32749
+ */
32750
+ reset() {
32751
+ const batcher = this.renderer.currentBatcher;
32752
+ const cache2 = this.renderer.cache;
32753
+ const drop = (resource) => {
32754
+ if (batcher !== void 0) {
32755
+ batcher.deleteTexture2D(resource);
32756
+ } else {
32757
+ cache2.freeTextureUnit(resource);
32758
+ cache2.delete(resource);
32759
+ }
32760
+ };
32761
+ for (const resource of this.resources.values()) {
32762
+ drop(resource);
32763
+ }
32764
+ for (const entry of this.animLookups.values()) {
32765
+ drop(entry.resource);
32766
+ }
32767
+ this.resources.clear();
32768
+ this.animLookups.clear();
32769
+ }
32770
+ /**
32771
+ * Write a `vec2` uniform without allocating a fresh Float32Array per
32772
+ * call. Both components flow into the shared `_v2` scratch buffer,
32773
+ * which `setUniform` reads synchronously and forwards to
32774
+ * `gl.uniform2fv` — so reusing the buffer across calls is safe.
32775
+ * @param {string} name
32776
+ * @param {number} x
32777
+ * @param {number} y
32778
+ * @private
32779
+ */
32780
+ _setV2(name, x, y) {
32781
+ this._v2[0] = x;
32782
+ this._v2[1] = y;
32783
+ this.shader.setUniform(name, this._v2);
32784
+ }
32785
+ /**
32786
+ * `vec4` counterpart to {@link _setV2}.
32787
+ * @param {string} name
32788
+ * @param {number} x
32789
+ * @param {number} y
32790
+ * @param {number} z
32791
+ * @param {number} w
32792
+ * @private
32793
+ */
32794
+ _setV4(name, x, y, z, w) {
32795
+ this._v4[0] = x;
32796
+ this._v4[1] = y;
32797
+ this._v4[2] = z;
32798
+ this._v4[3] = w;
32799
+ this.shader.setUniform(name, this._v4);
32800
+ }
32801
+ /**
32802
+ * Get-or-create the per-tileset animation-lookup entry. Returns
32803
+ * `undefined` for tilesets that have no animated tiles (the shader's
32804
+ * `uAnimEnabled` uniform is then set to 0 and the lookup texture is
32805
+ * not bound).
32806
+ *
32807
+ * The entry holds a `tileCount × 1` RGBA8 `BufferTextureResource`
32808
+ * where texel `localId` encodes the CURRENT frame's local id as
32809
+ * `R = lo byte, G = hi byte` (same encoding as the GID index
32810
+ * texture). Each call walks `tileset.animations` and rewrites
32811
+ * dirty texels — `tileset.update(dt)` (driven by the layer) advances
32812
+ * `anim.cur.tileid` independently of this renderer.
32813
+ *
32814
+ * @param {object} tileset
32815
+ * @param {number} tileCount - tiles in the tileset's atlas grid
32816
+ * (`atlasCols * atlasRows`)
32817
+ * @returns {{resource: BufferTextureResource, data: Uint8Array,
32818
+ * tileCount: number, dirty: boolean}|undefined}
32819
+ */
32820
+ _getOrUpdateAnimLookup(tileset, tileCount) {
32821
+ if (!tileset.isAnimated || tileset.animations.size === 0) {
32822
+ return void 0;
32823
+ }
32824
+ let entry = this.animLookups.get(tileset);
32825
+ if (entry === void 0) {
32826
+ const data3 = new Uint8Array(tileCount * 4);
32827
+ for (let id = 0; id < tileCount; id++) {
32828
+ data3[id * 4 + 0] = id & 255;
32829
+ data3[id * 4 + 1] = id >> 8 & 255;
32830
+ }
32831
+ const resource = new BufferTextureResource(data3, {
32832
+ width: tileCount,
32833
+ height: 1,
32834
+ premultipliedAlpha: false,
32835
+ repeat: "no-repeat",
32836
+ // NEAREST so the shader's `texelFetch` reads byte-exact
32837
+ // channel values back as normalized floats
32838
+ filter: this.gl.NEAREST,
32839
+ format: "rgba8"
32840
+ });
32841
+ entry = { resource, data: data3, tileCount, dirty: false };
32842
+ this.animLookups.set(tileset, entry);
32843
+ }
32844
+ const data2 = entry.data;
32845
+ for (const [localId, anim] of tileset.animations) {
32846
+ const off2 = localId * 4;
32847
+ const cur = anim.cur.tileid;
32848
+ const lo = cur & 255;
32849
+ const hi = cur >> 8 & 255;
32850
+ if (data2[off2] !== lo || data2[off2 + 1] !== hi) {
32851
+ data2[off2] = lo;
32852
+ data2[off2 + 1] = hi;
32853
+ entry.dirty = true;
32854
+ }
32855
+ }
32856
+ return entry;
32857
+ }
32858
+ /**
32859
+ * Get-or-create the per-layer index `BufferTextureResource`.
32860
+ * @param {TMXLayer} layer
32861
+ * @returns {BufferTextureResource}
32862
+ */
32863
+ _getResource(layer) {
32864
+ let resource = this.resources.get(layer);
32865
+ if (resource === void 0) {
32866
+ resource = new BufferTextureResource(
32867
+ new Uint8Array(layer.layerData.buffer),
32868
+ {
32869
+ width: layer.cols,
32870
+ height: layer.rows,
32871
+ // raw GID bytes — must NOT have alpha pre-multiplied
32872
+ // into RGB, otherwise A=0 cells wipe their R/G/B
32873
+ premultipliedAlpha: false,
32874
+ repeat: "no-repeat",
32875
+ // NEAREST so `texelFetch` returns the original byte
32876
+ // values (as normalized floats) for the GID/flip
32877
+ // decode below
32878
+ filter: this.gl.NEAREST,
32879
+ format: "rgba8"
32880
+ }
32881
+ );
32882
+ resource._uploadedVersion = -1;
32883
+ this.resources.set(layer, resource);
32884
+ }
32885
+ return resource;
32886
+ }
32887
+ /**
32888
+ * Draw an orthogonal TMX layer through the shader path.
32889
+ * @param {TMXLayer} layer
32890
+ * @param {object} rect - the visible viewport rect (world coords)
32891
+ */
32892
+ draw(layer, rect) {
32893
+ const renderer2 = this.renderer;
32894
+ const tileWidth = layer.tilewidth;
32895
+ const tileHeight = layer.tileheight;
32896
+ const cols = layer.cols;
32897
+ const rows = layer.rows;
32898
+ const startTileX = Math.max(0, Math.floor(rect.pos.x / tileWidth));
32899
+ const startTileY = Math.max(0, Math.floor(rect.pos.y / tileHeight));
32900
+ const endTileX = Math.min(
32901
+ cols,
32902
+ Math.ceil((rect.pos.x + rect.width) / tileWidth)
32903
+ );
32904
+ const endTileY = Math.min(
32905
+ rows,
32906
+ Math.ceil((rect.pos.y + rect.height) / tileHeight)
32907
+ );
32908
+ if (endTileX <= startTileX || endTileY <= startTileY) {
32909
+ return;
32910
+ }
32911
+ const worldX = startTileX * tileWidth;
32912
+ const worldY = startTileY * tileHeight;
32913
+ const worldW = (endTileX - startTileX) * tileWidth;
32914
+ const worldH = (endTileY - startTileY) * tileHeight;
32915
+ const visStartX = startTileX;
32916
+ const visStartY = startTileY;
32917
+ const visSizeX = endTileX - startTileX;
32918
+ const visSizeY = endTileY - startTileY;
32919
+ const batcher = renderer2.setBatcher("quad", this.shader);
32920
+ const resource = this._getResource(layer);
32921
+ const indexUnit = batcher.uploadTexture(
32922
+ resource,
32923
+ cols,
32924
+ rows,
32925
+ resource._uploadedVersion !== layer.dataVersion
32926
+ );
32927
+ resource._uploadedVersion = layer.dataVersion;
32928
+ this.shader.setUniform("uTileIndex", indexUnit);
32929
+ this._setV2("uMapSize", cols, rows);
32930
+ this._setV2("uVisibleStart", visStartX, visStartY);
32931
+ this._setV2("uVisibleSize", visSizeX, visSizeY);
32932
+ this._setV2("uCellSize", tileWidth, tileHeight);
32933
+ this.shader.setUniform("uOpacity", layer.getOpacity());
32934
+ this.shader.setUniform(
32935
+ "uTint",
32936
+ layer.tint ? layer.tint.toArray() : DEFAULT_TINT
32937
+ );
32938
+ const tilesets = layer.tilesets.tilesets;
32939
+ for (let i = 0; i < tilesets.length; i++) {
32940
+ const tileset = tilesets[i];
32941
+ if (tileset.isCollection || tileset.image === void 0) {
32942
+ continue;
32943
+ }
32944
+ const tsW = tileset.tilewidth;
32945
+ const tsH = tileset.tileheight;
32946
+ const margin = tileset.margin;
32947
+ const spacing = tileset.spacing;
32948
+ const atlasW = tileset.image.width;
32949
+ const atlasH = tileset.image.height;
32950
+ const atlasCols = Math.max(
32951
+ 1,
32952
+ Math.floor((atlasW - margin * 2 + spacing) / (tsW + spacing))
32953
+ );
32954
+ const atlasRows = Math.max(
32955
+ 1,
32956
+ Math.floor((atlasH - margin * 2 + spacing) / (tsH + spacing))
32957
+ );
32958
+ const tileCount = atlasCols * atlasRows;
32959
+ const animEntry = this._getOrUpdateAnimLookup(tileset, tileCount);
32960
+ if (animEntry !== void 0) {
32961
+ const animUnit = batcher.uploadTexture(
32962
+ animEntry.resource,
32963
+ animEntry.tileCount,
32964
+ 1,
32965
+ animEntry.dirty
32966
+ );
32967
+ animEntry.dirty = false;
32968
+ this.shader.setUniform("uAnimLookup", animUnit);
32969
+ this.shader.setUniform("uAnimSize", animEntry.tileCount);
32970
+ } else {
32971
+ this.shader.setUniform("uAnimLookup", indexUnit);
32972
+ this.shader.setUniform("uAnimSize", 0);
32973
+ }
32974
+ this._setV2("uTileSize", tsW, tsH);
32975
+ this._setV2("uTilesetCols", atlasCols, atlasRows);
32976
+ this._setV2("uInvTilesetSize", 1 / atlasW, 1 / atlasH);
32977
+ this._setV4("uTilesetMargin", margin, margin, spacing, spacing);
32978
+ this._setV2(
32979
+ "uOverflow",
32980
+ Math.max(0, Math.ceil(tsW / tileWidth) - 1),
32981
+ Math.max(0, Math.ceil(tsH / tileHeight) - 1)
32982
+ );
32983
+ this._setV2("uGidRange", tileset.firstgid, tileset.lastgid);
32984
+ batcher.addQuad(
32985
+ tileset.texture,
32986
+ worldX,
32987
+ worldY,
32988
+ worldW,
32989
+ worldH,
32990
+ 0,
32991
+ 0,
32992
+ 1,
32993
+ 1,
32994
+ 4294967295
32995
+ );
32996
+ batcher.flush();
32997
+ }
32998
+ }
32999
+ };
33000
+
32113
33001
  // src/video/webgl/webgl_renderer.js
32114
33002
  var _tempMatrix = new Matrix3d();
32115
33003
  var _savedTransform = new Matrix3d();
@@ -32252,6 +33140,7 @@ var WebGLRenderer = class extends Renderer {
32252
33140
  reset() {
32253
33141
  super.reset();
32254
33142
  this.clear();
33143
+ this._orthogonalTMXGPURenderer?.reset();
32255
33144
  this.setViewport();
32256
33145
  if (this.gl.getParameter(this.gl.ARRAY_BUFFER_BINDING) !== this.vertexBuffer) {
32257
33146
  this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
@@ -32280,6 +33169,53 @@ var WebGLRenderer = class extends Renderer {
32280
33169
  });
32281
33170
  this._lightAtlas = void 0;
32282
33171
  }
33172
+ if (this.isContextValid === false) {
33173
+ this._orthogonalTMXGPURenderer = void 0;
33174
+ }
33175
+ }
33176
+ /**
33177
+ * Draw a TMX tile layer through whichever path the layer's `renderMode`
33178
+ * resolves to. WebGL2-eligible layers (`renderMode === "shader"`) take
33179
+ * the procedural shader path — one quad per tileset, GID lookup in a
33180
+ * per-layer data texture. All other layers fall through to the base
33181
+ * `Renderer.drawTileLayer` (preRender blit or per-tile loop).
33182
+ * @param {object} layer - the TMXLayer to draw
33183
+ * @param {object} rect - the visible region in world coords
33184
+ */
33185
+ drawTileLayer(layer, rect) {
33186
+ if (layer.renderMode === "shader") {
33187
+ const gpu = this._getTMXGPURendererFor(layer.orientation);
33188
+ if (gpu !== void 0) {
33189
+ gpu.draw(layer, rect);
33190
+ return;
33191
+ }
33192
+ }
33193
+ super.drawTileLayer(layer, rect);
33194
+ }
33195
+ /**
33196
+ * Lazy-init the orientation-specific GPU tilemap renderer.
33197
+ * @param {string} orientation
33198
+ * @returns {object|undefined}
33199
+ * @ignore
33200
+ */
33201
+ _getTMXGPURendererFor(orientation2) {
33202
+ if (orientation2 === "orthogonal") {
33203
+ if (this._orthogonalTMXGPURenderer === void 0) {
33204
+ try {
33205
+ this._orthogonalTMXGPURenderer = new OrthogonalTMXLayerGPURenderer(
33206
+ this
33207
+ );
33208
+ } catch (err) {
33209
+ console.warn(
33210
+ "melonJS: GPU tilemap shader failed to compile, falling back to legacy renderer",
33211
+ err
33212
+ );
33213
+ this._orthogonalTMXGPURenderer = null;
33214
+ }
33215
+ }
33216
+ return this._orthogonalTMXGPURenderer || void 0;
33217
+ }
33218
+ return void 0;
32283
33219
  }
32284
33220
  /**
32285
33221
  * add a new batcher to this renderer
@@ -34202,6 +35138,14 @@ var Application = class {
34202
35138
  this.world = new World(0, 0, this.settings.width, this.settings.height);
34203
35139
  this.world.app = this;
34204
35140
  this.world.physic = this.settings.physic;
35141
+ this.world.gpuTilemap = this.settings.gpuTilemap;
35142
+ if (this.settings.gpuTilemap && // duck-type rather than `instanceof WebGLRenderer` to avoid a
35143
+ // runtime import; only the WebGL renderer carries `WebGLVersion`
35144
+ this.renderer.WebGLVersion !== 2) {
35145
+ console.warn(
35146
+ "melonJS: gpuTilemap is enabled but the active renderer is not WebGL 2 \u2014 falling back to the legacy tile renderer for every tile layer"
35147
+ );
35148
+ }
34205
35149
  this.lastUpdate = globalThis.performance.now();
34206
35150
  if (!this.isInitialized) {
34207
35151
  on(STATE_CHANGE, this.repaint, this);
@@ -34223,9 +35167,7 @@ var Application = class {
34223
35167
  reset() {
34224
35168
  const current = state_default.get();
34225
35169
  if (typeof current !== "undefined") {
34226
- this.viewport = current.cameras.get(
34227
- "default"
34228
- );
35170
+ this.viewport = current.cameras.get("default");
34229
35171
  }
34230
35172
  emit(GAME_RESET);
34231
35173
  this.updateFrameRate();