@codexo/exojs-tilemap 0.15.2 → 0.16.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 (151) hide show
  1. package/README.md +33 -26
  2. package/dist/esm/ChunkSource.d.ts +33 -0
  3. package/dist/esm/ChunkSource.d.ts.map +1 -0
  4. package/dist/esm/ChunkStreamer.d.ts +98 -0
  5. package/dist/esm/ChunkStreamer.d.ts.map +1 -0
  6. package/dist/esm/ChunkStreamer.js +201 -0
  7. package/dist/esm/ChunkStreamer.js.map +1 -0
  8. package/dist/esm/ImageLayer.d.ts +11 -3
  9. package/dist/esm/ImageLayer.d.ts.map +1 -0
  10. package/dist/esm/ImageLayer.js +71 -64
  11. package/dist/esm/ImageLayer.js.map +1 -1
  12. package/dist/esm/ImageLayerNode.d.ts +89 -0
  13. package/dist/esm/ImageLayerNode.d.ts.map +1 -0
  14. package/dist/esm/ImageLayerNode.js +205 -0
  15. package/dist/esm/ImageLayerNode.js.map +1 -0
  16. package/dist/esm/MapObject.d.ts +70 -0
  17. package/dist/esm/MapObject.d.ts.map +1 -0
  18. package/dist/esm/MapObject.js +34 -0
  19. package/dist/esm/MapObject.js.map +1 -0
  20. package/dist/esm/MapObjectSpawner.d.ts +133 -0
  21. package/dist/esm/MapObjectSpawner.d.ts.map +1 -0
  22. package/dist/esm/MapObjectSpawner.js +147 -0
  23. package/dist/esm/MapObjectSpawner.js.map +1 -0
  24. package/dist/esm/MapSpawnSession.d.ts +53 -0
  25. package/dist/esm/MapSpawnSession.d.ts.map +1 -0
  26. package/dist/esm/MapSpawnSession.js +88 -0
  27. package/dist/esm/MapSpawnSession.js.map +1 -0
  28. package/dist/esm/MapWorld.d.ts +134 -0
  29. package/dist/esm/MapWorld.d.ts.map +1 -0
  30. package/dist/esm/MapWorld.js +150 -0
  31. package/dist/esm/MapWorld.js.map +1 -0
  32. package/dist/esm/MapWorldRuntime.d.ts +182 -0
  33. package/dist/esm/MapWorldRuntime.d.ts.map +1 -0
  34. package/dist/esm/MapWorldRuntime.js +261 -0
  35. package/dist/esm/MapWorldRuntime.js.map +1 -0
  36. package/dist/esm/ObjectLayer.d.ts +93 -7
  37. package/dist/esm/ObjectLayer.d.ts.map +1 -0
  38. package/dist/esm/ObjectLayer.js +184 -167
  39. package/dist/esm/ObjectLayer.js.map +1 -1
  40. package/dist/esm/SampledChunkSource.d.ts +43 -0
  41. package/dist/esm/SampledChunkSource.d.ts.map +1 -0
  42. package/dist/esm/SampledChunkSource.js +48 -0
  43. package/dist/esm/SampledChunkSource.js.map +1 -0
  44. package/dist/esm/TileAnimator.d.ts +12 -2
  45. package/dist/esm/TileAnimator.d.ts.map +1 -0
  46. package/dist/esm/TileAnimator.js +154 -165
  47. package/dist/esm/TileAnimator.js.map +1 -1
  48. package/dist/esm/TileChunk.d.ts +30 -5
  49. package/dist/esm/TileChunk.d.ts.map +1 -0
  50. package/dist/esm/TileChunk.js +190 -188
  51. package/dist/esm/TileChunk.js.map +1 -1
  52. package/dist/esm/TileChunkNode.d.ts +18 -3
  53. package/dist/esm/TileChunkNode.d.ts.map +1 -0
  54. package/dist/esm/TileChunkNode.js +104 -85
  55. package/dist/esm/TileChunkNode.js.map +1 -1
  56. package/dist/esm/TileLayer.d.ts +104 -24
  57. package/dist/esm/TileLayer.d.ts.map +1 -0
  58. package/dist/esm/TileLayer.js +511 -469
  59. package/dist/esm/TileLayer.js.map +1 -1
  60. package/dist/esm/TileLayerNode.d.ts +52 -11
  61. package/dist/esm/TileLayerNode.d.ts.map +1 -0
  62. package/dist/esm/TileLayerNode.js +224 -166
  63. package/dist/esm/TileLayerNode.js.map +1 -1
  64. package/dist/esm/TileMap.d.ts +96 -31
  65. package/dist/esm/TileMap.d.ts.map +1 -0
  66. package/dist/esm/TileMap.js +401 -357
  67. package/dist/esm/TileMap.js.map +1 -1
  68. package/dist/esm/TileMapBand.d.ts +42 -29
  69. package/dist/esm/TileMapBand.d.ts.map +1 -0
  70. package/dist/esm/TileMapBand.js +151 -158
  71. package/dist/esm/TileMapBand.js.map +1 -1
  72. package/dist/esm/TileMapNode.d.ts +34 -18
  73. package/dist/esm/TileMapNode.d.ts.map +1 -0
  74. package/dist/esm/TileMapNode.js +122 -109
  75. package/dist/esm/TileMapNode.js.map +1 -1
  76. package/dist/esm/TileMapView.d.ts +108 -47
  77. package/dist/esm/TileMapView.d.ts.map +1 -0
  78. package/dist/esm/TileMapView.js +385 -338
  79. package/dist/esm/TileMapView.js.map +1 -1
  80. package/dist/esm/TileSet.d.ts +15 -6
  81. package/dist/esm/TileSet.d.ts.map +1 -0
  82. package/dist/esm/TileSet.js +161 -181
  83. package/dist/esm/TileSet.js.map +1 -1
  84. package/dist/esm/WangSet.d.ts +5 -4
  85. package/dist/esm/WangSet.d.ts.map +1 -0
  86. package/dist/esm/WangSet.js +70 -78
  87. package/dist/esm/WangSet.js.map +1 -1
  88. package/dist/esm/WorkerSampledChunkSource.d.ts +95 -0
  89. package/dist/esm/WorkerSampledChunkSource.d.ts.map +1 -0
  90. package/dist/esm/WorkerSampledChunkSource.js +117 -0
  91. package/dist/esm/WorkerSampledChunkSource.js.map +1 -0
  92. package/dist/esm/autoTile.d.ts +10 -5
  93. package/dist/esm/autoTile.d.ts.map +1 -0
  94. package/dist/esm/autoTile.js +164 -205
  95. package/dist/esm/autoTile.js.map +1 -1
  96. package/dist/esm/chunkGeometry.d.ts +8 -7
  97. package/dist/esm/chunkGeometry.d.ts.map +1 -0
  98. package/dist/esm/chunkGeometry.js +83 -94
  99. package/dist/esm/chunkGeometry.js.map +1 -1
  100. package/dist/esm/index.d.ts +1 -0
  101. package/dist/esm/index.d.ts.map +1 -0
  102. package/dist/esm/index.js +26 -15
  103. package/dist/esm/nodeBounds.d.ts +20 -0
  104. package/dist/esm/nodeBounds.d.ts.map +1 -0
  105. package/dist/esm/nodeBounds.js +40 -0
  106. package/dist/esm/nodeBounds.js.map +1 -0
  107. package/dist/esm/pixelSnap.d.ts +4 -3
  108. package/dist/esm/pixelSnap.d.ts.map +1 -0
  109. package/dist/esm/pixelSnap.js +18 -16
  110. package/dist/esm/pixelSnap.js.map +1 -1
  111. package/dist/esm/public.d.ts +23 -3
  112. package/dist/esm/public.d.ts.map +1 -0
  113. package/dist/esm/tileCollision.d.ts +190 -0
  114. package/dist/esm/tileCollision.d.ts.map +1 -0
  115. package/dist/esm/tileCollision.js +367 -0
  116. package/dist/esm/tileCollision.js.map +1 -0
  117. package/dist/esm/tileWord.d.ts +12 -0
  118. package/dist/esm/tileWord.d.ts.map +1 -0
  119. package/dist/esm/tileWord.js +16 -0
  120. package/dist/esm/tileWord.js.map +1 -0
  121. package/dist/esm/tilemapExtension.d.ts +4 -3
  122. package/dist/esm/tilemapExtension.d.ts.map +1 -0
  123. package/dist/esm/tilemapExtension.js +47 -47
  124. package/dist/esm/tilemapExtension.js.map +1 -1
  125. package/dist/esm/tilemapSerializers.d.ts +5 -4
  126. package/dist/esm/tilemapSerializers.d.ts.map +1 -0
  127. package/dist/esm/tilemapSerializers.js +48 -41
  128. package/dist/esm/tilemapSerializers.js.map +1 -1
  129. package/dist/esm/types.d.ts +34 -18
  130. package/dist/esm/types.d.ts.map +1 -0
  131. package/dist/esm/types.js +111 -120
  132. package/dist/esm/types.js.map +1 -1
  133. package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts +50 -2
  134. package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts.map +1 -0
  135. package/dist/esm/webgl2/WebGl2TileChunkRenderer.js +366 -332
  136. package/dist/esm/webgl2/WebGl2TileChunkRenderer.js.map +1 -1
  137. package/dist/esm/webgl2/shaders/tile-chunk.frag.js +6 -0
  138. package/dist/esm/webgl2/shaders/tile-chunk.frag.js.map +1 -0
  139. package/dist/esm/webgl2/shaders/tile-chunk.vert.js +6 -0
  140. package/dist/esm/webgl2/shaders/tile-chunk.vert.js.map +1 -0
  141. package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts +70 -4
  142. package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts.map +1 -0
  143. package/dist/esm/webgpu/WebGpuTileChunkRenderer.js +496 -383
  144. package/dist/esm/webgpu/WebGpuTileChunkRenderer.js.map +1 -1
  145. package/dist/esm/webgpu/shaders/tile-chunk.wgsl.js +6 -0
  146. package/dist/esm/webgpu/shaders/tile-chunk.wgsl.js.map +1 -0
  147. package/package.json +8 -15
  148. package/dist/esm/index.js.map +0 -1
  149. package/dist/esm/register.d.ts +0 -1
  150. package/dist/esm/register.js +0 -25
  151. package/dist/esm/register.js.map +0 -1
@@ -1,361 +1,405 @@
1
- import { validatePositiveInteger } from './types.js';
2
- import { TileMapView } from './TileMapView.js';
1
+ import { validatePairedDimensions, validatePositiveInteger } from "./types.js";
2
+ import { TileMapView } from "./TileMapView.js";
3
3
 
4
+ //#region src/TileMap.ts
4
5
  /**
5
- * A generic, format-independent tile map.
6
- *
7
- * Owns a finite grid of {@link TileLayer}s and a shared set of {@link TileSet}s.
8
- * Tile data is stored in compact chunked arrays no per-tile heap objects.
9
- *
10
- * The map does NOT own tileset textures (those are Loader-owned) and does
11
- * NOT own SceneNode children see {@link import('./TileMapNode').TileMapNode},
12
- * which owns those.
13
- *
14
- * Multiple tilesets are supported: each cell stores a packed tileset index
15
- * and local tile ID, so different tilesets may have different tile dimensions.
16
- *
17
- * @advanced
18
- */
19
- class TileMap {
20
- /** Map name (debug). */
21
- name;
22
- /** Map width in tiles. */
23
- width;
24
- /** Map height in tiles. */
25
- height;
26
- /** Tile width in pixels. */
27
- tileWidth;
28
- /** Tile height in pixels. */
29
- tileHeight;
30
- /** Pixel width. */
31
- get pixelWidth() { return this.width * this.tileWidth; }
32
- /** Pixel height. */
33
- get pixelHeight() { return this.height * this.tileHeight; }
34
- /** Default chunk width for layers. */
35
- chunkWidth;
36
- /** Default chunk height for layers. */
37
- chunkHeight;
38
- /** Map class/type string (Tiled `class`; may be empty). */
39
- class;
40
- /** Map background colour as `0xRRGGBB`, or `null`. Informational. */
41
- backgroundColor;
42
- /** Tile draw order (Tiled `renderorder`). Informational. */
43
- renderOrder;
44
- /** Map-level properties (immutable). */
45
- properties;
46
- _tilesets;
47
- _layers = [];
48
- _layerById = new Map();
49
- _objectLayers = [];
50
- _imageLayers = [];
51
- _revision = 0;
52
- _destroyed = false;
53
- /**
54
- * @throws When dimensions or other options are invalid.
55
- */
56
- constructor(options) {
57
- validatePositiveInteger(options.width, 'map.width');
58
- validatePositiveInteger(options.height, 'map.height');
59
- validatePositiveInteger(options.tileWidth, 'map.tileWidth');
60
- validatePositiveInteger(options.tileHeight, 'map.tileHeight');
61
- const chunkWidth = options.chunkWidth ?? 32;
62
- const chunkHeight = options.chunkHeight ?? 32;
63
- validatePositiveInteger(chunkWidth, 'chunkWidth');
64
- validatePositiveInteger(chunkHeight, 'chunkHeight');
65
- this.name = options.name ?? 'TileMap';
66
- this.width = options.width;
67
- this.height = options.height;
68
- this.tileWidth = options.tileWidth;
69
- this.tileHeight = options.tileHeight;
70
- this.chunkWidth = chunkWidth;
71
- this.chunkHeight = chunkHeight;
72
- this.class = options.class ?? '';
73
- this.backgroundColor = options.backgroundColor ?? null;
74
- this.renderOrder = options.renderOrder ?? 'right-down';
75
- this._tilesets = options.tilesets ? [...options.tilesets] : [];
76
- this.properties = options.properties
77
- ? Object.freeze({ ...options.properties })
78
- : Object.freeze({});
79
- if (options.layers) {
80
- for (const layer of options.layers) {
81
- this._addLayer(layer);
82
- }
83
- }
84
- if (options.objectLayers) {
85
- this._objectLayers.push(...options.objectLayers);
86
- }
87
- if (options.imageLayers) {
88
- this._imageLayers.push(...options.imageLayers);
89
- }
90
- }
91
- // ── Tilesets ──────────────────────────────────────────────────────────
92
- /** Immutable list of tilesets available to this map. */
93
- get tilesets() {
94
- return this._tilesets;
95
- }
96
- /**
97
- * Add a tileset. Tilesets must have unique names.
98
- * @throws If a tileset with the same name already exists, or the map is destroyed.
99
- */
100
- addTileset(tileset) {
101
- this._checkDestroyed();
102
- if (this._tilesets.some(ts => ts.name === tileset.name)) {
103
- throw new Error(`Tileset "${tileset.name}" already exists in map "${this.name}".`);
104
- }
105
- this._tilesets.push(tileset);
106
- this._revision++;
107
- }
108
- /**
109
- * Get a tileset by name, or undefined.
110
- */
111
- getTileset(name) {
112
- return this._tilesets.find(ts => ts.name === name);
113
- }
114
- // ── Layers ────────────────────────────────────────────────────────────
115
- /** Immutable snapshot of layers (ordered). */
116
- get layers() {
117
- return this._layers;
118
- }
119
- _addLayer(layer) {
120
- if (this._layerById.has(layer.id)) {
121
- throw new Error(`Layer ID ${layer.id} already exists in map "${this.name}".`);
122
- }
123
- this._layerById.set(layer.id, layer);
124
- this._layers.push(layer);
125
- }
126
- /**
127
- * Add a layer after construction.
128
- * @throws If a layer with the same ID already exists.
129
- */
130
- addLayer(layer) {
131
- this._checkDestroyed();
132
- this._addLayer(layer);
133
- this._revision++;
134
- }
135
- /**
136
- * Get a layer by ID.
137
- */
138
- getLayerById(id) {
139
- return this._layerById.get(id);
140
- }
141
- /**
142
- * Get a layer by name. Returns the first match in insertion order.
143
- */
144
- getLayerByName(name) {
145
- return this._layers.find(l => l.name === name);
146
- }
147
- /**
148
- * Get a tile layer by name. Convenience alias for {@link getLayerByName}.
149
- */
150
- getTileLayer(name) {
151
- return this.getLayerByName(name);
152
- }
153
- /**
154
- * Remove a layer by ID. The layer is destroyed.
155
- * @returns true if the layer was found and removed.
156
- */
157
- removeLayer(id) {
158
- this._checkDestroyed();
159
- const layer = this._layerById.get(id);
160
- if (!layer)
161
- return false;
162
- this._layers.splice(this._layers.indexOf(layer), 1);
163
- this._layerById.delete(id);
164
- layer.destroy();
165
- this._revision++;
166
- return true;
167
- }
168
- // ── Object layers (data-only) ─────────────────────────────────────────
169
- /** Immutable snapshot of object layers (insertion order). */
170
- get objectLayers() {
171
- return this._objectLayers;
172
- }
173
- /**
174
- * Add an object layer after construction.
175
- * @throws If the map is destroyed.
176
- */
177
- addObjectLayer(layer) {
178
- this._checkDestroyed();
179
- this._objectLayers.push(layer);
180
- this._revision++;
181
- }
182
- /**
183
- * Get an object layer by name (first match in insertion order), or undefined.
184
- *
185
- * Supply an {@link ObjectSchema} type argument `S` to obtain a typed view of
186
- * the layer — `getObjectLayer<LevelObjects>('Entities')` returns an
187
- * `ObjectLayer<LevelObjects>` whose {@link ObjectLayer.byType} / {@link
188
- * ObjectLayer.where} accessors narrow `properties`. The schema is a static
189
- * developer promise only; no runtime validation is performed and the call
190
- * remains fully back-compatible when omitted.
191
- */
192
- getObjectLayer(name) {
193
- return this._objectLayers.find(layer => layer.name === name);
194
- }
195
- /**
196
- * Get an object layer by ID.
197
- *
198
- * Supply an {@link ObjectSchema} type argument `S` to obtain a typed view of
199
- * the layer, as with {@link getObjectLayer}.
200
- */
201
- getObjectLayerById(id) {
202
- return this._objectLayers.find(layer => layer.id === id);
203
- }
204
- /**
205
- * Remove an object layer by ID.
206
- * @returns true if the layer was found and removed.
207
- */
208
- removeObjectLayer(id) {
209
- this._checkDestroyed();
210
- const index = this._objectLayers.findIndex(layer => layer.id === id);
211
- if (index === -1)
212
- return false;
213
- this._objectLayers.splice(index, 1);
214
- this._revision++;
215
- return true;
216
- }
217
- // ── Image layers (data-only) ──────────────────────────────────────────
218
- /** Immutable snapshot of image layers (insertion order). */
219
- get imageLayers() {
220
- return this._imageLayers;
221
- }
222
- /**
223
- * Get an image layer by name (first match in insertion order), or undefined.
224
- */
225
- getImageLayer(name) {
226
- return this._imageLayers.find(layer => layer.name === name);
227
- }
228
- /**
229
- * Get an image layer by ID.
230
- */
231
- getImageLayerById(id) {
232
- return this._imageLayers.find(layer => layer.id === id);
233
- }
234
- /**
235
- * Remove an image layer by ID.
236
- * @returns true if the layer was found and removed.
237
- */
238
- removeImageLayer(id) {
239
- this._checkDestroyed();
240
- const index = this._imageLayers.findIndex(layer => layer.id === id);
241
- if (index === -1)
242
- return false;
243
- this._imageLayers.splice(index, 1);
244
- this._revision++;
245
- return true;
246
- }
247
- // ── Scene composition ─────────────────────────────────────────────────
248
- /**
249
- * Create a new {@link TileMapView} that groups this map's layers into
250
- * independently placeable band / layer scene nodes for interleaving
251
- * application actors between tile layers.
252
- *
253
- * Each call returns a fresh, independent view — the map does **not** cache a
254
- * single global view, so multiple coexisting views of the same map are
255
- * allowed. The view references this map but never owns it: destroying the
256
- * view frees only its generated layer/band nodes never the map, its layers,
257
- * tileset textures, or any application actors.
258
- *
259
- * @advanced
260
- */
261
- createView(options) {
262
- return new TileMapView(this, options);
263
- }
264
- // ── Queries ───────────────────────────────────────────────────────────
265
- /**
266
- * Get a resolved tile from a given layer at tile coordinates.
267
- * Convenience for `map.getLayerById(id)?.getTileAt(tx, ty)`.
268
- * Returns null for an empty cell, out-of-bounds, or missing layer.
269
- */
270
- getTileAt(layerId, tx, ty) {
271
- const layer = this._layerById.get(layerId);
272
- if (!layer)
273
- return null;
274
- return layer.getTileAt(tx, ty);
275
- }
276
- /**
277
- * Set a tile on a given layer at tile coordinates.
278
- * Convenience for `map.getLayerById(id)?.setTileAt(tx, ty, tile)`.
279
- * @throws If the layer does not exist, coordinates are out of bounds,
280
- * or the tile reference is invalid.
281
- */
282
- setTileAt(layerId, tx, ty, tile) {
283
- const layer = this._layerById.get(layerId);
284
- if (!layer)
285
- throw new Error(`Layer ${layerId} not found in map "${this.name}".`);
286
- layer.setTileAt(tx, ty, tile);
287
- }
288
- /**
289
- * Clear a tile on a given layer at tile coordinates.
290
- * Convenience for `map.getLayerById(id)?.clearTileAt(tx, ty)`.
291
- * @throws If the layer does not exist or coordinates are out of bounds.
292
- */
293
- clearTileAt(layerId, tx, ty) {
294
- const layer = this._layerById.get(layerId);
295
- if (!layer)
296
- throw new Error(`Layer ${layerId} not found in map "${this.name}".`);
297
- layer.clearTileAt(tx, ty);
298
- }
299
- // ── Coordinate conversion (base layer) ────────────────────────────────
300
- /**
301
- * Convert a tile coordinate to the pixel position of its top-left corner
302
- * in map-local space (ignoring layer offsets).
303
- */
304
- tileToPixel(tx, ty) {
305
- return {
306
- x: tx * this.tileWidth,
307
- y: ty * this.tileHeight,
308
- };
309
- }
310
- /**
311
- * Convert a pixel position in map-local space to the tile coordinate
312
- * that contains it. Uses `floor`. May return coordinates outside map bounds.
313
- */
314
- pixelToTile(px, py) {
315
- return {
316
- tx: Math.floor(px / this.tileWidth),
317
- ty: Math.floor(py / this.tileHeight),
318
- };
319
- }
320
- // ── Revision / lifecycle ──────────────────────────────────────────────
321
- /**
322
- * Monotonic map revision counter. Increments on structural changes only
323
- * (add/remove layer, add tileset). Cell mutations are tracked per-chunk
324
- * and per-layer; the renderer reads chunk-level revisions directly.
325
- * @advanced
326
- */
327
- get revision() {
328
- return this._revision;
329
- }
330
- /** Whether the map has been destroyed. */
331
- get destroyed() {
332
- return this._destroyed;
333
- }
334
- _checkDestroyed() {
335
- if (this._destroyed) {
336
- throw new Error(`TileMap "${this.name}" has been destroyed.`);
337
- }
338
- }
339
- /**
340
- * Destroy the map and all owned layers and chunk storage.
341
- *
342
- * Is idempotent. Does NOT destroy tileset textures (Loader-owned) or
343
- * any SceneNodes (those do not exist yet in this slice).
344
- */
345
- destroy() {
346
- if (this._destroyed)
347
- return;
348
- this._destroyed = true;
349
- for (const layer of this._layers) {
350
- layer.destroy();
351
- }
352
- this._layers.length = 0;
353
- this._layerById.clear();
354
- this._objectLayers.length = 0;
355
- this._imageLayers.length = 0;
356
- this._tilesets.length = 0;
357
- }
358
- }
6
+ * A generic, format-independent tile map.
7
+ *
8
+ * Owns a finite grid of {@link TileLayer}s and a shared set of {@link TileSet}s.
9
+ * Tile data is stored in compact chunked arrays - no per-tile heap objects.
10
+ *
11
+ * The map does NOT own tileset textures (those are Loader-owned) and does
12
+ * NOT own SceneNode children - see {@link import('./TileMapNode').TileMapNode},
13
+ * which owns those.
14
+ *
15
+ * Multiple tilesets are supported: each cell stores a packed tileset index
16
+ * and local tile ID, so different tilesets may have different tile dimensions.
17
+ *
18
+ * @advanced
19
+ */
20
+ var TileMap = class {
21
+ /** Map name (debug). */
22
+ name;
23
+ /** Map width in tiles, or `undefined` if unbounded. */
24
+ width;
25
+ /** Map height in tiles, or `undefined` if unbounded. */
26
+ height;
27
+ /** Tile width in pixels. */
28
+ tileWidth;
29
+ /** Tile height in pixels. */
30
+ tileHeight;
31
+ /** Pixel width, or `undefined` if unbounded. */
32
+ get pixelWidth() {
33
+ return this.width === void 0 ? void 0 : this.width * this.tileWidth;
34
+ }
35
+ /** Pixel height, or `undefined` if unbounded. */
36
+ get pixelHeight() {
37
+ return this.height === void 0 ? void 0 : this.height * this.tileHeight;
38
+ }
39
+ /** `true` if this map has a fixed width/height; `false` if unbounded. */
40
+ get bounded() {
41
+ return this.width !== void 0 && this.height !== void 0;
42
+ }
43
+ /** Default chunk width for layers. */
44
+ chunkWidth;
45
+ /** Default chunk height for layers. */
46
+ chunkHeight;
47
+ /** Map class/type string (Tiled `class`; may be empty). */
48
+ class;
49
+ /** Map background colour as `0xRRGGBB`, or `null`. Informational. */
50
+ backgroundColor;
51
+ /** Tile draw order (Tiled `renderorder`). Informational. */
52
+ renderOrder;
53
+ /** Map-level properties (immutable). */
54
+ properties;
55
+ _tilesets;
56
+ _layers = [];
57
+ _layerById = /* @__PURE__ */ new Map();
58
+ _objectLayers = [];
59
+ _imageLayers = [];
60
+ _documentOrder = [];
61
+ _revision = 0;
62
+ _destroyed = false;
63
+ /**
64
+ * @throws When dimensions or other options are invalid.
65
+ */
66
+ constructor(options) {
67
+ validatePairedDimensions(options.width, options.height, "TileMap", "map");
68
+ validatePositiveInteger(options.tileWidth, "map.tileWidth");
69
+ validatePositiveInteger(options.tileHeight, "map.tileHeight");
70
+ const chunkWidth = options.chunkWidth ?? 32;
71
+ const chunkHeight = options.chunkHeight ?? 32;
72
+ validatePositiveInteger(chunkWidth, "chunkWidth");
73
+ validatePositiveInteger(chunkHeight, "chunkHeight");
74
+ this.name = options.name ?? "TileMap";
75
+ this.width = options.width;
76
+ this.height = options.height;
77
+ this.tileWidth = options.tileWidth;
78
+ this.tileHeight = options.tileHeight;
79
+ this.chunkWidth = chunkWidth;
80
+ this.chunkHeight = chunkHeight;
81
+ this.class = options.class ?? "";
82
+ this.backgroundColor = options.backgroundColor ?? null;
83
+ this.renderOrder = options.renderOrder ?? "right-down";
84
+ this._tilesets = options.tilesets ? [...options.tilesets] : [];
85
+ this.properties = options.properties ? Object.freeze({ ...options.properties }) : Object.freeze({});
86
+ if (options.layers) for (const layer of options.layers) this._addLayer(layer);
87
+ if (options.objectLayers) this._objectLayers.push(...options.objectLayers);
88
+ if (options.imageLayers) for (const layer of options.imageLayers) this._addImageLayer(layer);
89
+ this._documentOrder.push(...this._buildDocumentOrder(options.documentOrder));
90
+ }
91
+ /**
92
+ * Validate an explicit `documentOrder` option against current tile/image
93
+ * layer membership and resolve it to layer instances, or compute the
94
+ * fallback order (tile layers in insertion order, then image layers) when
95
+ * omitted. The order stores instance references - not ids - so a
96
+ * cross-kind id collision in a fallback map can never resolve to the
97
+ * wrong layer.
98
+ * @throws Per the validation rules documented on {@link
99
+ * TileMapOptions.documentOrder}.
100
+ */
101
+ _buildDocumentOrder(documentOrder) {
102
+ if (!documentOrder) return [...this._layers, ...this._imageLayers];
103
+ const tileIds = new Set(this._layers.map((l) => l.id));
104
+ const imageIds = new Set(this._imageLayers.map((l) => l.id));
105
+ for (const id of tileIds) if (imageIds.has(id)) throw new Error(`Layer ID ${id} exists as both a tile layer and an image layer in map "${this.name}"; documentOrder cannot disambiguate them.`);
106
+ const seen = /* @__PURE__ */ new Set();
107
+ for (const id of documentOrder) {
108
+ if (!tileIds.has(id) && !imageIds.has(id)) throw new Error(`documentOrder references unknown layer ID ${id} in map "${this.name}".`);
109
+ if (seen.has(id)) throw new Error(`documentOrder lists layer ID ${id} more than once in map "${this.name}".`);
110
+ seen.add(id);
111
+ }
112
+ const totalLayers = tileIds.size + imageIds.size;
113
+ if (seen.size !== totalLayers) {
114
+ for (const id of [...tileIds, ...imageIds]) if (!seen.has(id)) throw new Error(`documentOrder is missing layer ID ${id} in map "${this.name}".`);
115
+ }
116
+ const instanceCount = this._layers.length + this._imageLayers.length;
117
+ if (documentOrder.length !== instanceCount) throw new Error(`documentOrder has ${documentOrder.length} entries but map "${this.name}" has ${instanceCount} tile/image layer instances; check for a duplicate image-layer ID.`);
118
+ return documentOrder.map((id) => {
119
+ const layer = this._layerById.get(id) ?? this._imageLayers.find((imageLayer) => imageLayer.id === id);
120
+ if (!layer) throw new Error(`documentOrder references layer id ${id}, but map "${this.name}" has no tile or image layer with that id.`);
121
+ return layer;
122
+ });
123
+ }
124
+ /** Immutable list of tilesets available to this map. */
125
+ get tilesets() {
126
+ return this._tilesets;
127
+ }
128
+ /**
129
+ * Add a tileset. Tilesets must have unique names.
130
+ * @throws If a tileset with the same name already exists, or the map is destroyed.
131
+ */
132
+ addTileset(tileset) {
133
+ this._checkDestroyed();
134
+ if (this._tilesets.some((ts) => ts.name === tileset.name)) throw new Error(`Tileset "${tileset.name}" already exists in map "${this.name}".`);
135
+ this._tilesets.push(tileset);
136
+ this._revision++;
137
+ }
138
+ /**
139
+ * Get a tileset by name, or undefined.
140
+ */
141
+ getTileset(name) {
142
+ return this._tilesets.find((ts) => ts.name === name);
143
+ }
144
+ /** Immutable snapshot of layers (ordered). */
145
+ get layers() {
146
+ return this._layers;
147
+ }
148
+ _addLayer(layer) {
149
+ if (this._layerById.has(layer.id)) throw new Error(`Layer ID ${layer.id} already exists in map "${this.name}".`);
150
+ this._layerById.set(layer.id, layer);
151
+ this._layers.push(layer);
152
+ }
153
+ /**
154
+ * Add a layer after construction. Appended to the end of {@link
155
+ * renderableLayers}'s document order.
156
+ * @throws If a layer with the same ID already exists.
157
+ */
158
+ addLayer(layer) {
159
+ this._checkDestroyed();
160
+ this._addLayer(layer);
161
+ this._documentOrder.push(layer);
162
+ this._revision++;
163
+ }
164
+ /**
165
+ * Get a tile layer by ID.
166
+ */
167
+ getTileLayerById(id) {
168
+ return this._layerById.get(id);
169
+ }
170
+ /**
171
+ * Get a tile layer by name. Returns the first match in insertion order.
172
+ */
173
+ getTileLayer(name) {
174
+ return this._layers.find((l) => l.name === name);
175
+ }
176
+ /**
177
+ * Remove a layer by ID. The layer is destroyed and spliced out of {@link
178
+ * renderableLayers}'s document order.
179
+ * @returns true if the layer was found and removed.
180
+ */
181
+ removeLayer(id) {
182
+ this._checkDestroyed();
183
+ const layer = this._layerById.get(id);
184
+ if (!layer) return false;
185
+ this._layers.splice(this._layers.indexOf(layer), 1);
186
+ this._layerById.delete(id);
187
+ const orderIndex = this._documentOrder.indexOf(layer);
188
+ if (orderIndex !== -1) this._documentOrder.splice(orderIndex, 1);
189
+ layer.destroy();
190
+ this._revision++;
191
+ return true;
192
+ }
193
+ /** Immutable snapshot of object layers (insertion order). */
194
+ get objectLayers() {
195
+ return this._objectLayers;
196
+ }
197
+ /**
198
+ * Add an object layer after construction.
199
+ * @throws If the map is destroyed.
200
+ */
201
+ addObjectLayer(layer) {
202
+ this._checkDestroyed();
203
+ this._objectLayers.push(layer);
204
+ this._revision++;
205
+ }
206
+ /**
207
+ * Get an object layer by name (first match in insertion order), or undefined.
208
+ *
209
+ * Supply an {@link ObjectSchema} type argument `S` to obtain a typed view of
210
+ * the layer - `getObjectLayer<LevelObjects>('Entities')` returns an
211
+ * `ObjectLayer<LevelObjects>` whose {@link ObjectLayer.byType} / {@link
212
+ * ObjectLayer.where} accessors narrow `properties`. The schema is a static
213
+ * developer promise only; no runtime validation is performed and the call
214
+ * remains fully back-compatible when omitted.
215
+ */
216
+ getObjectLayer(name) {
217
+ return this._objectLayers.find((layer) => layer.name === name);
218
+ }
219
+ /**
220
+ * Get an object layer by ID.
221
+ *
222
+ * Supply an {@link ObjectSchema} type argument `S` to obtain a typed view of
223
+ * the layer, as with {@link getObjectLayer}.
224
+ */
225
+ getObjectLayerById(id) {
226
+ return this._objectLayers.find((layer) => layer.id === id);
227
+ }
228
+ /**
229
+ * Remove an object layer by ID.
230
+ * @returns true if the layer was found and removed.
231
+ */
232
+ removeObjectLayer(id) {
233
+ this._checkDestroyed();
234
+ const index = this._objectLayers.findIndex((layer) => layer.id === id);
235
+ if (index === -1) return false;
236
+ this._objectLayers.splice(index, 1);
237
+ this._revision++;
238
+ return true;
239
+ }
240
+ /** Immutable snapshot of image layers (insertion order). */
241
+ get imageLayers() {
242
+ return this._imageLayers;
243
+ }
244
+ /**
245
+ * Add an image layer after construction. Appended to both {@link
246
+ * imageLayers} and the end of {@link renderableLayers}'s document order.
247
+ * @throws If an image layer with the same ID already exists. An ID shared
248
+ * with a *tile* layer is permitted - the two kinds keep separate ID
249
+ * spaces, and all internal bookkeeping is instance-based.
250
+ */
251
+ addImageLayer(layer) {
252
+ this._checkDestroyed();
253
+ this._addImageLayer(layer);
254
+ this._documentOrder.push(layer);
255
+ this._revision++;
256
+ }
257
+ /** Shared by the constructor and {@link addImageLayer}: enforce ID uniqueness within the image-layer kind. */
258
+ _addImageLayer(layer) {
259
+ if (this._imageLayers.some((existing) => existing.id === layer.id)) throw new Error(`Image layer ID ${layer.id} already exists in map "${this.name}".`);
260
+ this._imageLayers.push(layer);
261
+ }
262
+ /**
263
+ * Get an image layer by name (first match in insertion order), or undefined.
264
+ */
265
+ getImageLayer(name) {
266
+ return this._imageLayers.find((layer) => layer.name === name);
267
+ }
268
+ /**
269
+ * Get an image layer by ID.
270
+ */
271
+ getImageLayerById(id) {
272
+ return this._imageLayers.find((layer) => layer.id === id);
273
+ }
274
+ /**
275
+ * Remove an image layer by ID. Spliced out of {@link renderableLayers}'s
276
+ * document order.
277
+ * @returns true if the layer was found and removed.
278
+ */
279
+ removeImageLayer(id) {
280
+ this._checkDestroyed();
281
+ const layer = this._imageLayers.find((l) => l.id === id);
282
+ if (!layer) return false;
283
+ this._imageLayers.splice(this._imageLayers.indexOf(layer), 1);
284
+ const orderIndex = this._documentOrder.indexOf(layer);
285
+ if (orderIndex !== -1) this._documentOrder.splice(orderIndex, 1);
286
+ this._revision++;
287
+ return true;
288
+ }
289
+ /**
290
+ * Tile and image layers combined into a single document order (see {@link
291
+ * TileMapOptions.documentOrder}), maintained live as membership changes.
292
+ * Reflects `addLayer` / `addImageLayer` / `removeLayer` /
293
+ * `removeImageLayer` calls made after construction. Holds instance
294
+ * references, so entries stay correct even when a tile layer and an image
295
+ * layer share an id in a fallback-ordered map. Object layers are
296
+ * data-only and never appear here.
297
+ */
298
+ get renderableLayers() {
299
+ return this._documentOrder;
300
+ }
301
+ /**
302
+ * Create a new {@link TileMapView} that groups this map's layers into
303
+ * independently placeable band / layer scene nodes for interleaving
304
+ * application actors between tile layers.
305
+ *
306
+ * Each call returns a fresh, independent view - the map does **not** cache a
307
+ * single global view, so multiple coexisting views of the same map are
308
+ * allowed. The view references this map but never owns it: destroying the
309
+ * view frees only its generated layer/band nodes - never the map, its layers,
310
+ * tileset textures, or any application actors.
311
+ *
312
+ * @advanced
313
+ */
314
+ createView(options) {
315
+ return new TileMapView(this, options);
316
+ }
317
+ /**
318
+ * Get a resolved tile from a given layer at tile coordinates.
319
+ * Convenience for `map.getTileLayerById(id)?.getTileAt(tx, ty)`.
320
+ * Returns null for an empty cell, out-of-bounds, or missing layer.
321
+ */
322
+ getTileAt(layerId, tx, ty) {
323
+ const layer = this._layerById.get(layerId);
324
+ if (!layer) return null;
325
+ return layer.getTileAt(tx, ty);
326
+ }
327
+ /**
328
+ * Set a tile on a given layer at tile coordinates.
329
+ * Convenience for `map.getTileLayerById(id)?.setTileAt(tx, ty, tile)`.
330
+ * @throws If the layer does not exist, coordinates are out of bounds,
331
+ * or the tile reference is invalid.
332
+ */
333
+ setTileAt(layerId, tx, ty, tile) {
334
+ const layer = this._layerById.get(layerId);
335
+ if (!layer) throw new Error(`Layer ${layerId} not found in map "${this.name}".`);
336
+ layer.setTileAt(tx, ty, tile);
337
+ }
338
+ /**
339
+ * Clear a tile on a given layer at tile coordinates.
340
+ * Convenience for `map.getTileLayerById(id)?.clearTileAt(tx, ty)`.
341
+ * @throws If the layer does not exist or coordinates are out of bounds.
342
+ */
343
+ clearTileAt(layerId, tx, ty) {
344
+ const layer = this._layerById.get(layerId);
345
+ if (!layer) throw new Error(`Layer ${layerId} not found in map "${this.name}".`);
346
+ layer.clearTileAt(tx, ty);
347
+ }
348
+ /**
349
+ * Convert a tile coordinate to the pixel position of its top-left corner
350
+ * in map-local space (ignoring layer offsets).
351
+ */
352
+ tileToPixel(tx, ty) {
353
+ return {
354
+ x: tx * this.tileWidth,
355
+ y: ty * this.tileHeight
356
+ };
357
+ }
358
+ /**
359
+ * Convert a pixel position in map-local space to the tile coordinate
360
+ * that contains it. Uses `floor`. May return coordinates outside map bounds.
361
+ */
362
+ pixelToTile(px, py) {
363
+ return {
364
+ tx: Math.floor(px / this.tileWidth),
365
+ ty: Math.floor(py / this.tileHeight)
366
+ };
367
+ }
368
+ /**
369
+ * Monotonic map revision counter. Increments on structural changes only
370
+ * (add/remove layer, add tileset). Cell mutations are tracked per-chunk
371
+ * and per-layer; the renderer reads chunk-level revisions directly.
372
+ * @advanced
373
+ */
374
+ get revision() {
375
+ return this._revision;
376
+ }
377
+ /** Whether the map has been destroyed. */
378
+ get destroyed() {
379
+ return this._destroyed;
380
+ }
381
+ _checkDestroyed() {
382
+ if (this._destroyed) throw new Error(`TileMap "${this.name}" has been destroyed.`);
383
+ }
384
+ /**
385
+ * Destroy the map and all owned layers and chunk storage.
386
+ *
387
+ * Is idempotent. Does NOT destroy tileset textures (Loader-owned) or
388
+ * any SceneNodes (those do not exist yet in this slice).
389
+ */
390
+ destroy() {
391
+ if (this._destroyed) return;
392
+ this._destroyed = true;
393
+ for (const layer of this._layers) layer.destroy();
394
+ this._layers.length = 0;
395
+ this._layerById.clear();
396
+ this._objectLayers.length = 0;
397
+ this._imageLayers.length = 0;
398
+ this._documentOrder.length = 0;
399
+ this._tilesets.length = 0;
400
+ }
401
+ };
359
402
 
403
+ //#endregion
360
404
  export { TileMap };
361
- //# sourceMappingURL=TileMap.js.map
405
+ //# sourceMappingURL=TileMap.js.map