@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.
- package/README.md +33 -26
- package/dist/esm/ChunkSource.d.ts +33 -0
- package/dist/esm/ChunkSource.d.ts.map +1 -0
- package/dist/esm/ChunkStreamer.d.ts +98 -0
- package/dist/esm/ChunkStreamer.d.ts.map +1 -0
- package/dist/esm/ChunkStreamer.js +201 -0
- package/dist/esm/ChunkStreamer.js.map +1 -0
- package/dist/esm/ImageLayer.d.ts +11 -3
- package/dist/esm/ImageLayer.d.ts.map +1 -0
- package/dist/esm/ImageLayer.js +71 -64
- package/dist/esm/ImageLayer.js.map +1 -1
- package/dist/esm/ImageLayerNode.d.ts +89 -0
- package/dist/esm/ImageLayerNode.d.ts.map +1 -0
- package/dist/esm/ImageLayerNode.js +205 -0
- package/dist/esm/ImageLayerNode.js.map +1 -0
- package/dist/esm/MapObject.d.ts +70 -0
- package/dist/esm/MapObject.d.ts.map +1 -0
- package/dist/esm/MapObject.js +34 -0
- package/dist/esm/MapObject.js.map +1 -0
- package/dist/esm/MapObjectSpawner.d.ts +133 -0
- package/dist/esm/MapObjectSpawner.d.ts.map +1 -0
- package/dist/esm/MapObjectSpawner.js +147 -0
- package/dist/esm/MapObjectSpawner.js.map +1 -0
- package/dist/esm/MapSpawnSession.d.ts +53 -0
- package/dist/esm/MapSpawnSession.d.ts.map +1 -0
- package/dist/esm/MapSpawnSession.js +88 -0
- package/dist/esm/MapSpawnSession.js.map +1 -0
- package/dist/esm/MapWorld.d.ts +134 -0
- package/dist/esm/MapWorld.d.ts.map +1 -0
- package/dist/esm/MapWorld.js +150 -0
- package/dist/esm/MapWorld.js.map +1 -0
- package/dist/esm/MapWorldRuntime.d.ts +182 -0
- package/dist/esm/MapWorldRuntime.d.ts.map +1 -0
- package/dist/esm/MapWorldRuntime.js +261 -0
- package/dist/esm/MapWorldRuntime.js.map +1 -0
- package/dist/esm/ObjectLayer.d.ts +93 -7
- package/dist/esm/ObjectLayer.d.ts.map +1 -0
- package/dist/esm/ObjectLayer.js +184 -167
- package/dist/esm/ObjectLayer.js.map +1 -1
- package/dist/esm/SampledChunkSource.d.ts +43 -0
- package/dist/esm/SampledChunkSource.d.ts.map +1 -0
- package/dist/esm/SampledChunkSource.js +48 -0
- package/dist/esm/SampledChunkSource.js.map +1 -0
- package/dist/esm/TileAnimator.d.ts +12 -2
- package/dist/esm/TileAnimator.d.ts.map +1 -0
- package/dist/esm/TileAnimator.js +154 -165
- package/dist/esm/TileAnimator.js.map +1 -1
- package/dist/esm/TileChunk.d.ts +30 -5
- package/dist/esm/TileChunk.d.ts.map +1 -0
- package/dist/esm/TileChunk.js +190 -188
- package/dist/esm/TileChunk.js.map +1 -1
- package/dist/esm/TileChunkNode.d.ts +18 -3
- package/dist/esm/TileChunkNode.d.ts.map +1 -0
- package/dist/esm/TileChunkNode.js +104 -85
- package/dist/esm/TileChunkNode.js.map +1 -1
- package/dist/esm/TileLayer.d.ts +104 -24
- package/dist/esm/TileLayer.d.ts.map +1 -0
- package/dist/esm/TileLayer.js +511 -469
- package/dist/esm/TileLayer.js.map +1 -1
- package/dist/esm/TileLayerNode.d.ts +52 -11
- package/dist/esm/TileLayerNode.d.ts.map +1 -0
- package/dist/esm/TileLayerNode.js +224 -166
- package/dist/esm/TileLayerNode.js.map +1 -1
- package/dist/esm/TileMap.d.ts +96 -31
- package/dist/esm/TileMap.d.ts.map +1 -0
- package/dist/esm/TileMap.js +401 -357
- package/dist/esm/TileMap.js.map +1 -1
- package/dist/esm/TileMapBand.d.ts +42 -29
- package/dist/esm/TileMapBand.d.ts.map +1 -0
- package/dist/esm/TileMapBand.js +151 -158
- package/dist/esm/TileMapBand.js.map +1 -1
- package/dist/esm/TileMapNode.d.ts +34 -18
- package/dist/esm/TileMapNode.d.ts.map +1 -0
- package/dist/esm/TileMapNode.js +122 -109
- package/dist/esm/TileMapNode.js.map +1 -1
- package/dist/esm/TileMapView.d.ts +108 -47
- package/dist/esm/TileMapView.d.ts.map +1 -0
- package/dist/esm/TileMapView.js +385 -338
- package/dist/esm/TileMapView.js.map +1 -1
- package/dist/esm/TileSet.d.ts +15 -6
- package/dist/esm/TileSet.d.ts.map +1 -0
- package/dist/esm/TileSet.js +161 -181
- package/dist/esm/TileSet.js.map +1 -1
- package/dist/esm/WangSet.d.ts +5 -4
- package/dist/esm/WangSet.d.ts.map +1 -0
- package/dist/esm/WangSet.js +70 -78
- package/dist/esm/WangSet.js.map +1 -1
- package/dist/esm/WorkerSampledChunkSource.d.ts +95 -0
- package/dist/esm/WorkerSampledChunkSource.d.ts.map +1 -0
- package/dist/esm/WorkerSampledChunkSource.js +117 -0
- package/dist/esm/WorkerSampledChunkSource.js.map +1 -0
- package/dist/esm/autoTile.d.ts +10 -5
- package/dist/esm/autoTile.d.ts.map +1 -0
- package/dist/esm/autoTile.js +164 -205
- package/dist/esm/autoTile.js.map +1 -1
- package/dist/esm/chunkGeometry.d.ts +8 -7
- package/dist/esm/chunkGeometry.d.ts.map +1 -0
- package/dist/esm/chunkGeometry.js +83 -94
- package/dist/esm/chunkGeometry.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +26 -15
- package/dist/esm/nodeBounds.d.ts +20 -0
- package/dist/esm/nodeBounds.d.ts.map +1 -0
- package/dist/esm/nodeBounds.js +40 -0
- package/dist/esm/nodeBounds.js.map +1 -0
- package/dist/esm/pixelSnap.d.ts +4 -3
- package/dist/esm/pixelSnap.d.ts.map +1 -0
- package/dist/esm/pixelSnap.js +18 -16
- package/dist/esm/pixelSnap.js.map +1 -1
- package/dist/esm/public.d.ts +23 -3
- package/dist/esm/public.d.ts.map +1 -0
- package/dist/esm/tileCollision.d.ts +190 -0
- package/dist/esm/tileCollision.d.ts.map +1 -0
- package/dist/esm/tileCollision.js +367 -0
- package/dist/esm/tileCollision.js.map +1 -0
- package/dist/esm/tileWord.d.ts +12 -0
- package/dist/esm/tileWord.d.ts.map +1 -0
- package/dist/esm/tileWord.js +16 -0
- package/dist/esm/tileWord.js.map +1 -0
- package/dist/esm/tilemapExtension.d.ts +4 -3
- package/dist/esm/tilemapExtension.d.ts.map +1 -0
- package/dist/esm/tilemapExtension.js +47 -47
- package/dist/esm/tilemapExtension.js.map +1 -1
- package/dist/esm/tilemapSerializers.d.ts +5 -4
- package/dist/esm/tilemapSerializers.d.ts.map +1 -0
- package/dist/esm/tilemapSerializers.js +48 -41
- package/dist/esm/tilemapSerializers.js.map +1 -1
- package/dist/esm/types.d.ts +34 -18
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +111 -120
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts +50 -2
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts.map +1 -0
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.js +366 -332
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.js.map +1 -1
- package/dist/esm/webgl2/shaders/tile-chunk.frag.js +6 -0
- package/dist/esm/webgl2/shaders/tile-chunk.frag.js.map +1 -0
- package/dist/esm/webgl2/shaders/tile-chunk.vert.js +6 -0
- package/dist/esm/webgl2/shaders/tile-chunk.vert.js.map +1 -0
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts +70 -4
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts.map +1 -0
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.js +496 -383
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.js.map +1 -1
- package/dist/esm/webgpu/shaders/tile-chunk.wgsl.js +6 -0
- package/dist/esm/webgpu/shaders/tile-chunk.wgsl.js.map +1 -0
- package/package.json +8 -15
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/register.d.ts +0 -1
- package/dist/esm/register.js +0 -25
- package/dist/esm/register.js.map +0 -1
package/dist/esm/TileLayer.js
CHANGED
|
@@ -1,474 +1,516 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { packTile, tileToChunkCoord, tileToLocalInChunk, unpackTile, validateInteger, validateNonNegativeInteger, validatePairedDimensions, validatePositiveInteger } from "./types.js";
|
|
2
|
+
import { TileChunk } from "./TileChunk.js";
|
|
3
3
|
|
|
4
|
+
//#region src/TileLayer.ts
|
|
4
5
|
const DEFAULT_CHUNK_SIZE = 32;
|
|
6
|
+
/** Resolve and validate the optional uniform parallax scale. */
|
|
7
|
+
const resolveParallaxScale = (value = 1) => {
|
|
8
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error("TileLayer parallaxScale must be a positive finite number.");
|
|
9
|
+
return value;
|
|
10
|
+
};
|
|
5
11
|
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
12
|
+
* Validate a {@link TileLayerOptions} bag and resolve its optional fields
|
|
13
|
+
* (chunk size, opacity, offset, parallax) to concrete defaults.
|
|
14
|
+
* @throws When dimensions, chunk size, or other options are invalid.
|
|
15
|
+
*/
|
|
16
|
+
const validateTileLayerOptions = (options) => {
|
|
17
|
+
validateNonNegativeInteger(options.id, "layer.id");
|
|
18
|
+
if (!options.name || typeof options.name !== "string") throw new Error("TileLayer name must be a non-empty string.");
|
|
19
|
+
validatePairedDimensions(options.width, options.height, "TileLayer", "layer");
|
|
20
|
+
validatePositiveInteger(options.tileWidth, "layer.tileWidth");
|
|
21
|
+
validatePositiveInteger(options.tileHeight, "layer.tileHeight");
|
|
22
|
+
const chunkWidth = options.chunkWidth ?? DEFAULT_CHUNK_SIZE;
|
|
23
|
+
const chunkHeight = options.chunkHeight ?? DEFAULT_CHUNK_SIZE;
|
|
24
|
+
validatePositiveInteger(chunkWidth, "chunkWidth");
|
|
25
|
+
validatePositiveInteger(chunkHeight, "chunkHeight");
|
|
26
|
+
if (!Array.isArray(options.tilesets)) throw new Error("TileLayer tilesets must be an array.");
|
|
27
|
+
const opacity = options.opacity ?? 1;
|
|
28
|
+
if (typeof opacity !== "number" || opacity < 0 || opacity > 1) throw new Error(`TileLayer opacity must be 0..1 (got ${opacity}).`);
|
|
29
|
+
const offsetX = options.offsetX ?? 0;
|
|
30
|
+
const offsetY = options.offsetY ?? 0;
|
|
31
|
+
if (!Number.isFinite(offsetX) || !Number.isFinite(offsetY)) throw new Error("TileLayer offset must be finite numbers.");
|
|
32
|
+
const parallaxX = options.parallaxX ?? 1;
|
|
33
|
+
const parallaxY = options.parallaxY ?? 1;
|
|
34
|
+
if (!Number.isFinite(parallaxX) || !Number.isFinite(parallaxY)) throw new Error("TileLayer parallax must be finite numbers.");
|
|
35
|
+
return {
|
|
36
|
+
chunkWidth,
|
|
37
|
+
chunkHeight,
|
|
38
|
+
opacity,
|
|
39
|
+
offsetX,
|
|
40
|
+
offsetY,
|
|
41
|
+
parallaxX,
|
|
42
|
+
parallaxY,
|
|
43
|
+
parallaxScale: resolveParallaxScale(options.parallaxScale)
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* A generic, format-independent tile layer with chunk-first storage.
|
|
48
|
+
*
|
|
49
|
+
* Tile data is stored in fixed-size {@link TileChunk}s indexed by signed
|
|
50
|
+
* chunk coordinates. For finite maps, only chunks that intersect the layer
|
|
51
|
+
* bounds exist.
|
|
52
|
+
*
|
|
53
|
+
* **Mutation must go through the layer's public APIs only.**
|
|
54
|
+
* `setTileAt` / `clearTileAt` validate coordinates, tileset references,
|
|
55
|
+
* and increment revision counters only when the stored value actually
|
|
56
|
+
* changes. Direct chunk mutation is not supported - the layer owns chunk
|
|
57
|
+
* storage and exposes only a {@link ReadonlyTileChunk} view.
|
|
58
|
+
*
|
|
59
|
+
* The layer is NOT a SceneNode - that integration lives in
|
|
60
|
+
* {@link import('./TileLayerNode').TileLayerNode}.
|
|
61
|
+
*
|
|
62
|
+
* @advanced
|
|
63
|
+
*/
|
|
64
|
+
var TileLayer = class {
|
|
65
|
+
/** Stable unique ID within the map. */
|
|
66
|
+
id;
|
|
67
|
+
/** Display name (may not be unique). */
|
|
68
|
+
name;
|
|
69
|
+
/** Width in tiles, or `undefined` if unbounded. */
|
|
70
|
+
width;
|
|
71
|
+
/** Height in tiles, or `undefined` if unbounded. */
|
|
72
|
+
height;
|
|
73
|
+
/** Chunk width (tiles). */
|
|
74
|
+
chunkWidth;
|
|
75
|
+
/** Chunk height (tiles). */
|
|
76
|
+
chunkHeight;
|
|
77
|
+
/** Tile width in pixels. */
|
|
78
|
+
tileWidth;
|
|
79
|
+
/** Tile height in pixels. */
|
|
80
|
+
tileHeight;
|
|
81
|
+
/** `true` if this layer has a fixed width/height; `false` if unbounded. */
|
|
82
|
+
get bounded() {
|
|
83
|
+
return this.width !== void 0 && this.height !== void 0;
|
|
84
|
+
}
|
|
85
|
+
/** Pixel width, or `undefined` if unbounded. */
|
|
86
|
+
get pixelWidth() {
|
|
87
|
+
return this.width === void 0 ? void 0 : this.width * this.tileWidth;
|
|
88
|
+
}
|
|
89
|
+
/** Pixel height, or `undefined` if unbounded. */
|
|
90
|
+
get pixelHeight() {
|
|
91
|
+
return this.height === void 0 ? void 0 : this.height * this.tileHeight;
|
|
92
|
+
}
|
|
93
|
+
/** Visibility flag (mutable). */
|
|
94
|
+
visible;
|
|
95
|
+
/** Opacity 0..1 (mutable). */
|
|
96
|
+
opacity;
|
|
97
|
+
/** Horizontal pixel offset (mutable). */
|
|
98
|
+
offsetX;
|
|
99
|
+
/** Vertical pixel offset (mutable). */
|
|
100
|
+
offsetY;
|
|
101
|
+
/**
|
|
102
|
+
* Parallax scroll factor on the X axis.
|
|
103
|
+
* `1.0` = full camera speed, `0.5` = half speed, `0.0` = stationary.
|
|
104
|
+
*/
|
|
105
|
+
parallaxX;
|
|
106
|
+
/**
|
|
107
|
+
* Parallax scroll factor on the Y axis.
|
|
108
|
+
* `1.0` = full camera speed, `0.5` = half speed, `0.0` = stationary.
|
|
109
|
+
*/
|
|
110
|
+
parallaxY;
|
|
111
|
+
/** Uniform scale applied around the layer origin during parallax rendering. */
|
|
112
|
+
parallaxScale;
|
|
113
|
+
/** Layer class/type string (Tiled `class`; may be empty). */
|
|
114
|
+
class;
|
|
115
|
+
/** Multiplicative layer tint as `0xRRGGBB`, or `null` for no tint. */
|
|
116
|
+
tintColor;
|
|
117
|
+
/** Immutable layer properties. */
|
|
118
|
+
properties;
|
|
119
|
+
/** The tilesets available to this layer (shared array reference). */
|
|
120
|
+
tilesets;
|
|
121
|
+
/** Chunk storage: chunkKey → mutable TileChunk (internal). */
|
|
122
|
+
_chunks = /* @__PURE__ */ new Map();
|
|
123
|
+
/** Package-internal structural (adopt/evict) listeners. */
|
|
124
|
+
_structuralListeners = null;
|
|
125
|
+
/**
|
|
126
|
+
* Monotonic layer revision counter.
|
|
127
|
+
* Increments on every cell mutation that actually changes a stored value.
|
|
128
|
+
* Does NOT increment on no-op writes or failed mutations.
|
|
129
|
+
*/
|
|
130
|
+
_revision = 0;
|
|
131
|
+
/** Whether the layer has been destroyed. */
|
|
132
|
+
_destroyed = false;
|
|
133
|
+
/**
|
|
134
|
+
* @throws When dimensions, chunk size, or other options are invalid.
|
|
135
|
+
*/
|
|
136
|
+
constructor(options) {
|
|
137
|
+
const { chunkWidth, chunkHeight, opacity, offsetX, offsetY, parallaxX, parallaxY, parallaxScale } = validateTileLayerOptions(options);
|
|
138
|
+
this.id = options.id;
|
|
139
|
+
this.name = options.name;
|
|
140
|
+
this.width = options.width;
|
|
141
|
+
this.height = options.height;
|
|
142
|
+
this.chunkWidth = chunkWidth;
|
|
143
|
+
this.chunkHeight = chunkHeight;
|
|
144
|
+
this.tileWidth = options.tileWidth;
|
|
145
|
+
this.tileHeight = options.tileHeight;
|
|
146
|
+
this.tilesets = options.tilesets;
|
|
147
|
+
this.visible = options.visible ?? true;
|
|
148
|
+
this.opacity = opacity;
|
|
149
|
+
this.offsetX = offsetX;
|
|
150
|
+
this.offsetY = offsetY;
|
|
151
|
+
this.parallaxX = parallaxX;
|
|
152
|
+
this.parallaxY = parallaxY;
|
|
153
|
+
this.parallaxScale = parallaxScale;
|
|
154
|
+
this.class = options.class ?? "";
|
|
155
|
+
this.tintColor = options.tintColor ?? null;
|
|
156
|
+
this.properties = options.properties ? Object.freeze({ ...options.properties }) : Object.freeze({});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Check whether a tile coordinate lies within the layer bounds.
|
|
160
|
+
* Always `true` for an unbounded layer.
|
|
161
|
+
* @advanced
|
|
162
|
+
*/
|
|
163
|
+
inBounds(tx, ty) {
|
|
164
|
+
if (this.width === void 0 || this.height === void 0) return true;
|
|
165
|
+
return tx >= 0 && tx < this.width && ty >= 0 && ty < this.height;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Compute the range of chunk coordinates that intersect this layer, or
|
|
169
|
+
* `null` if the layer is unbounded (any signed chunk coordinate is valid).
|
|
170
|
+
*/
|
|
171
|
+
chunkRange() {
|
|
172
|
+
if (this.width === void 0 || this.height === void 0) return null;
|
|
173
|
+
return {
|
|
174
|
+
minCx: 0,
|
|
175
|
+
minCy: 0,
|
|
176
|
+
maxCx: Math.floor((this.width - 1) / this.chunkWidth),
|
|
177
|
+
maxCy: Math.floor((this.height - 1) / this.chunkHeight)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
_chunkKey(cx, cy) {
|
|
181
|
+
return `${cx},${cy}`;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get a readonly view of a chunk by signed chunk coordinates,
|
|
185
|
+
* or undefined if it does not exist (never been touched).
|
|
186
|
+
* @advanced
|
|
187
|
+
*/
|
|
188
|
+
getChunk(cx, cy) {
|
|
189
|
+
return this._chunks.get(this._chunkKey(cx, cy));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get or create a chunk at the given coordinates.
|
|
193
|
+
* For a bounded layer, creation is only allowed within the valid chunk
|
|
194
|
+
* range and edge chunks are clamped to the layer's remaining size. For an
|
|
195
|
+
* unbounded layer, any signed chunk coordinate is accepted and every
|
|
196
|
+
* chunk is full-size ({@link chunkWidth} × {@link chunkHeight}).
|
|
197
|
+
*
|
|
198
|
+
* @internal Package-private: used by {@link setTileAt}, {@link fillRect},
|
|
199
|
+
* and future adapter ingest. External users should not allocate
|
|
200
|
+
* chunks - use {@link setTileAt} to populate tiles.
|
|
201
|
+
*/
|
|
202
|
+
_ensureChunk(cx, cy) {
|
|
203
|
+
this._checkDestroyed();
|
|
204
|
+
const key = this._chunkKey(cx, cy);
|
|
205
|
+
let chunk = this._chunks.get(key);
|
|
206
|
+
if (!chunk) {
|
|
207
|
+
const range = this.chunkRange();
|
|
208
|
+
let cw = this.chunkWidth;
|
|
209
|
+
let ch = this.chunkHeight;
|
|
210
|
+
if (range !== null && this.width !== void 0 && this.height !== void 0) {
|
|
211
|
+
if (cx < range.minCx || cx > range.maxCx || cy < range.minCy || cy > range.maxCy) throw new Error(`Chunk (${cx}, ${cy}) outside layer chunk range [${range.minCx}..${range.maxCx}, ${range.minCy}..${range.maxCy}].`);
|
|
212
|
+
const startTx = cx * this.chunkWidth;
|
|
213
|
+
const startTy = cy * this.chunkHeight;
|
|
214
|
+
cw = Math.min(this.chunkWidth, this.width - startTx);
|
|
215
|
+
ch = Math.min(this.chunkHeight, this.height - startTy);
|
|
216
|
+
}
|
|
217
|
+
chunk = new TileChunk(cx, cy, cw, ch);
|
|
218
|
+
this._chunks.set(key, chunk);
|
|
219
|
+
}
|
|
220
|
+
return chunk;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Install a chunk-provider-supplied payload directly, bypassing per-tile
|
|
224
|
+
* `setTileAt` cost. Overwrites any existing chunk at `(cx, cy)`. Always
|
|
225
|
+
* bumps the layer revision (installing a chunk is always a structural
|
|
226
|
+
* change, even if the payload happens to be all-zero).
|
|
227
|
+
*
|
|
228
|
+
* Does not validate `(cx, cy)` against {@link chunkRange} - callers
|
|
229
|
+
* (chunk-streaming controllers) are trusted to request coordinates within
|
|
230
|
+
* whatever range they intend to manage.
|
|
231
|
+
*
|
|
232
|
+
* @internal Package-private: for future chunk-provider/streaming use.
|
|
233
|
+
*/
|
|
234
|
+
_adoptChunk(cx, cy, payload) {
|
|
235
|
+
this._checkDestroyed();
|
|
236
|
+
const chunk = new TileChunk(cx, cy, payload.width, payload.height, payload.tiles);
|
|
237
|
+
this._chunks.set(this._chunkKey(cx, cy), chunk);
|
|
238
|
+
this._revision++;
|
|
239
|
+
this._notifyStructural({
|
|
240
|
+
cx,
|
|
241
|
+
cy,
|
|
242
|
+
chunk
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Evict a chunk from storage, freeing it for garbage collection. No-op if
|
|
247
|
+
* no chunk is loaded at `(cx, cy)`.
|
|
248
|
+
*
|
|
249
|
+
* @returns `true` if a chunk was found and removed, `false` otherwise.
|
|
250
|
+
* @internal Package-private: for future chunk-streaming eviction use.
|
|
251
|
+
*/
|
|
252
|
+
_evictChunk(cx, cy) {
|
|
253
|
+
this._checkDestroyed();
|
|
254
|
+
const key = this._chunkKey(cx, cy);
|
|
255
|
+
if (!this._chunks.delete(key)) return false;
|
|
256
|
+
this._revision++;
|
|
257
|
+
this._notifyStructural({
|
|
258
|
+
cx,
|
|
259
|
+
cy,
|
|
260
|
+
chunk: null
|
|
261
|
+
});
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Register a callback invoked synchronously whenever a chunk is installed
|
|
266
|
+
* ({@link _adoptChunk}) or evicted ({@link _evictChunk}). Package-internal:
|
|
267
|
+
* {@link import('./TileLayerNode').TileLayerNode} subscribes to keep its
|
|
268
|
+
* chunk-node children in sync with chunk-provider-driven adopt/evict calls
|
|
269
|
+
* (e.g. from {@link import('./ChunkStreamer').ChunkStreamer}) without
|
|
270
|
+
* needing a full {@link import('./TileLayerNode').TileLayerNode.refresh}.
|
|
271
|
+
* @internal
|
|
272
|
+
*/
|
|
273
|
+
_addStructuralListener(listener) {
|
|
274
|
+
(this._structuralListeners ??= /* @__PURE__ */ new Set()).add(listener);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Unregister a listener added via {@link _addStructuralListener} (node destroy).
|
|
278
|
+
* @internal
|
|
279
|
+
*/
|
|
280
|
+
_removeStructuralListener(listener) {
|
|
281
|
+
this._structuralListeners?.delete(listener);
|
|
282
|
+
}
|
|
283
|
+
_notifyStructural(event) {
|
|
284
|
+
if (this._structuralListeners === null) return;
|
|
285
|
+
for (const listener of this._structuralListeners) listener(event);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Iterate over all loaded chunks in deterministic (cy, cx) ascending order.
|
|
289
|
+
* Returns readonly chunk views - callers cannot mutate storage.
|
|
290
|
+
* @advanced
|
|
291
|
+
*/
|
|
292
|
+
loadedChunks() {
|
|
293
|
+
const entries = [...this._chunks.values()];
|
|
294
|
+
entries.sort((a, b) => a.cy - b.cy || a.cx - b.cx);
|
|
295
|
+
return entries[Symbol.iterator]();
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Get the raw packed tile word at (tx, ty). Returns 0 for empty or out-of-bounds.
|
|
299
|
+
* @advanced
|
|
300
|
+
*/
|
|
301
|
+
getRawTileAt(tx, ty) {
|
|
302
|
+
validateInteger(tx, "tx");
|
|
303
|
+
validateInteger(ty, "ty");
|
|
304
|
+
if (!this.inBounds(tx, ty)) return 0;
|
|
305
|
+
const { cx, cy } = tileToChunkCoord(tx, ty, this.chunkWidth, this.chunkHeight);
|
|
306
|
+
const chunk = this._chunks.get(this._chunkKey(cx, cy));
|
|
307
|
+
if (!chunk) return 0;
|
|
308
|
+
const { lx, ly } = tileToLocalInChunk(tx, ty, this.chunkWidth, this.chunkHeight);
|
|
309
|
+
return chunk.getRawAt(lx, ly);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Query a resolved tile at (tx, ty). Returns null for empty or out-of-bounds.
|
|
313
|
+
* @advanced
|
|
314
|
+
*/
|
|
315
|
+
getTileAt(tx, ty) {
|
|
316
|
+
const packed = this.getRawTileAt(tx, ty);
|
|
317
|
+
if (packed === 0) return null;
|
|
318
|
+
const decoded = unpackTile(packed);
|
|
319
|
+
if (!decoded) return null;
|
|
320
|
+
if (decoded.tilesetIndex >= this.tilesets.length) return null;
|
|
321
|
+
const tileset = this.tilesets[decoded.tilesetIndex];
|
|
322
|
+
if (decoded.localTileId >= tileset.tileCount) return null;
|
|
323
|
+
return {
|
|
324
|
+
tileset,
|
|
325
|
+
localTileId: decoded.localTileId,
|
|
326
|
+
transform: decoded.transform
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Validate a tile reference against the layer's tilesets.
|
|
331
|
+
* Returns the packed form or throws.
|
|
332
|
+
*/
|
|
333
|
+
_validateTileRef(tile) {
|
|
334
|
+
if (!tile?.tileset) throw new Error("setTileAt requires a valid ResolvedTile.");
|
|
335
|
+
const tilesetIndex = this.tilesets.indexOf(tile.tileset);
|
|
336
|
+
if (tilesetIndex === -1) throw new Error(`Tileset "${tile.tileset.name}" is not available to layer "${this.name}".`);
|
|
337
|
+
if (tile.localTileId < 0 || tile.localTileId >= tile.tileset.tileCount) throw new Error(`localTileId ${tile.localTileId} out of range for tileset "${tile.tileset.name}" (max ${tile.tileset.tileCount - 1}).`);
|
|
338
|
+
return packTile(tilesetIndex, tile.localTileId, tile.transform);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Set a tile at the given tile coordinates.
|
|
342
|
+
* No-op if the effective value is unchanged.
|
|
343
|
+
* @throws If coordinates are out of bounds or the tile reference is invalid.
|
|
344
|
+
* @advanced
|
|
345
|
+
*/
|
|
346
|
+
setTileAt(tx, ty, tile) {
|
|
347
|
+
this._checkDestroyed();
|
|
348
|
+
validateInteger(tx, "tx");
|
|
349
|
+
validateInteger(ty, "ty");
|
|
350
|
+
if (!this.inBounds(tx, ty)) {
|
|
351
|
+
const boundsMsg = this.width !== void 0 && this.height !== void 0 ? `[0..${this.width - 1}, 0..${this.height - 1}]` : "[unbounded]";
|
|
352
|
+
throw new Error(`setTileAt (${tx}, ${ty}) out of bounds ${boundsMsg}.`);
|
|
353
|
+
}
|
|
354
|
+
const packed = this._validateTileRef(tile);
|
|
355
|
+
const { cx, cy } = tileToChunkCoord(tx, ty, this.chunkWidth, this.chunkHeight);
|
|
356
|
+
const chunk = this._ensureChunk(cx, cy);
|
|
357
|
+
const { lx, ly } = tileToLocalInChunk(tx, ty, this.chunkWidth, this.chunkHeight);
|
|
358
|
+
if (chunk._setRawAt(lx, ly, packed)) this._revision++;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Clear (erase) the tile at the given coordinates.
|
|
362
|
+
* No-op if the cell is already empty.
|
|
363
|
+
* @throws If coordinates are out of bounds.
|
|
364
|
+
* @advanced
|
|
365
|
+
*/
|
|
366
|
+
clearTileAt(tx, ty) {
|
|
367
|
+
this._checkDestroyed();
|
|
368
|
+
validateInteger(tx, "tx");
|
|
369
|
+
validateInteger(ty, "ty");
|
|
370
|
+
if (!this.inBounds(tx, ty)) {
|
|
371
|
+
const boundsMsg = this.width !== void 0 && this.height !== void 0 ? `[0..${this.width - 1}, 0..${this.height - 1}]` : "[unbounded]";
|
|
372
|
+
throw new Error(`clearTileAt (${tx}, ${ty}) out of bounds ${boundsMsg}.`);
|
|
373
|
+
}
|
|
374
|
+
const { cx, cy } = tileToChunkCoord(tx, ty, this.chunkWidth, this.chunkHeight);
|
|
375
|
+
const chunk = this._chunks.get(this._chunkKey(cx, cy));
|
|
376
|
+
if (!chunk) return;
|
|
377
|
+
const { lx, ly } = tileToLocalInChunk(tx, ty, this.chunkWidth, this.chunkHeight);
|
|
378
|
+
if (chunk._setRawAt(lx, ly, 0)) this._revision++;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Fill a rectangular region with a tile.
|
|
382
|
+
* @advanced
|
|
383
|
+
*/
|
|
384
|
+
fillRect(x, y, w, h, tile) {
|
|
385
|
+
this._checkDestroyed();
|
|
386
|
+
const packed = this._validateTileRef(tile);
|
|
387
|
+
let changed = false;
|
|
388
|
+
for (let ty = y; ty < y + h; ty++) for (let tx = x; tx < x + w; tx++) {
|
|
389
|
+
if (!this.inBounds(tx, ty)) continue;
|
|
390
|
+
const { cx, cy } = tileToChunkCoord(tx, ty, this.chunkWidth, this.chunkHeight);
|
|
391
|
+
const chunk = this._ensureChunk(cx, cy);
|
|
392
|
+
const { lx, ly } = tileToLocalInChunk(tx, ty, this.chunkWidth, this.chunkHeight);
|
|
393
|
+
if (chunk._setRawAt(lx, ly, packed)) changed = true;
|
|
394
|
+
}
|
|
395
|
+
if (changed) this._revision++;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Clear a rectangular region.
|
|
399
|
+
* @advanced
|
|
400
|
+
*/
|
|
401
|
+
clearRect(x, y, w, h) {
|
|
402
|
+
this._checkDestroyed();
|
|
403
|
+
let changed = false;
|
|
404
|
+
for (let ty = y; ty < y + h; ty++) for (let tx = x; tx < x + w; tx++) {
|
|
405
|
+
if (!this.inBounds(tx, ty)) continue;
|
|
406
|
+
const { cx, cy } = tileToChunkCoord(tx, ty, this.chunkWidth, this.chunkHeight);
|
|
407
|
+
const chunk = this._chunks.get(this._chunkKey(cx, cy));
|
|
408
|
+
if (!chunk) continue;
|
|
409
|
+
const { lx, ly } = tileToLocalInChunk(tx, ty, this.chunkWidth, this.chunkHeight);
|
|
410
|
+
if (chunk._setRawAt(lx, ly, 0)) changed = true;
|
|
411
|
+
}
|
|
412
|
+
if (changed) this._revision++;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Iterate non-empty tiles within a rectangular region in row-major order.
|
|
416
|
+
* Yields (tx, ty, resolvedTile) tuples. Skips empty cells.
|
|
417
|
+
* @advanced
|
|
418
|
+
*/
|
|
419
|
+
*tilesInRect(x, y, w, h) {
|
|
420
|
+
const startCx = Math.floor(x / this.chunkWidth);
|
|
421
|
+
const endCx = Math.floor((x + w - 1) / this.chunkWidth);
|
|
422
|
+
const startCy = Math.floor(y / this.chunkHeight);
|
|
423
|
+
const endCy = Math.floor((y + h - 1) / this.chunkHeight);
|
|
424
|
+
for (let cy = startCy; cy <= endCy; cy++) for (let cx = startCx; cx <= endCx; cx++) {
|
|
425
|
+
const chunk = this._chunks.get(this._chunkKey(cx, cy));
|
|
426
|
+
if (!chunk || chunk.empty) continue;
|
|
427
|
+
const chunkStartTx = cx * this.chunkWidth;
|
|
428
|
+
const chunkStartTy = cy * this.chunkHeight;
|
|
429
|
+
const minLx = Math.max(0, x - chunkStartTx);
|
|
430
|
+
const maxLx = Math.min(chunk.width - 1, x + w - 1 - chunkStartTx);
|
|
431
|
+
const minLy = Math.max(0, y - chunkStartTy);
|
|
432
|
+
const maxLy = Math.min(chunk.height - 1, y + h - 1 - chunkStartTy);
|
|
433
|
+
for (let ly = minLy; ly <= maxLy; ly++) for (let lx = minLx; lx <= maxLx; lx++) {
|
|
434
|
+
const packed = chunk.getRawAt(lx, ly);
|
|
435
|
+
if (packed === 0) continue;
|
|
436
|
+
const decoded = unpackTile(packed);
|
|
437
|
+
if (!decoded) continue;
|
|
438
|
+
if (decoded.tilesetIndex >= this.tilesets.length) continue;
|
|
439
|
+
const tileset = this.tilesets[decoded.tilesetIndex];
|
|
440
|
+
if (decoded.localTileId >= tileset.tileCount) continue;
|
|
441
|
+
yield {
|
|
442
|
+
tx: chunkStartTx + lx,
|
|
443
|
+
ty: chunkStartTy + ly,
|
|
444
|
+
tile: {
|
|
445
|
+
tileset,
|
|
446
|
+
localTileId: decoded.localTileId,
|
|
447
|
+
transform: decoded.transform
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Convert a tile coordinate to the pixel position of its top-left corner
|
|
455
|
+
* in the layer's local space.
|
|
456
|
+
* @advanced
|
|
457
|
+
*/
|
|
458
|
+
tileToPixel(tx, ty) {
|
|
459
|
+
return {
|
|
460
|
+
x: tx * this.tileWidth + this.offsetX,
|
|
461
|
+
y: ty * this.tileHeight + this.offsetY
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Convert a local pixel position to the tile coordinate that contains it.
|
|
466
|
+
* Uses `floor`, so a point exactly on a tile boundary maps to the tile.
|
|
467
|
+
* May return coordinates outside layer bounds.
|
|
468
|
+
* @advanced
|
|
469
|
+
*/
|
|
470
|
+
pixelToTile(px, py) {
|
|
471
|
+
return {
|
|
472
|
+
tx: Math.floor((px - this.offsetX) / this.tileWidth),
|
|
473
|
+
ty: Math.floor((py - this.offsetY) / this.tileHeight)
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Monotonic layer revision counter.
|
|
478
|
+
* Increments on every cell mutation that changes a stored value.
|
|
479
|
+
* No-op writes and failed mutations do NOT increment.
|
|
480
|
+
* @advanced
|
|
481
|
+
*/
|
|
482
|
+
get revision() {
|
|
483
|
+
return this._revision;
|
|
484
|
+
}
|
|
485
|
+
/** Whether the layer has been destroyed. */
|
|
486
|
+
get destroyed() {
|
|
487
|
+
return this._destroyed;
|
|
488
|
+
}
|
|
489
|
+
_checkDestroyed() {
|
|
490
|
+
if (this._destroyed) throw new Error(`TileLayer "${this.name}" has been destroyed.`);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Destroy this layer: clear chunk storage and mark destroyed.
|
|
494
|
+
* Does NOT destroy tileset textures or external resources.
|
|
495
|
+
* Idempotent.
|
|
496
|
+
*/
|
|
497
|
+
destroy() {
|
|
498
|
+
if (this._destroyed) return;
|
|
499
|
+
this._destroyed = true;
|
|
500
|
+
this._chunks.clear();
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Total number of non-empty tiles across all chunks.
|
|
504
|
+
* Walk is cheap for dense layers; sparse layers benefit from empty-chunk fast path.
|
|
505
|
+
* @advanced
|
|
506
|
+
*/
|
|
507
|
+
countNonEmptyTiles() {
|
|
508
|
+
let count = 0;
|
|
509
|
+
for (const chunk of this._chunks.values()) for (let ly = 0; ly < chunk.height; ly++) for (let lx = 0; lx < chunk.width; lx++) if (chunk.getRawAt(lx, ly) !== 0) count++;
|
|
510
|
+
return count;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
472
513
|
|
|
514
|
+
//#endregion
|
|
473
515
|
export { TileLayer };
|
|
474
|
-
//# sourceMappingURL=TileLayer.js.map
|
|
516
|
+
//# sourceMappingURL=TileLayer.js.map
|