@codexo/exojs-ldtk 0.15.3 → 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 (67) hide show
  1. package/README.md +38 -20
  2. package/dist/esm/LdtkData.d.ts +59 -14
  3. package/dist/esm/LdtkData.d.ts.map +1 -0
  4. package/dist/esm/LdtkData.js +8 -16
  5. package/dist/esm/LdtkData.js.map +1 -1
  6. package/dist/esm/LdtkMap.d.ts +25 -6
  7. package/dist/esm/LdtkMap.d.ts.map +1 -0
  8. package/dist/esm/LdtkMap.js +88 -63
  9. package/dist/esm/LdtkMap.js.map +1 -1
  10. package/dist/esm/LdtkProject.d.ts +107 -0
  11. package/dist/esm/LdtkProject.d.ts.map +1 -0
  12. package/dist/esm/LdtkProject.js +136 -0
  13. package/dist/esm/LdtkProject.js.map +1 -0
  14. package/dist/esm/index.d.ts +1 -0
  15. package/dist/esm/index.d.ts.map +1 -0
  16. package/dist/esm/index.js +11 -7
  17. package/dist/esm/ldtkExtension.d.ts +9 -5
  18. package/dist/esm/ldtkExtension.d.ts.map +1 -0
  19. package/dist/esm/ldtkExtension.js +25 -22
  20. package/dist/esm/ldtkExtension.js.map +1 -1
  21. package/dist/esm/ldtkLevelEntries.d.ts +4 -3
  22. package/dist/esm/ldtkLevelEntries.d.ts.map +1 -0
  23. package/dist/esm/ldtkLevelEntries.js +30 -26
  24. package/dist/esm/ldtkLevelEntries.js.map +1 -1
  25. package/dist/esm/ldtkParallax.d.ts +22 -0
  26. package/dist/esm/ldtkParallax.d.ts.map +1 -0
  27. package/dist/esm/ldtkParallax.js +22 -0
  28. package/dist/esm/ldtkParallax.js.map +1 -0
  29. package/dist/esm/ldtkToMapWorld.d.ts +20 -0
  30. package/dist/esm/ldtkToMapWorld.d.ts.map +1 -0
  31. package/dist/esm/ldtkToMapWorld.js +76 -0
  32. package/dist/esm/ldtkToMapWorld.js.map +1 -0
  33. package/dist/esm/ldtkToTileMap.d.ts +48 -10
  34. package/dist/esm/ldtkToTileMap.d.ts.map +1 -0
  35. package/dist/esm/ldtkToTileMap.js +410 -387
  36. package/dist/esm/ldtkToTileMap.js.map +1 -1
  37. package/dist/esm/ldtkTypes.d.ts +47 -0
  38. package/dist/esm/ldtkTypes.d.ts.map +1 -0
  39. package/dist/esm/ldtkTypes.js +57 -0
  40. package/dist/esm/ldtkTypes.js.map +1 -0
  41. package/dist/esm/loadLdtkMap.d.ts +30 -5
  42. package/dist/esm/loadLdtkMap.d.ts.map +1 -0
  43. package/dist/esm/loadLdtkMap.js +139 -133
  44. package/dist/esm/loadLdtkMap.js.map +1 -1
  45. package/dist/esm/loadLdtkProject.d.ts +17 -0
  46. package/dist/esm/loadLdtkProject.d.ts.map +1 -0
  47. package/dist/esm/loadLdtkProject.js +31 -0
  48. package/dist/esm/loadLdtkProject.js.map +1 -0
  49. package/dist/esm/public.d.ts +24 -9
  50. package/dist/esm/public.d.ts.map +1 -0
  51. package/dist/esm/public.js +11 -0
  52. package/dist/esm/url.d.ts +16 -0
  53. package/dist/esm/url.d.ts.map +1 -0
  54. package/dist/esm/url.js +31 -0
  55. package/dist/esm/url.js.map +1 -0
  56. package/dist/esm/validate.d.ts +27 -0
  57. package/dist/esm/validate.d.ts.map +1 -0
  58. package/dist/esm/validate.js +327 -0
  59. package/dist/esm/validate.js.map +1 -0
  60. package/package.json +10 -18
  61. package/dist/esm/index.js.map +0 -1
  62. package/dist/esm/ldtkBinding.d.ts +0 -22
  63. package/dist/esm/ldtkBinding.js +0 -31
  64. package/dist/esm/ldtkBinding.js.map +0 -1
  65. package/dist/esm/register.d.ts +0 -1
  66. package/dist/esm/register.js +0 -17
  67. package/dist/esm/register.js.map +0 -1
@@ -1,400 +1,423 @@
1
- import { ObjectLayer, TileMap, TileLayer, TilePropertyKind } from '@codexo/exojs-tilemap';
2
- export { TILE_TRANSFORM_IDENTITY } from '@codexo/exojs-tilemap';
3
- import { ldtkFlipY, ldtkFlipX } from './LdtkData.js';
4
- import { getLdtkLevelEntries } from './ldtkLevelEntries.js';
5
- import { LdtkMap } from './LdtkMap.js';
1
+ import { getLdtkLevelEntries } from "./ldtkLevelEntries.js";
2
+ import { LDTK_FLIP_X, LDTK_FLIP_Y } from "./LdtkData.js";
3
+ import { resolveLdtkLayerParallax } from "./ldtkParallax.js";
4
+ import { LdtkMap } from "./LdtkMap.js";
5
+ import { ObjectLayer, TileLayer, TileMap, TilePropertyKind } from "@codexo/exojs-tilemap";
6
6
 
7
+ //#region src/ldtkToTileMap.ts
7
8
  /**
8
- * Convert a raw {@link LdtkData} document into an {@link LdtkMap} containing
9
- * one runtime {@link TileMap} per LDtk level.
10
- *
11
- * Tile layers (`Tiles` / `AutoLayer`) become renderable `TileLayer`s. IntGrid
12
- * layers become dimension-correct `TileLayer`s tile data is placed only when
13
- * the layer carries `autoLayerTiles`. Entity layers become data-only
14
- * `ObjectLayer`s with entity position, size, and scalar field properties.
15
- *
16
- * Pass `options.tilesets` to populate tile data; omit it for structure-only
17
- * conversion (useful in unit tests that do not need textures).
18
- *
19
- * Transparently handles both LDtk root shapes via {@link getLdtkLevelEntries}:
20
- * single-world (`data.levels`) and multi-world (`data.worlds[].levels`) in
21
- * the latter case every converted level's `TileMap.properties` is additionally
22
- * tagged with its owning world's iid under the reserved `ldtkWorldIid` key.
23
- */
24
- function ldtkToTileMap(data, options) {
25
- const source = options?.source ?? '';
26
- const tilesets = options?.tilesets ?? new Map();
27
- const levels = getLdtkLevelEntries(data).map((entry, levelIndex) => convertLevel(entry.level, entry.worldIid, levelIndex, data, tilesets));
28
- return new LdtkMap(source, data, levels);
29
- }
30
- // ── Level conversion ──────────────────────────────────────────────────────────
31
- // eslint-disable-next-line complexity
32
- function convertLevel(level, worldIid, levelIndex, data, tilesets) {
33
- // Derive map-level tile size from the first non-entity layer, or fall back.
34
- const gridSize = pickLevelGridSize(level, data.defaultGridSize ?? 16);
35
- const mapWidth = Math.max(1, Math.ceil(level.pxWid / gridSize));
36
- const mapHeight = Math.max(1, Math.ceil(level.pxHei / gridSize));
37
- const runtimeTilesets = [...tilesets.values()];
38
- const runtimeLayers = [];
39
- const runtimeObjectLayers = [];
40
- // LDtk stores layers top-to-bottom (first = top-most); preserve that order.
41
- const layerInstances = level.layerInstances ?? [];
42
- let entityCounter = 0;
43
- for (const layerInst of layerInstances) {
44
- const layerGridSize = layerInst.__gridSize;
45
- // Layer IDs must be unique within a TileMap (= one level).
46
- // Use layerDefUid directly — it is unique per layer definition in the file.
47
- const layerId = layerInst.layerDefUid;
48
- switch (layerInst.__type) {
49
- case 'Tiles':
50
- case 'AutoLayer': {
51
- const rLayer = makeTileLayer(layerInst, layerId, runtimeTilesets);
52
- const tiles = layerInst.__type === 'Tiles'
53
- ? (layerInst.gridTiles ?? [])
54
- : (layerInst.autoLayerTiles ?? []);
55
- const tsUid = layerInst.__tilesetDefUid;
56
- if (tsUid !== undefined) {
57
- const rts = tilesets.get(tsUid);
58
- if (rts)
59
- populateTileLayer(rLayer, tiles, rts, layerGridSize);
60
- }
61
- runtimeLayers.push(rLayer);
62
- break;
63
- }
64
- case 'IntGrid': {
65
- const intGridProperties = buildIntGridProperties(layerInst, data);
66
- const rLayer = makeTileLayer(layerInst, layerId, runtimeTilesets, intGridProperties);
67
- // IntGrid layers may carry auto-tiles when "Auto-layer" rules are
68
- // configured. Use those for rendering; raw intGridCsv is exposed as
69
- // data-only layer properties (see buildIntGridProperties).
70
- const autoTiles = layerInst.autoLayerTiles ?? [];
71
- const tsUid = layerInst.__tilesetDefUid;
72
- if (autoTiles.length > 0 && tsUid !== undefined) {
73
- const rts = tilesets.get(tsUid);
74
- if (rts)
75
- populateTileLayer(rLayer, autoTiles, rts, layerGridSize);
76
- }
77
- runtimeLayers.push(rLayer);
78
- break;
79
- }
80
- case 'Entities': {
81
- const objects = convertEntityLayer(layerInst, layerGridSize, levelIndex, entityCounter);
82
- entityCounter += layerInst.entityInstances?.length ?? 0;
83
- runtimeObjectLayers.push(new ObjectLayer({
84
- id: layerId,
85
- name: layerInst.__identifier,
86
- visible: layerInst.visible,
87
- opacity: layerInst.opacity ?? 1,
88
- offsetX: layerInst.pxOffsetX ?? 0,
89
- offsetY: layerInst.pxOffsetY ?? 0,
90
- objects,
91
- }));
92
- break;
93
- }
94
- }
95
- }
96
- return new TileMap({
97
- name: level.identifier,
98
- width: mapWidth,
99
- height: mapHeight,
100
- tileWidth: gridSize,
101
- tileHeight: gridSize,
102
- tilesets: runtimeTilesets,
103
- layers: runtimeLayers,
104
- objectLayers: runtimeObjectLayers,
105
- // Convert user-defined level fields first, then apply the reserved keys
106
- // last so a same-named user field can never clobber them. ldtkWorldIid is
107
- // only added for multi-world documents (worldIid !== undefined) — a
108
- // single-world document's properties must stay exactly as they were
109
- // before multi-world support existed.
110
- properties: {
111
- ...convertFieldInstances(level.fieldInstances ?? []),
112
- ldtkUid: level.uid,
113
- ldtkIid: level.iid,
114
- worldX: level.worldX,
115
- worldY: level.worldY,
116
- ...(worldIid !== undefined && { ldtkWorldIid: worldIid }),
117
- },
118
- });
119
- }
120
- // ── Helpers: TileLayer ────────────────────────────────────────────────────────
121
- function makeTileLayer(layerInst, layerId, tilesets, properties) {
122
- return new TileLayer({
123
- id: layerId,
124
- name: layerInst.__identifier,
125
- width: layerInst.__cWid,
126
- height: layerInst.__cHei,
127
- tilesets,
128
- tileWidth: layerInst.__gridSize,
129
- tileHeight: layerInst.__gridSize,
130
- visible: layerInst.visible,
131
- opacity: layerInst.opacity ?? 1,
132
- offsetX: layerInst.pxOffsetX ?? 0,
133
- offsetY: layerInst.pxOffsetY ?? 0,
134
- ...(properties && { properties }),
135
- });
136
- }
137
- function populateTileLayer(layer, tiles, tileset, gridSize) {
138
- for (const tile of tiles) {
139
- const tx = Math.floor(tile.px[0] / gridSize);
140
- const ty = Math.floor(tile.px[1] / gridSize);
141
- if (!layer.inBounds(tx, ty))
142
- continue;
143
- const localTileId = tile.t;
144
- if (localTileId < 0 || localTileId >= tileset.tileCount)
145
- continue;
146
- const f = tile.f;
147
- layer.setTileAt(tx, ty, {
148
- tileset,
149
- localTileId,
150
- transform: {
151
- flipX: (f & ldtkFlipX) !== 0,
152
- flipY: (f & ldtkFlipY) !== 0,
153
- diagonal: false,
154
- },
155
- });
156
- }
157
- }
158
- // ── Helpers: IntGrid ──────────────────────────────────────────────────────────
9
+ * Convert a raw {@link LdtkData} document into an {@link LdtkMap} containing
10
+ * one runtime {@link TileMap} per LDtk level.
11
+ *
12
+ * Tile layers (`Tiles` / `AutoLayer`) become renderable `TileLayer`s. IntGrid
13
+ * layers become dimension-correct `TileLayer`s - tile data is placed only when
14
+ * the layer carries `autoLayerTiles`. Entity layers become data-only
15
+ * `ObjectLayer`s with entity position, size, and scalar field properties.
16
+ *
17
+ * Pass `options.tilesets` to populate tile data; omit it for structure-only
18
+ * conversion (useful in unit tests that do not need textures).
19
+ *
20
+ * Transparently handles both LDtk root shapes via {@link getLdtkLevelEntries}:
21
+ * single-world (`data.levels`) and multi-world (`data.worlds[].levels`) - in
22
+ * the latter case every converted level's `TileMap.properties` is additionally
23
+ * tagged with its owning world's iid under the reserved `ldtkWorldIid` key.
24
+ */
25
+ const ldtkToTileMap = (data, options) => {
26
+ const source = options?.source ?? "";
27
+ const tilesets = options?.tilesets ?? /* @__PURE__ */ new Map();
28
+ const levels = getLdtkLevelEntries(data).map((entry, levelIndex) => convertLevel(entry.level, entry.worldIid, levelIndex, data, tilesets));
29
+ return new LdtkMap(source, data, levels);
30
+ };
159
31
  /**
160
- * Reserved {@link TileLayer.properties} key holding the JSON-encoded raw
161
- * IntGrid CSV array (`readonly number[]`) for a `TileLayer` converted from an
162
- * LDtk `IntGrid` layer instance. Index = `y * layer.width + x`; `0` = empty.
163
- * Prefer {@link getLdtkIntGridValueAt} over reading this directly.
164
- */
165
- const ldtkIntGridCsvProperty = 'ldtkIntGridCsv';
32
+ * Convert one already-resolved LDtk level into its runtime {@link TileMap}.
33
+ *
34
+ * `level.layerInstances` must be present: an externalized level has to have its
35
+ * `.ldtkl` payload merged in first, or the result is an empty map.
36
+ * @internal
37
+ */
38
+ const ldtkLevelToTileMap = (level, worldIid, levelIndex, data, tilesets) => convertLevel(level, worldIid, levelIndex, data, tilesets);
39
+ const convertLevel = (level, worldIid, levelIndex, data, tilesets) => {
40
+ const gridSize = pickLevelGridSize(level, data.defaultGridSize ?? 16);
41
+ const mapWidth = Math.max(1, Math.ceil(level.pxWid / gridSize));
42
+ const mapHeight = Math.max(1, Math.ceil(level.pxHei / gridSize));
43
+ const runtimeTilesets = [...tilesets.values()];
44
+ const runtimeLayers = [];
45
+ const runtimeObjectLayers = [];
46
+ const layerInstances = level.layerInstances ?? [];
47
+ let entityCounter = 0;
48
+ for (const layerInst of layerInstances) {
49
+ const layerId = layerInst.layerDefUid;
50
+ const parallax = resolveLdtkLayerParallax(data, layerInst);
51
+ switch (layerInst.__type) {
52
+ case "Tiles":
53
+ case "AutoLayer":
54
+ runtimeLayers.push(convertTilesOrAutoLayer(layerInst, layerId, runtimeTilesets, tilesets, parallax));
55
+ break;
56
+ case "IntGrid":
57
+ runtimeLayers.push(convertIntGridLayer(layerInst, layerId, runtimeTilesets, tilesets, data, parallax));
58
+ break;
59
+ case "Entities":
60
+ runtimeObjectLayers.push(convertEntitiesLayer(layerInst, layerId, levelIndex, entityCounter, parallax));
61
+ entityCounter += layerInst.entityInstances?.length ?? 0;
62
+ }
63
+ }
64
+ return new TileMap({
65
+ name: level.identifier,
66
+ width: mapWidth,
67
+ height: mapHeight,
68
+ tileWidth: gridSize,
69
+ tileHeight: gridSize,
70
+ tilesets: runtimeTilesets,
71
+ layers: runtimeLayers,
72
+ objectLayers: runtimeObjectLayers,
73
+ properties: buildLdtkLevelProperties(level, worldIid)
74
+ });
75
+ };
166
76
  /**
167
- * Reserved {@link TileLayer.properties} key holding the JSON-encoded
168
- * {@link LdtkIntGridValueDef} array for a `TileLayer` converted from an LDtk
169
- * `IntGrid` layer instance the raw-int → named/coloured mapping declared on
170
- * the owning layer definition (`data.defs.layers[].intGridValues`).
171
- * Prefer {@link getLdtkIntGridValueAt} over reading this directly.
172
- */
173
- const ldtkIntGridValuesProperty = 'ldtkIntGridValues';
77
+ * The property bag a level contributes to its runtime representation - user
78
+ * fields first, then the reserved LDtk keys, so a same-named user field can
79
+ * never clobber them. `ldtkWorldIid` is added only for multi-world documents;
80
+ * a single-world document's properties stay exactly as they were before
81
+ * multi-world support existed.
82
+ * @internal
83
+ */
84
+ const buildLdtkLevelProperties = (level, worldIid) => ({
85
+ ...convertFieldInstances(level.fieldInstances ?? []),
86
+ ldtkUid: level.uid,
87
+ ldtkIid: level.iid,
88
+ worldX: level.worldX,
89
+ worldY: level.worldY,
90
+ ...worldIid !== void 0 && { ldtkWorldIid: worldIid }
91
+ });
92
+ /** Convert a `Tiles`/`AutoLayer` LDtk layer instance into a runtime {@link TileLayer}. */
93
+ const convertTilesOrAutoLayer = (layerInst, layerId, runtimeTilesets, tilesets, parallax) => {
94
+ const rLayer = makeTileLayer(layerInst, layerId, runtimeTilesets, parallax);
95
+ const tiles = layerInst.__type === "Tiles" ? layerInst.gridTiles ?? [] : layerInst.autoLayerTiles ?? [];
96
+ const tsUid = layerInst.__tilesetDefUid;
97
+ if (tsUid !== void 0 && tsUid !== null) {
98
+ const rts = tilesets.get(tsUid);
99
+ if (rts) populateTileLayer(rLayer, tiles, rts, layerInst.__gridSize);
100
+ }
101
+ return rLayer;
102
+ };
103
+ /** Convert an `IntGrid` LDtk layer instance into a runtime {@link TileLayer}. */
104
+ const convertIntGridLayer = (layerInst, layerId, runtimeTilesets, tilesets, data, parallax) => {
105
+ const intGridProperties = buildIntGridProperties(layerInst, data);
106
+ const rLayer = makeTileLayer(layerInst, layerId, runtimeTilesets, parallax, intGridProperties);
107
+ const autoTiles = layerInst.autoLayerTiles ?? [];
108
+ const tsUid = layerInst.__tilesetDefUid;
109
+ if (autoTiles.length > 0 && tsUid !== void 0 && tsUid !== null) {
110
+ const rts = tilesets.get(tsUid);
111
+ if (rts) populateTileLayer(rLayer, autoTiles, rts, layerInst.__gridSize);
112
+ }
113
+ return rLayer;
114
+ };
115
+ /** Convert an `Entities` LDtk layer instance into a runtime {@link ObjectLayer}. */
116
+ const convertEntitiesLayer = (layerInst, layerId, levelIndex, entityCounter, parallax) => {
117
+ const objects = convertEntityLayer(layerInst, layerInst.__gridSize, levelIndex, entityCounter);
118
+ return new ObjectLayer({
119
+ id: layerId,
120
+ name: layerInst.__identifier,
121
+ visible: layerInst.visible,
122
+ opacity: layerInst.opacity ?? 1,
123
+ offsetX: parallax.offsetX + (layerInst.pxOffsetX ?? 0),
124
+ offsetY: parallax.offsetY + (layerInst.pxOffsetY ?? 0),
125
+ parallaxX: parallax.parallaxX,
126
+ parallaxY: parallax.parallaxY,
127
+ parallaxScale: parallax.parallaxScale,
128
+ objects
129
+ });
130
+ };
131
+ const makeTileLayer = (layerInst, layerId, tilesets, parallax, properties) => new TileLayer({
132
+ id: layerId,
133
+ name: layerInst.__identifier,
134
+ width: layerInst.__cWid,
135
+ height: layerInst.__cHei,
136
+ tilesets,
137
+ tileWidth: layerInst.__gridSize,
138
+ tileHeight: layerInst.__gridSize,
139
+ visible: layerInst.visible,
140
+ opacity: layerInst.opacity ?? 1,
141
+ offsetX: parallax.offsetX + (layerInst.pxOffsetX ?? 0),
142
+ offsetY: parallax.offsetY + (layerInst.pxOffsetY ?? 0),
143
+ parallaxX: parallax.parallaxX,
144
+ parallaxY: parallax.parallaxY,
145
+ parallaxScale: parallax.parallaxScale,
146
+ ...properties && { properties }
147
+ });
148
+ const populateTileLayer = (layer, tiles, tileset, gridSize) => {
149
+ for (const tile of tiles) {
150
+ const tx = Math.floor(tile.px[0] / gridSize);
151
+ const ty = Math.floor(tile.px[1] / gridSize);
152
+ if (!layer.inBounds(tx, ty)) continue;
153
+ const localTileId = tile.t;
154
+ if (localTileId < 0 || localTileId >= tileset.tileCount) continue;
155
+ const f = tile.f;
156
+ layer.setTileAt(tx, ty, {
157
+ tileset,
158
+ localTileId,
159
+ transform: {
160
+ flipX: (f & 1) !== 0,
161
+ flipY: (f & 2) !== 0,
162
+ diagonal: false
163
+ }
164
+ });
165
+ }
166
+ };
174
167
  /**
175
- * Build the reserved IntGrid properties for a `TileLayer` from an LDtk
176
- * `IntGrid` layer instance, or `undefined` when the layer carries no
177
- * `intGridCsv` data (nothing to expose).
178
- *
179
- * {@link TileLayer.properties} values are scalar-only, so the raw CSV array
180
- * and the value-definition mapping are JSON-encoded into two reserved string
181
- * properties rather than stored as nested structures — the same
182
- * `properties`-bag mechanism the Tiled adapter already uses for per-layer
183
- * metadata, just serialized to fit its scalar-only value type.
184
- */
185
- function buildIntGridProperties(layerInst, data) {
186
- const csv = layerInst.intGridCsv;
187
- if (!csv || csv.length === 0)
188
- return undefined;
189
- const layerDef = data.defs.layers.find(def => def.uid === layerInst.layerDefUid);
190
- const values = layerDef?.intGridValues ?? [];
191
- return Object.freeze({
192
- [ldtkIntGridCsvProperty]: JSON.stringify(csv),
193
- [ldtkIntGridValuesProperty]: JSON.stringify(values),
194
- });
195
- }
168
+ * Reserved {@link TileLayer.properties} key holding the JSON-encoded raw
169
+ * IntGrid CSV array (`readonly number[]`) for a `TileLayer` converted from an
170
+ * LDtk `IntGrid` layer instance. Index = `y * layer.width + x`; `0` = empty.
171
+ * Prefer {@link getLdtkIntGridValueAt} over reading this directly.
172
+ */
173
+ const ldtkIntGridCsvProperty = "ldtkIntGridCsv";
196
174
  /**
197
- * Per-`TileLayer` cache of parsed IntGrid CSV/value-defs data, populated
198
- * lazily on first {@link getLdtkIntGridValueAt} lookup for a given layer.
199
- *
200
- * Keyed by the `TileLayer` instance itself (`WeakMap`), so an entry is
201
- * naturally garbage-collected once the layer it was derived from is no
202
- * longer referenced — no manual invalidation needed since
203
- * {@link TileLayer.properties} is frozen and copied at construction time and
204
- * can never change afterwards.
205
- */
206
- const intGridCache = new WeakMap();
175
+ * Reserved {@link TileLayer.properties} key holding the JSON-encoded
176
+ * {@link LdtkIntGridValueDef} array for a `TileLayer` converted from an LDtk
177
+ * `IntGrid` layer instance - the raw-int → named/coloured mapping declared on
178
+ * the owning layer definition (`data.defs.layers[].intGridValues`).
179
+ * Prefer {@link getLdtkIntGridValueAt} over reading this directly.
180
+ */
181
+ const ldtkIntGridValuesProperty = "ldtkIntGridValues";
207
182
  /**
208
- * Parse (or retrieve from {@link intGridCache}) the IntGrid CSV/value-defs
209
- * data attached to `layer`, or `undefined` when `layer` carries no such data
210
- * (not converted from an IntGrid layer instance).
211
- */
212
- function getParsedIntGridData(layer) {
213
- const cached = intGridCache.get(layer);
214
- if (cached)
215
- return cached;
216
- const csvRaw = layer.properties[ldtkIntGridCsvProperty];
217
- const valuesRaw = layer.properties[ldtkIntGridValuesProperty];
218
- if (typeof csvRaw !== 'string' || typeof valuesRaw !== 'string')
219
- return undefined;
220
- const parsed = {
221
- csv: JSON.parse(csvRaw),
222
- values: JSON.parse(valuesRaw),
223
- };
224
- intGridCache.set(layer, parsed);
225
- return parsed;
226
- }
183
+ * Build the reserved IntGrid properties for a `TileLayer` from an LDtk
184
+ * `IntGrid` layer instance, or `undefined` when the layer carries no
185
+ * `intGridCsv` data (nothing to expose).
186
+ *
187
+ * {@link TileLayer.properties} values are scalar-only, so the raw CSV array
188
+ * and the value-definition mapping are JSON-encoded into two reserved string
189
+ * properties rather than stored as nested structures - the same
190
+ * `properties`-bag mechanism the Tiled adapter already uses for per-layer
191
+ * metadata, just serialized to fit its scalar-only value type.
192
+ */
193
+ const buildIntGridProperties = (layerInst, data) => {
194
+ const csv = layerInst.intGridCsv;
195
+ if (!csv || csv.length === 0) return void 0;
196
+ const values = data.defs.layers.find((def) => def.uid === layerInst.layerDefUid)?.intGridValues ?? [];
197
+ return Object.freeze({
198
+ [ldtkIntGridCsvProperty]: JSON.stringify(csv),
199
+ [ldtkIntGridValuesProperty]: JSON.stringify(values)
200
+ });
201
+ };
227
202
  /**
228
- * Look up the named/coloured {@link LdtkIntGridValueDef} at a tile coordinate
229
- * on a `TileLayer` converted from an LDtk `IntGrid` layer instance.
230
- *
231
- * Returns `undefined` when the coordinate is out of bounds, the cell's raw
232
- * value is `0` (empty), the raw value has no matching definition, or `layer`
233
- * was not converted from an IntGrid layer instance (no IntGrid data attached
234
- * via {@link ldtkIntGridCsvProperty} / {@link ldtkIntGridValuesProperty}).
235
- *
236
- * The underlying JSON-encoded CSV/value-defs properties are parsed once per
237
- * layer and cached (see {@link getParsedIntGridData}) — safe to call from a
238
- * hot path (e.g. per-cell or per-frame collision/classification checks).
239
- */
240
- function getLdtkIntGridValueAt(layer, x, y) {
241
- if (!layer.inBounds(x, y))
242
- return undefined;
243
- const parsed = getParsedIntGridData(layer);
244
- if (!parsed)
245
- return undefined;
246
- const raw = parsed.csv[y * layer.width + x];
247
- if (raw === undefined || raw === 0)
248
- return undefined;
249
- return parsed.values.find(v => v.value === raw);
250
- }
251
- // ── Helpers: ObjectLayer ──────────────────────────────────────────────────────
252
- function convertEntityLayer(layerInst, _gridSize, levelIndex, baseCounter) {
253
- const instances = layerInst.entityInstances ?? [];
254
- const objects = [];
255
- for (let i = 0; i < instances.length; i++) {
256
- const entity = instances[i];
257
- if (entity === undefined)
258
- continue;
259
- // Build a deterministic numeric id: (levelIndex * 1_000_000) + counter.
260
- const id = levelIndex * 1_000_000 + baseCounter + i;
261
- objects.push(convertEntity(entity, id));
262
- }
263
- return objects;
264
- }
265
- function convertEntity(entity, id) {
266
- // entity.px is the pivot-adjusted anchor, not the bounding box's top-left
267
- // corner — undo the pivot offset to recover the corner TileMapObject expects.
268
- const x = entity.px[0] - entity.width * entity.__pivot[0];
269
- const y = entity.px[1] - entity.height * entity.__pivot[1];
270
- return {
271
- kind: 'rectangle',
272
- id,
273
- name: entity.__identifier,
274
- type: entity.__identifier,
275
- x,
276
- y,
277
- width: entity.width,
278
- height: entity.height,
279
- rotation: 0,
280
- visible: true,
281
- properties: convertFieldInstances(entity.fieldInstances),
282
- };
283
- }
203
+ * Per-`TileLayer` cache of parsed IntGrid CSV/value-defs data, populated
204
+ * lazily on first {@link getLdtkIntGridValueAt} lookup for a given layer.
205
+ *
206
+ * Keyed by the `TileLayer` instance itself (`WeakMap`), so an entry is
207
+ * naturally garbage-collected once the layer it was derived from is no
208
+ * longer referenced - no manual invalidation needed since
209
+ * {@link TileLayer.properties} is frozen and copied at construction time and
210
+ * can never change afterwards.
211
+ */
212
+ const intGridCache = /* @__PURE__ */ new WeakMap();
284
213
  /**
285
- * Project LDtk field instances to a flat {@link TileProperties} bag.
286
- * Every LDtk field type maps to a canonical {@link TilePropertyValue}
287
- * (scalars pass through; `Point`/`EntityRef`/`Tile` become their tagged
288
- * structured variants; `Array<T>` fields recursively convert each element).
289
- * A field whose raw `__value` is `null` (LDtk's "not set" convention) is
290
- * omitted from the bag entirely, matching the property-absent case rather
291
- * than a present-but-null value.
292
- */
293
- function convertFieldInstances(fields) {
294
- if (fields.length === 0)
295
- return Object.freeze({});
296
- const out = {};
297
- for (const field of fields) {
298
- const value = convertField(field);
299
- if (value !== undefined) {
300
- out[field.__identifier] = value;
301
- }
302
- }
303
- return Object.freeze(out);
304
- }
214
+ * Parse (or retrieve from {@link intGridCache}) the IntGrid CSV/value-defs
215
+ * data attached to `layer`, or `undefined` when `layer` carries no such data
216
+ * (not converted from an IntGrid layer instance).
217
+ */
218
+ const getParsedIntGridData = (layer) => {
219
+ const cached = intGridCache.get(layer);
220
+ if (cached) return cached;
221
+ const csvRaw = layer.properties[ldtkIntGridCsvProperty];
222
+ const valuesRaw = layer.properties[ldtkIntGridValuesProperty];
223
+ if (typeof csvRaw !== "string" || typeof valuesRaw !== "string") return void 0;
224
+ const parsed = {
225
+ csv: JSON.parse(csvRaw),
226
+ values: JSON.parse(valuesRaw)
227
+ };
228
+ intGridCache.set(layer, parsed);
229
+ return parsed;
230
+ };
305
231
  /**
306
- * Type guard narrowing to the `Array<T>` member of {@link LdtkFieldInstance}.
307
- * `String.prototype.startsWith` alone does not narrow a template-literal
308
- * union member for the compiler; a predicate on `field` itself does.
309
- */
310
- function isLdtkArrayField(field) {
311
- return field.__type.startsWith('Array<');
312
- }
232
+ * Look up the named/coloured {@link LdtkIntGridValueDef} at a tile coordinate
233
+ * on a `TileLayer` converted from an LDtk `IntGrid` layer instance.
234
+ *
235
+ * Returns `undefined` when the coordinate is out of bounds, the cell's raw
236
+ * value is `0` (empty), the raw value has no matching definition, or `layer`
237
+ * was not converted from an IntGrid layer instance (no IntGrid data attached
238
+ * via {@link ldtkIntGridCsvProperty} / {@link ldtkIntGridValuesProperty}).
239
+ *
240
+ * The underlying JSON-encoded CSV/value-defs properties are parsed once per
241
+ * layer and cached (see {@link getParsedIntGridData}) - safe to call from a
242
+ * hot path (e.g. per-cell or per-frame collision/classification checks).
243
+ */
244
+ const getLdtkIntGridValueAt = (layer, x, y) => {
245
+ if (!layer.inBounds(x, y) || layer.width === void 0) return void 0;
246
+ const parsed = getParsedIntGridData(layer);
247
+ if (!parsed) return void 0;
248
+ const raw = parsed.csv[y * layer.width + x];
249
+ if (raw === void 0 || raw === 0) return void 0;
250
+ return parsed.values.find((v) => v.value === raw);
251
+ };
252
+ /**
253
+ * Per-cell collision classification for a `TileLayer` converted from an LDtk
254
+ * `IntGrid` layer instance, or `undefined` when the layer carries no IntGrid
255
+ * data (not converted from an IntGrid layer instance, or unbounded).
256
+ *
257
+ * The classification is the value's LDtk-authored identifier, falling back to
258
+ * the raw integer as a string when the value definition declares none. Cells
259
+ * whose raw value is `0` (empty) classify as `null`. Distinct IntGrid values
260
+ * stay distinct strings: what they mean - solid, water, hazard - is the
261
+ * caller's to decide, and an unrecognised value is data, not an error.
262
+ *
263
+ * The result reads the layer's frozen IntGrid data, so it is stable for the
264
+ * lifetime of the layer and safe to hand to a consumer that samples it while
265
+ * building geometry.
266
+ *
267
+ * ```ts
268
+ * const geometry = buildTileCollisionGeometry(layer, { cells: createLdtkIntGridCellSource(layer) });
269
+ * ```
270
+ */
271
+ const createLdtkIntGridCellSource = (layer) => {
272
+ const parsed = getParsedIntGridData(layer);
273
+ const width = layer.width;
274
+ if (!parsed || width === void 0) return void 0;
275
+ const identifiers = /* @__PURE__ */ new Map();
276
+ for (const value of parsed.values) identifiers.set(value.value, value.identifier ?? String(value.value));
277
+ return (tx, ty) => {
278
+ if (!layer.inBounds(tx, ty)) return null;
279
+ const raw = parsed.csv[ty * width + tx];
280
+ if (raw === void 0 || raw === 0) return null;
281
+ return identifiers.get(raw) ?? String(raw);
282
+ };
283
+ };
284
+ const convertEntityLayer = (layerInst, _gridSize, levelIndex, baseCounter) => {
285
+ const instances = layerInst.entityInstances ?? [];
286
+ const objects = [];
287
+ for (let i = 0; i < instances.length; i++) {
288
+ const entity = instances[i];
289
+ if (entity === void 0) continue;
290
+ const id = levelIndex * 1e6 + baseCounter + i;
291
+ objects.push(convertEntity(entity, id));
292
+ }
293
+ return objects;
294
+ };
295
+ const convertEntity = (entity, id) => {
296
+ const x = entity.px[0] - entity.width * entity.__pivot[0];
297
+ const y = entity.px[1] - entity.height * entity.__pivot[1];
298
+ return {
299
+ kind: "rectangle",
300
+ id,
301
+ sourceId: entity.iid,
302
+ name: entity.__identifier,
303
+ type: entity.__identifier,
304
+ x,
305
+ y,
306
+ width: entity.width,
307
+ height: entity.height,
308
+ rotation: 0,
309
+ visible: true,
310
+ properties: convertFieldInstances(entity.fieldInstances),
311
+ source: entity
312
+ };
313
+ };
314
+ /**
315
+ * Project LDtk field instances to a flat {@link TileProperties} bag.
316
+ * Every LDtk field type maps to a canonical {@link TilePropertyValue}
317
+ * (scalars pass through; `Point`/`EntityRef`/`Tile` become their tagged
318
+ * structured variants; `Array<T>` fields recursively convert each element).
319
+ * A field whose raw `__value` is `null` (LDtk's "not set" convention) is
320
+ * omitted from the bag entirely, matching the property-absent case rather
321
+ * than a present-but-null value.
322
+ */
323
+ const convertFieldInstances = (fields) => {
324
+ if (fields.length === 0) return Object.freeze({});
325
+ const out = {};
326
+ for (const field of fields) {
327
+ const value = convertField(field);
328
+ if (value !== void 0) out[field.__identifier] = value;
329
+ }
330
+ return Object.freeze(out);
331
+ };
332
+ /**
333
+ * Type guard narrowing to the `Array<T>` member of {@link LdtkFieldInstance}.
334
+ * `String.prototype.startsWith` alone does not narrow a template-literal
335
+ * union member for the compiler; a predicate on `field` itself does.
336
+ */
337
+ const isLdtkArrayField = (field) => field.__type.startsWith("Array<");
313
338
  /** Convert one {@link LdtkFieldInstance} to its canonical {@link TilePropertyValue}, or `undefined` for a `null` (unset) field. */
314
- function convertField(field) {
315
- switch (field.__type) {
316
- case 'Int':
317
- case 'Float':
318
- case 'Bool':
319
- case 'String':
320
- case 'Multilines':
321
- case 'Color':
322
- case 'FilePath':
323
- case 'Enum':
324
- case 'Point':
325
- case 'EntityRef':
326
- case 'Tile':
327
- return mapLdtkFieldValue(field.__type, field.__value);
328
- }
329
- if (isLdtkArrayField(field)) {
330
- if (field.__value === null)
331
- return undefined;
332
- const elementType = field.__type.slice('Array<'.length, -1);
333
- const elements = [];
334
- for (const raw of field.__value) {
335
- const converted = mapLdtkFieldValue(elementType, raw);
336
- if (converted !== undefined)
337
- elements.push(converted);
338
- }
339
- return Object.freeze(elements);
340
- }
341
- throw new Error(`convertFieldInstances: unrecognised LDtk field type "${field.__type}".`);
342
- }
339
+ const convertField = (field) => {
340
+ switch (field.__type) {
341
+ case "Int":
342
+ case "Float":
343
+ case "Bool":
344
+ case "String":
345
+ case "Multilines":
346
+ case "Color":
347
+ case "FilePath":
348
+ case "Enum":
349
+ case "Point":
350
+ case "EntityRef":
351
+ case "Tile": return mapLdtkFieldValue(field.__type, field.__value);
352
+ }
353
+ if (isLdtkArrayField(field)) {
354
+ if (field.__value === null) return void 0;
355
+ const elementType = field.__type.slice(6, -1);
356
+ const elements = [];
357
+ for (const raw of field.__value) {
358
+ const converted = mapLdtkFieldValue(elementType, raw);
359
+ if (converted !== void 0) elements.push(converted);
360
+ }
361
+ return Object.freeze(elements);
362
+ }
363
+ throw new Error(`convertFieldInstances: unrecognised LDtk field type "${field.__type}".`);
364
+ };
343
365
  /**
344
- * Map a single raw LDtk field value (or array element) to its canonical
345
- * {@link TilePropertyValue}, given the LDtk type name it was declared with.
346
- * Shared by {@link convertField} (top-level fields) and array-element
347
- * conversion (`typeName` is the `T` extracted from an `Array<T>` field).
348
- * Returns `undefined` for a `null` value or an unrecognised `typeName`.
349
- */
350
- function mapLdtkFieldValue(typeName, value) {
351
- if (value === null || value === undefined)
352
- return undefined;
353
- switch (typeName) {
354
- case 'Int':
355
- case 'Float':
356
- case 'Bool':
357
- case 'String':
358
- case 'Multilines':
359
- case 'Color':
360
- case 'FilePath':
361
- case 'Enum':
362
- return value;
363
- case 'Point': {
364
- const v = value;
365
- return { kind: TilePropertyKind.Point, cx: v.cx, cy: v.cy };
366
- }
367
- case 'EntityRef': {
368
- const v = value;
369
- return {
370
- kind: TilePropertyKind.ObjectRef,
371
- id: v.entityIid,
372
- layerIid: v.layerIid,
373
- levelIid: v.levelIid,
374
- worldIid: v.worldIid,
375
- };
376
- }
377
- case 'Tile': {
378
- const v = value;
379
- return { kind: TilePropertyKind.TileRef, tilesetUid: v.tilesetUid, x: v.x, y: v.y, w: v.w, h: v.h };
380
- }
381
- default:
382
- // Unknown/unsupported array element type (e.g. a future LDtk type
383
- // inside Array<T>) — skip rather than throw, since this path is not
384
- // compiler-exhaustive (element types are plain runtime strings).
385
- return undefined;
386
- }
387
- }
388
- // ── Helpers: grid size ────────────────────────────────────────────────────────
389
- function pickLevelGridSize(level, fallback) {
390
- const instances = level.layerInstances ?? [];
391
- for (const layer of instances) {
392
- if (layer.__type !== 'Entities' && layer.__gridSize > 0) {
393
- return layer.__gridSize;
394
- }
395
- }
396
- return fallback;
397
- }
366
+ * Map a single raw LDtk field value (or array element) to its canonical
367
+ * {@link TilePropertyValue}, given the LDtk type name it was declared with.
368
+ * Shared by {@link convertField} (top-level fields) and array-element
369
+ * conversion (`typeName` is the `T` extracted from an `Array<T>` field).
370
+ * Returns `undefined` for a `null` value or an unrecognised `typeName`.
371
+ */
372
+ const mapLdtkFieldValue = (typeName, value) => {
373
+ if (value === null || value === void 0) return void 0;
374
+ switch (typeName) {
375
+ case "Int":
376
+ case "Float":
377
+ case "Bool":
378
+ case "String":
379
+ case "Multilines":
380
+ case "Color":
381
+ case "FilePath":
382
+ case "Enum": return value;
383
+ case "Point": {
384
+ const v = value;
385
+ return {
386
+ kind: TilePropertyKind.Point,
387
+ cx: v.cx,
388
+ cy: v.cy
389
+ };
390
+ }
391
+ case "EntityRef": {
392
+ const v = value;
393
+ return {
394
+ kind: TilePropertyKind.ObjectRef,
395
+ id: v.entityIid,
396
+ layerIid: v.layerIid,
397
+ levelIid: v.levelIid,
398
+ worldIid: v.worldIid
399
+ };
400
+ }
401
+ case "Tile": {
402
+ const v = value;
403
+ return {
404
+ kind: TilePropertyKind.TileRef,
405
+ tilesetUid: v.tilesetUid,
406
+ x: v.x,
407
+ y: v.y,
408
+ w: v.w,
409
+ h: v.h
410
+ };
411
+ }
412
+ default: return;
413
+ }
414
+ };
415
+ const pickLevelGridSize = (level, fallback) => {
416
+ const instances = level.layerInstances ?? [];
417
+ for (const layer of instances) if (layer.__type !== "Entities" && layer.__gridSize > 0) return layer.__gridSize;
418
+ return fallback;
419
+ };
398
420
 
399
- export { getLdtkIntGridValueAt, ldtkIntGridCsvProperty, ldtkIntGridValuesProperty, ldtkToTileMap };
400
- //# sourceMappingURL=ldtkToTileMap.js.map
421
+ //#endregion
422
+ export { buildLdtkLevelProperties, createLdtkIntGridCellSource, getLdtkIntGridValueAt, ldtkIntGridCsvProperty, ldtkIntGridValuesProperty, ldtkLevelToTileMap, ldtkToTileMap };
423
+ //# sourceMappingURL=ldtkToTileMap.js.map