@codexo/exojs-tiled 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.
- package/README.md +59 -66
- package/dist/esm/TiledLayer.d.ts +2 -1
- package/dist/esm/TiledLayer.d.ts.map +1 -0
- package/dist/esm/TiledLayer.js +106 -108
- package/dist/esm/TiledLayer.js.map +1 -1
- package/dist/esm/TiledMap.d.ts +35 -7
- package/dist/esm/TiledMap.d.ts.map +1 -0
- package/dist/esm/TiledMap.js +714 -589
- package/dist/esm/TiledMap.js.map +1 -1
- package/dist/esm/TiledObject.d.ts +7 -0
- package/dist/esm/TiledObject.d.ts.map +1 -0
- package/dist/esm/TiledObject.js +59 -51
- package/dist/esm/TiledObject.js.map +1 -1
- package/dist/esm/TiledTileset.d.ts +11 -4
- package/dist/esm/TiledTileset.d.ts.map +1 -0
- package/dist/esm/TiledTileset.js +81 -70
- package/dist/esm/TiledTileset.js.map +1 -1
- package/dist/esm/data.d.ts +25 -7
- package/dist/esm/data.d.ts.map +1 -0
- package/dist/esm/decodeLayerData.d.ts +2 -1
- package/dist/esm/decodeLayerData.d.ts.map +1 -0
- package/dist/esm/decodeLayerData.js +53 -92
- package/dist/esm/decodeLayerData.js.map +1 -1
- package/dist/esm/gid.d.ts +2 -1
- package/dist/esm/gid.d.ts.map +1 -0
- package/dist/esm/gid.js +15 -24
- package/dist/esm/gid.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 +14 -12
- package/dist/esm/loadTiledMap.d.ts +11 -5
- package/dist/esm/loadTiledMap.d.ts.map +1 -0
- package/dist/esm/loadTiledMap.js +82 -91
- package/dist/esm/loadTiledMap.js.map +1 -1
- package/dist/esm/objectAlignment.d.ts +28 -0
- package/dist/esm/objectAlignment.d.ts.map +1 -0
- package/dist/esm/objectAlignment.js +79 -0
- package/dist/esm/objectAlignment.js.map +1 -0
- package/dist/esm/public.d.ts +17 -15
- package/dist/esm/public.d.ts.map +1 -0
- package/dist/esm/public.js +14 -0
- package/dist/esm/tileMapType.d.ts +31 -0
- package/dist/esm/tileMapType.d.ts.map +1 -0
- package/dist/esm/tileMapType.js +42 -0
- package/dist/esm/tileMapType.js.map +1 -0
- package/dist/esm/tiledBuildInfo.d.ts +1 -0
- package/dist/esm/tiledBuildInfo.d.ts.map +1 -0
- package/dist/esm/tiledBuildInfo.js +6 -4
- package/dist/esm/tiledBuildInfo.js.map +1 -1
- package/dist/esm/tiledExtension.d.ts +7 -6
- package/dist/esm/tiledExtension.d.ts.map +1 -0
- package/dist/esm/tiledExtension.js +23 -25
- package/dist/esm/tiledExtension.js.map +1 -1
- package/dist/esm/tiledOptions.d.ts +5 -4
- package/dist/esm/tiledOptions.d.ts.map +1 -0
- package/dist/esm/tiledOptions.js +11 -13
- package/dist/esm/tiledOptions.js.map +1 -1
- package/dist/esm/tiledSourceType.d.ts +24 -0
- package/dist/esm/tiledSourceType.d.ts.map +1 -0
- package/dist/esm/tiledSourceType.js +44 -0
- package/dist/esm/tiledSourceType.js.map +1 -0
- package/dist/esm/url.d.ts +2 -1
- package/dist/esm/url.d.ts.map +1 -0
- package/dist/esm/url.js +26 -37
- package/dist/esm/url.js.map +1 -1
- package/dist/esm/validate.d.ts +11 -10
- package/dist/esm/validate.d.ts.map +1 -0
- package/dist/esm/validate.js +485 -534
- package/dist/esm/validate.js.map +1 -1
- package/dist/esm/wangSets.d.ts +5 -4
- package/dist/esm/wangSets.d.ts.map +1 -0
- package/dist/esm/wangSets.js +90 -115
- package/dist/esm/wangSets.js.map +1 -1
- package/package.json +10 -18
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/register.d.ts +0 -1
- package/dist/esm/register.js +0 -22
- package/dist/esm/register.js.map +0 -1
- package/dist/esm/tiledMapBinding.d.ts +0 -19
- package/dist/esm/tiledMapBinding.js +0 -31
- package/dist/esm/tiledMapBinding.js.map +0 -1
- package/dist/esm/tiledRuntimeMapBinding.d.ts +0 -29
- package/dist/esm/tiledRuntimeMapBinding.js +0 -42
- package/dist/esm/tiledRuntimeMapBinding.js.map +0 -1
package/dist/esm/TiledMap.js
CHANGED
|
@@ -1,603 +1,728 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { resolveTiledUrl } from
|
|
6
|
-
import {
|
|
1
|
+
import { getTiledObjectAnchorOffset, resolveTiledObjectAlignment } from "./objectAlignment.js";
|
|
2
|
+
import { TiledFormatError } from "./validate.js";
|
|
3
|
+
import { TILED_FLIPPED_DIAGONALLY_FLAG, TILED_FLIPPED_HORIZONTALLY_FLAG, TILED_FLIPPED_VERTICALLY_FLAG, maskTiledGid } from "./gid.js";
|
|
4
|
+
import { TiledGroupLayer, TiledImageLayer, TiledObjectLayer, TiledTileLayer, createTiledLayer } from "./TiledLayer.js";
|
|
5
|
+
import { resolveTiledUrl } from "./url.js";
|
|
6
|
+
import { ImageLayer, ObjectLayer, TileLayer, TileMap, TilePropertyKind, TileSet, packTile } from "@codexo/exojs-tilemap";
|
|
7
|
+
import { TextureRegion } from "@codexo/exojs";
|
|
7
8
|
|
|
9
|
+
//#region src/TiledMap.ts
|
|
8
10
|
/**
|
|
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
|
-
|
|
11
|
+
* A parsed and validated Tiled map (`.tmj`).
|
|
12
|
+
*
|
|
13
|
+
* `TiledMap` represents the parsed Tiled source format. `TileMap` is the
|
|
14
|
+
* format-independent ExoJS runtime map used for rendering, queries, and
|
|
15
|
+
* mutation.
|
|
16
|
+
*
|
|
17
|
+
* Construction validates that {@link tilesets} cover a non-overlapping,
|
|
18
|
+
* duplicate-free range of global tile ids, and that every GID referenced by
|
|
19
|
+
* {@link layers} (tile layer cells, infinite-map chunks, and tile object
|
|
20
|
+
* `gid`s) falls within one of those ranges. Both checks throw
|
|
21
|
+
* {@link TiledFormatError} on failure.
|
|
22
|
+
*/
|
|
23
|
+
var TiledMap = class {
|
|
24
|
+
/** Resolved URL this map was loaded from. */
|
|
25
|
+
source;
|
|
26
|
+
/** The validated raw map data this instance was built from. */
|
|
27
|
+
data;
|
|
28
|
+
orientation;
|
|
29
|
+
renderOrder;
|
|
30
|
+
class;
|
|
31
|
+
/** Map width in tiles. */
|
|
32
|
+
width;
|
|
33
|
+
/** Map height in tiles. */
|
|
34
|
+
height;
|
|
35
|
+
/** Tile grid cell width in pixels. */
|
|
36
|
+
tileWidth;
|
|
37
|
+
/** Tile grid cell height in pixels. */
|
|
38
|
+
tileHeight;
|
|
39
|
+
infinite;
|
|
40
|
+
backgroundColor;
|
|
41
|
+
layers;
|
|
42
|
+
/** Tilesets used by this map, sorted by {@link TiledTileset.firstGid} ascending. */
|
|
43
|
+
tilesets;
|
|
44
|
+
properties;
|
|
45
|
+
_imageTextures;
|
|
46
|
+
_chunkSources = /* @__PURE__ */ new Map();
|
|
47
|
+
constructor(source, data, tilesets, imageTextures = /* @__PURE__ */ new Map()) {
|
|
48
|
+
this.source = source;
|
|
49
|
+
this.data = data;
|
|
50
|
+
this.orientation = data.orientation;
|
|
51
|
+
this.renderOrder = data.renderorder;
|
|
52
|
+
this.class = data.class ?? "";
|
|
53
|
+
this.width = data.width;
|
|
54
|
+
this.height = data.height;
|
|
55
|
+
this.tileWidth = data.tilewidth;
|
|
56
|
+
this.tileHeight = data.tileheight;
|
|
57
|
+
this.infinite = data.infinite;
|
|
58
|
+
this.backgroundColor = data.backgroundcolor;
|
|
59
|
+
this.properties = data.properties ?? [];
|
|
60
|
+
this._imageTextures = imageTextures;
|
|
61
|
+
this.layers = data.layers.map(createTiledLayer);
|
|
62
|
+
this.tilesets = sortAndValidateTilesetRanges(tilesets, source);
|
|
63
|
+
checkGidCoverage(this, source);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns the tileset that owns `gid`, or `undefined` if `gid` is `0`
|
|
67
|
+
* (the empty-cell sentinel) or is not covered by any tileset.
|
|
68
|
+
*
|
|
69
|
+
* Flip/rotation flag bits are masked off before the range lookup, so the
|
|
70
|
+
* raw GID values found in {@link TiledTileLayer.data}/`chunks` and
|
|
71
|
+
* {@link TiledObject.gid} can be passed directly.
|
|
72
|
+
*/
|
|
73
|
+
findTilesetForGid(gid) {
|
|
74
|
+
const id = maskTiledGid(gid);
|
|
75
|
+
if (id === 0) return;
|
|
76
|
+
for (const tileset of this.tilesets) if (id >= tileset.firstGid && id <= tileset.lastGid) return tileset;
|
|
77
|
+
}
|
|
78
|
+
/** Looks up a custom property by name. */
|
|
79
|
+
getProperty(name) {
|
|
80
|
+
return this.properties.find((property) => property.name === name);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Convert this parsed Tiled source model into a format-independent runtime
|
|
84
|
+
* {@link TileMap} from `@codexo/exojs-tilemap`.
|
|
85
|
+
*
|
|
86
|
+
* Only orthogonal maps with atlas tilesets are supported. A non-orthogonal
|
|
87
|
+
* map, a collection-of-images tileset, or a tileset with no image at all
|
|
88
|
+
* throws {@link TiledFormatError} rather than silently producing wrong
|
|
89
|
+
* (misplaced) or empty geometry. Tile
|
|
90
|
+
* layers become renderable `TileLayer`s, object groups become data-only
|
|
91
|
+
* `ObjectLayer`s, and image layers become data-only `ImageLayer`s; group
|
|
92
|
+
* layer children are flattened in document order.
|
|
93
|
+
*
|
|
94
|
+
* The runtime map has no group node, so each group's own style is baked into
|
|
95
|
+
* its children as Tiled composes it: `visible` ANDs, `opacity` and the
|
|
96
|
+
* parallax factors multiply, the offsets add, and tint colours multiply per
|
|
97
|
+
* colour channel. Object layers take the visibility, opacity and offset the
|
|
98
|
+
* same way - `ObjectLayer` carries no parallax or tint of its own, so a
|
|
99
|
+
* group's contribution to those two is dropped for object layers only.
|
|
100
|
+
*
|
|
101
|
+
* An `infinite: true` (chunked) tile layer converts to an **unbounded**
|
|
102
|
+
* runtime `TileLayer` with no tiles populated eagerly - its data streams in
|
|
103
|
+
* on demand via the {@link ChunkSource} built for it, retrievable with
|
|
104
|
+
* {@link getChunkSource} after this call returns.
|
|
105
|
+
*
|
|
106
|
+
* The returned `TileMap` does **not** own the tileset textures - they remain
|
|
107
|
+
* in the Loader cache. Destroying the returned map does not unload textures.
|
|
108
|
+
*/
|
|
109
|
+
toTileMap() {
|
|
110
|
+
this._chunkSources.clear();
|
|
111
|
+
if (this.orientation !== "orthogonal") throw new TiledFormatError(this.source, "orientation", `toTileMap() supports only orthogonal maps in this release, got "${this.orientation}"`);
|
|
112
|
+
const runtimeTilesets = [];
|
|
113
|
+
const indexToRuntime = [];
|
|
114
|
+
for (let i = 0; i < this.tilesets.length; i++) {
|
|
115
|
+
const tiledTs = this.tilesets[i];
|
|
116
|
+
if (!tiledTs) {
|
|
117
|
+
indexToRuntime.push(null);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!tiledTs.texture) {
|
|
121
|
+
if (tiledTs.tileTextures.size > 0) throw new TiledFormatError(this.source, `tilesets/${tiledTs.name}`, `tileset "${tiledTs.name}" is a collection-of-images tileset; toTileMap() requires atlas tilesets in this release`);
|
|
122
|
+
throw new TiledFormatError(this.source, `tilesets/${tiledTs.name}`, `tileset "${tiledTs.name}" has no image; an atlas tileset needs an "image" that resolves to a loaded texture`);
|
|
123
|
+
}
|
|
124
|
+
const tw = tiledTs.imageWidth ?? tiledTs.texture.width;
|
|
125
|
+
const th = tiledTs.imageHeight ?? tiledTs.texture.height;
|
|
126
|
+
const region = new TextureRegion(tiledTs.texture, {
|
|
127
|
+
x: 0,
|
|
128
|
+
y: 0,
|
|
129
|
+
width: tw,
|
|
130
|
+
height: th
|
|
131
|
+
});
|
|
132
|
+
const rts = new TileSet({
|
|
133
|
+
name: tiledTs.name,
|
|
134
|
+
texture: region,
|
|
135
|
+
tileWidth: tiledTs.tileWidth,
|
|
136
|
+
tileHeight: tiledTs.tileHeight,
|
|
137
|
+
tileCount: tiledTs.tileCount,
|
|
138
|
+
columns: tiledTs.columns,
|
|
139
|
+
spacing: tiledTs.spacing,
|
|
140
|
+
margin: tiledTs.margin,
|
|
141
|
+
class: tiledTs.class,
|
|
142
|
+
offsetX: tiledTs.tileOffset.x,
|
|
143
|
+
offsetY: tiledTs.tileOffset.y
|
|
144
|
+
});
|
|
145
|
+
const defs = buildTileDefinitions(tiledTs.tiles, tiledTs.tileCount);
|
|
146
|
+
if (defs.length > 0) rts._setDefinitions(defs);
|
|
147
|
+
runtimeTilesets.push(rts);
|
|
148
|
+
indexToRuntime.push(rts);
|
|
149
|
+
}
|
|
150
|
+
const runtimeLayers = [];
|
|
151
|
+
const runtimeObjectLayers = [];
|
|
152
|
+
const runtimeImageLayers = [];
|
|
153
|
+
const order = [];
|
|
154
|
+
const convertLayers = (layers, group) => {
|
|
155
|
+
for (const layer of layers) if (layer instanceof TiledGroupLayer) convertLayers(layer.layers, composeGroupStyle(group, layer));
|
|
156
|
+
else if (layer instanceof TiledTileLayer) {
|
|
157
|
+
const chunked = layer.chunks !== void 0;
|
|
158
|
+
const rLayer = new TileLayer({
|
|
159
|
+
id: layer.id,
|
|
160
|
+
name: layer.name,
|
|
161
|
+
...chunked ? {} : {
|
|
162
|
+
width: layer.width,
|
|
163
|
+
height: layer.height
|
|
164
|
+
},
|
|
165
|
+
tilesets: runtimeTilesets,
|
|
166
|
+
tileWidth: this.tileWidth,
|
|
167
|
+
tileHeight: this.tileHeight,
|
|
168
|
+
visible: group.visible && layer.visible,
|
|
169
|
+
opacity: group.opacity * layer.opacity,
|
|
170
|
+
offsetX: group.offsetX + layer.offsetX,
|
|
171
|
+
offsetY: group.offsetY + layer.offsetY,
|
|
172
|
+
parallaxX: group.parallaxX * layer.parallaxX,
|
|
173
|
+
parallaxY: group.parallaxY * layer.parallaxY,
|
|
174
|
+
class: layer.class,
|
|
175
|
+
tintColor: multiplyTiledTint(group.tintColor, parseTiledColor(layer.tintColor))
|
|
176
|
+
});
|
|
177
|
+
if (layer.data) populateTileLayer(rLayer, layer.data, this.tilesets, indexToRuntime, layer.width);
|
|
178
|
+
else if (chunked) this._chunkSources.set(layer.id, buildTiledChunkSource(layer, rLayer, this.tilesets, indexToRuntime, this.source));
|
|
179
|
+
runtimeLayers.push(rLayer);
|
|
180
|
+
order.push(layer.id);
|
|
181
|
+
} else if (layer instanceof TiledObjectLayer) runtimeObjectLayers.push(convertObjectLayer(layer, this.tilesets, indexToRuntime, this.orientation, group));
|
|
182
|
+
else if (layer instanceof TiledImageLayer) {
|
|
183
|
+
runtimeImageLayers.push(new ImageLayer({
|
|
184
|
+
id: layer.id,
|
|
185
|
+
name: layer.name,
|
|
186
|
+
class: layer.class,
|
|
187
|
+
image: resolveTiledUrl(layer.image, this.source),
|
|
188
|
+
texture: this._imageTextures.get(layer.id) ?? null,
|
|
189
|
+
visible: group.visible && layer.visible,
|
|
190
|
+
opacity: group.opacity * layer.opacity,
|
|
191
|
+
offsetX: group.offsetX + layer.offsetX,
|
|
192
|
+
offsetY: group.offsetY + layer.offsetY,
|
|
193
|
+
parallaxX: group.parallaxX * layer.parallaxX,
|
|
194
|
+
parallaxY: group.parallaxY * layer.parallaxY,
|
|
195
|
+
tintColor: multiplyTiledTint(group.tintColor, parseTiledColor(layer.tintColor)),
|
|
196
|
+
repeatX: layer.repeatX,
|
|
197
|
+
repeatY: layer.repeatY,
|
|
198
|
+
properties: convertProperties(layer.properties)
|
|
199
|
+
}));
|
|
200
|
+
order.push(layer.id);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
convertLayers(this.layers, rootGroupStyle);
|
|
204
|
+
return new TileMap({
|
|
205
|
+
name: this.source,
|
|
206
|
+
...this.infinite ? {} : {
|
|
207
|
+
width: this.width,
|
|
208
|
+
height: this.height
|
|
209
|
+
},
|
|
210
|
+
tileWidth: this.tileWidth,
|
|
211
|
+
tileHeight: this.tileHeight,
|
|
212
|
+
tilesets: runtimeTilesets,
|
|
213
|
+
layers: runtimeLayers,
|
|
214
|
+
objectLayers: runtimeObjectLayers,
|
|
215
|
+
imageLayers: runtimeImageLayers,
|
|
216
|
+
class: this.class,
|
|
217
|
+
backgroundColor: parseTiledColor(this.backgroundColor),
|
|
218
|
+
renderOrder: this.renderOrder ?? "right-down",
|
|
219
|
+
properties: convertProperties(this.properties),
|
|
220
|
+
documentOrder: order
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The {@link ChunkSource} built for the chunked tile layer with the given
|
|
225
|
+
* Tiled layer `id`, as a side effect of the most recent {@link toTileMap}
|
|
226
|
+
* call. Returns `undefined` if `toTileMap()` hasn't been called yet, if
|
|
227
|
+
* `layerId` doesn't name a tile layer, or if that layer is finite
|
|
228
|
+
* (`data`-based) rather than chunked (`infinite`-map `chunks`-based).
|
|
229
|
+
*
|
|
230
|
+
* Call {@link toTileMap} before calling this - it is what builds the
|
|
231
|
+
* providers this method reads from.
|
|
232
|
+
* @advanced
|
|
233
|
+
*/
|
|
234
|
+
getChunkSource(layerId) {
|
|
235
|
+
return this._chunkSources.get(layerId);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Releases this map's reference to its parsed source. Tileset textures are
|
|
239
|
+
* Loader-owned and may be shared with other maps; this does NOT destroy
|
|
240
|
+
* them.
|
|
241
|
+
*/
|
|
242
|
+
destroy() {}
|
|
243
|
+
};
|
|
240
244
|
/**
|
|
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
|
-
|
|
245
|
+
* Index of the tileset owning `baseGid` (flip flags already masked off), or
|
|
246
|
+
* `-1` when none covers it: the rightmost entry with `firstGid <= baseGid`,
|
|
247
|
+
* relying on `tilesets` being sorted ascending.
|
|
248
|
+
*/
|
|
249
|
+
const findTilesetIndexForGid = (baseGid, tiledTilesets) => {
|
|
250
|
+
for (let t = tiledTilesets.length - 1; t >= 0; t--) {
|
|
251
|
+
const candidate = tiledTilesets[t];
|
|
252
|
+
if (candidate && baseGid >= candidate.firstGid) return t;
|
|
253
|
+
}
|
|
254
|
+
return -1;
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Resolve a raw (flag-bearing) Tiled GID to a runtime {@link ResolvedTile}, or
|
|
258
|
+
* `null` for an empty cell or a GID no tileset covers.
|
|
259
|
+
*/
|
|
260
|
+
const resolveGid = (rawGid, tiledTilesets, indexToRuntime) => {
|
|
261
|
+
if (rawGid === 0) return null;
|
|
262
|
+
const baseGid = maskTiledGid(rawGid);
|
|
263
|
+
const transform = {
|
|
264
|
+
flipX: (rawGid >>> 0 & TILED_FLIPPED_HORIZONTALLY_FLAG) !== 0,
|
|
265
|
+
flipY: (rawGid >>> 0 & TILED_FLIPPED_VERTICALLY_FLAG) !== 0,
|
|
266
|
+
diagonal: (rawGid >>> 0 & TILED_FLIPPED_DIAGONALLY_FLAG) !== 0
|
|
267
|
+
};
|
|
268
|
+
const tsIdx = findTilesetIndexForGid(baseGid, tiledTilesets);
|
|
269
|
+
if (tsIdx === -1) return null;
|
|
270
|
+
const owningTs = tiledTilesets[tsIdx];
|
|
271
|
+
const runtimeTs = indexToRuntime[tsIdx];
|
|
272
|
+
if (!owningTs || !runtimeTs) return null;
|
|
273
|
+
return {
|
|
274
|
+
tileset: runtimeTs,
|
|
275
|
+
localTileId: baseGid - owningTs.firstGid,
|
|
276
|
+
transform
|
|
277
|
+
};
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* Fill a `TileLayer` from a flat GID array decoded from a Tiled tile layer.
|
|
281
|
+
*
|
|
282
|
+
* @param width The source Tiled layer's width in tiles - `toTileMap()` always
|
|
283
|
+
* constructs `layer` as bounded with this same width, but that
|
|
284
|
+
* fact isn't visible to the type checker from `layer` alone
|
|
285
|
+
* (runtime `TileLayer.width` is `number | undefined` since it
|
|
286
|
+
* also supports unbounded layers), so the caller's already-known
|
|
287
|
+
* Tiled-source width is threaded through explicitly instead.
|
|
288
|
+
*/
|
|
289
|
+
const populateTileLayer = (layer, gids, tiledTilesets, indexToRuntime, width) => {
|
|
290
|
+
for (let i = 0; i < gids.length; i++) {
|
|
291
|
+
const gid = gids[i];
|
|
292
|
+
if (gid === void 0) continue;
|
|
293
|
+
const tile = resolveGid(gid, tiledTilesets, indexToRuntime);
|
|
294
|
+
if (!tile) continue;
|
|
295
|
+
layer.setTileAt(i % width, Math.floor(i / width), tile);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* Build a {@link ChunkSource} that lazily re-slices a chunked
|
|
300
|
+
* {@link TiledTileLayer}'s fixed-size on-disk chunks into `runtimeLayer`'s
|
|
301
|
+
* own chunk grid. Indexes `layer.chunks` once by on-disk coordinate;
|
|
302
|
+
* `getChunk` composes each request from the on-disk chunks it overlaps.
|
|
303
|
+
*
|
|
304
|
+
* @throws {TiledFormatError} if the layer's on-disk chunks don't all share
|
|
305
|
+
* the same width/height (Tiled always emits a uniform size in
|
|
306
|
+
* practice; this is a defensive guard against malformed input).
|
|
307
|
+
* @throws {TiledFormatError} if an on-disk chunk's `x`/`y` isn't aligned to
|
|
308
|
+
* the on-disk chunk grid (Tiled always emits aligned chunks; a
|
|
309
|
+
* misaligned chunk would otherwise silently corrupt the re-sliced
|
|
310
|
+
* tile indices instead of failing loudly).
|
|
311
|
+
*/
|
|
312
|
+
const buildTiledChunkSource = (layer, runtimeLayer, tiledTilesets, indexToRuntime, source) => {
|
|
313
|
+
const index = /* @__PURE__ */ new Map();
|
|
314
|
+
let onDiskWidth = 0;
|
|
315
|
+
let onDiskHeight = 0;
|
|
316
|
+
for (const chunk of layer.chunks ?? []) {
|
|
317
|
+
if (onDiskWidth === 0) {
|
|
318
|
+
onDiskWidth = chunk.width;
|
|
319
|
+
onDiskHeight = chunk.height;
|
|
320
|
+
} else if (chunk.width !== onDiskWidth || chunk.height !== onDiskHeight) throw new TiledFormatError(source, `layers/${layer.name}/chunks`, `non-uniform infinite-map chunk size is not supported (expected ${onDiskWidth}x${onDiskHeight}, got ${chunk.width}x${chunk.height})`);
|
|
321
|
+
if (chunk.x % onDiskWidth !== 0 || chunk.y % onDiskHeight !== 0) throw new TiledFormatError(source, `layers/${layer.name}/chunks`, `misaligned infinite-map chunk in layer ${layer.id} at (${chunk.x}, ${chunk.y}) is not a multiple of the on-disk chunk size (${onDiskWidth}x${onDiskHeight})`);
|
|
322
|
+
index.set(`${chunk.x},${chunk.y}`, chunk);
|
|
323
|
+
}
|
|
324
|
+
return { getChunk(cx, cy) {
|
|
325
|
+
if (onDiskWidth === 0) return null;
|
|
326
|
+
const chunkWidth = runtimeLayer.chunkWidth;
|
|
327
|
+
const chunkHeight = runtimeLayer.chunkHeight;
|
|
328
|
+
const qx0 = cx * chunkWidth;
|
|
329
|
+
const qy0 = cy * chunkHeight;
|
|
330
|
+
const qx1 = qx0 + chunkWidth - 1;
|
|
331
|
+
const qy1 = qy0 + chunkHeight - 1;
|
|
332
|
+
const gx0 = Math.floor(qx0 / onDiskWidth) * onDiskWidth;
|
|
333
|
+
const gx1 = Math.floor(qx1 / onDiskWidth) * onDiskWidth;
|
|
334
|
+
const gy0 = Math.floor(qy0 / onDiskHeight) * onDiskHeight;
|
|
335
|
+
const gy1 = Math.floor(qy1 / onDiskHeight) * onDiskHeight;
|
|
336
|
+
let out = null;
|
|
337
|
+
for (let gy = gy0; gy <= gy1; gy += onDiskHeight) for (let gx = gx0; gx <= gx1; gx += onDiskWidth) {
|
|
338
|
+
const chunk = index.get(`${gx},${gy}`);
|
|
339
|
+
if (!chunk) continue;
|
|
340
|
+
const ix0 = Math.max(qx0, gx);
|
|
341
|
+
const ix1 = Math.min(qx1, gx + onDiskWidth - 1);
|
|
342
|
+
const iy0 = Math.max(qy0, gy);
|
|
343
|
+
const iy1 = Math.min(qy1, gy + onDiskHeight - 1);
|
|
344
|
+
for (let ty = iy0; ty <= iy1; ty++) for (let tx = ix0; tx <= ix1; tx++) {
|
|
345
|
+
const rawGid = chunk.data[(ty - gy) * onDiskWidth + (tx - gx)];
|
|
346
|
+
if (rawGid === void 0 || rawGid === 0) continue;
|
|
347
|
+
const resolved = resolveGid(rawGid, tiledTilesets, indexToRuntime);
|
|
348
|
+
if (!resolved) continue;
|
|
349
|
+
out ??= new Uint32Array(chunkWidth * chunkHeight);
|
|
350
|
+
const tilesetIndex = runtimeLayer.tilesets.indexOf(resolved.tileset);
|
|
351
|
+
out[(ty - qy0) * chunkWidth + (tx - qx0)] = packTile(tilesetIndex, resolved.localTileId, resolved.transform);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return out === null ? null : {
|
|
355
|
+
width: chunkWidth,
|
|
356
|
+
height: chunkHeight,
|
|
357
|
+
tiles: out
|
|
358
|
+
};
|
|
359
|
+
} };
|
|
360
|
+
};
|
|
286
361
|
/** Convert a parsed `TiledObjectLayer` into a runtime data-only `ObjectLayer`. */
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
362
|
+
const convertObjectLayer = (layer, tiledTilesets, indexToRuntime, orientation, group) => {
|
|
363
|
+
const objects = [];
|
|
364
|
+
for (const object of layer.objects) {
|
|
365
|
+
const converted = convertObject(object, tiledTilesets, indexToRuntime, orientation);
|
|
366
|
+
if (converted) objects.push(converted);
|
|
367
|
+
}
|
|
368
|
+
return new ObjectLayer({
|
|
369
|
+
id: layer.id,
|
|
370
|
+
name: layer.name,
|
|
371
|
+
class: layer.class,
|
|
372
|
+
visible: group.visible && layer.visible,
|
|
373
|
+
opacity: group.opacity * layer.opacity,
|
|
374
|
+
offsetX: group.offsetX + layer.offsetX,
|
|
375
|
+
offsetY: group.offsetY + layer.offsetY,
|
|
376
|
+
parallaxX: group.parallaxX * layer.parallaxX,
|
|
377
|
+
parallaxY: group.parallaxY * layer.parallaxY,
|
|
378
|
+
tintColor: multiplyTiledTint(group.tintColor, parseTiledColor(layer.tintColor)),
|
|
379
|
+
drawOrder: layer.drawOrder,
|
|
380
|
+
objects,
|
|
381
|
+
properties: convertProperties(layer.properties)
|
|
382
|
+
});
|
|
383
|
+
};
|
|
384
|
+
/**
|
|
385
|
+
* Convert one `TiledObject` to a `TileMapObject`. A tile object whose GID
|
|
386
|
+
* resolves to no tileset is dropped - returns `null`.
|
|
387
|
+
*
|
|
388
|
+
* Tiled stores a **tile object**'s `x`/`y` at the tileset's object-alignment
|
|
389
|
+
* anchor (bottom-left by default in an orthogonal map, bottom-centre in an
|
|
390
|
+
* isometric one), while every other object kind stores the top-left corner of
|
|
391
|
+
* its bounding box. The anchor is converted to the corner here so that
|
|
392
|
+
* `TileMapObject.x`/`y` means the same thing for every kind - see
|
|
393
|
+
* {@link import('@codexo/exojs-tilemap').TileObject} for the resulting
|
|
394
|
+
* convention.
|
|
395
|
+
*/
|
|
396
|
+
const convertObject = (object, tiledTilesets, indexToRuntime, orientation) => {
|
|
397
|
+
const base = {
|
|
398
|
+
id: object.id,
|
|
399
|
+
name: object.name,
|
|
400
|
+
type: object.type,
|
|
401
|
+
x: object.x,
|
|
402
|
+
y: object.y,
|
|
403
|
+
width: object.width,
|
|
404
|
+
height: object.height,
|
|
405
|
+
rotation: object.rotation,
|
|
406
|
+
visible: object.visible,
|
|
407
|
+
properties: convertProperties(object.properties),
|
|
408
|
+
source: object
|
|
409
|
+
};
|
|
410
|
+
if (object.text) {
|
|
411
|
+
const t = object.text;
|
|
412
|
+
const color = t.color !== void 0 ? parseTiledColor(t.color) : void 0;
|
|
413
|
+
const textStyle = {
|
|
414
|
+
text: t.text,
|
|
415
|
+
...color !== void 0 && { color },
|
|
416
|
+
...t.fontfamily !== void 0 && { fontFamily: t.fontfamily },
|
|
417
|
+
...t.pixelsize !== void 0 && { pixelSize: t.pixelsize },
|
|
418
|
+
...t.bold !== void 0 && { bold: t.bold },
|
|
419
|
+
...t.italic !== void 0 && { italic: t.italic },
|
|
420
|
+
...t.underline !== void 0 && { underline: t.underline },
|
|
421
|
+
...t.strikeout !== void 0 && { strikeout: t.strikeout },
|
|
422
|
+
...t.wrap !== void 0 && { wrap: t.wrap },
|
|
423
|
+
...t.halign !== void 0 && { halign: t.halign },
|
|
424
|
+
...t.valign !== void 0 && { valign: t.valign }
|
|
425
|
+
};
|
|
426
|
+
return {
|
|
427
|
+
...base,
|
|
428
|
+
kind: "text",
|
|
429
|
+
text: textStyle
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (object.gid !== void 0) {
|
|
433
|
+
const tile = resolveGid(object.gid, tiledTilesets, indexToRuntime);
|
|
434
|
+
if (!tile) return null;
|
|
435
|
+
const anchor = tileObjectAnchorOffset(object.gid, object.width, object.height, tiledTilesets, orientation);
|
|
436
|
+
return {
|
|
437
|
+
...base,
|
|
438
|
+
x: base.x - anchor.x,
|
|
439
|
+
y: base.y - anchor.y,
|
|
440
|
+
kind: "tile",
|
|
441
|
+
tile
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
if (object.point) return {
|
|
445
|
+
...base,
|
|
446
|
+
kind: "point"
|
|
447
|
+
};
|
|
448
|
+
if (object.ellipse) return {
|
|
449
|
+
...base,
|
|
450
|
+
kind: "ellipse"
|
|
451
|
+
};
|
|
452
|
+
if (object.polygon) return {
|
|
453
|
+
...base,
|
|
454
|
+
kind: "polygon",
|
|
455
|
+
points: toPoints(object.polygon)
|
|
456
|
+
};
|
|
457
|
+
if (object.polyline) return {
|
|
458
|
+
...base,
|
|
459
|
+
kind: "polyline",
|
|
460
|
+
points: toPoints(object.polyline)
|
|
461
|
+
};
|
|
462
|
+
return {
|
|
463
|
+
...base,
|
|
464
|
+
kind: "rectangle"
|
|
465
|
+
};
|
|
466
|
+
};
|
|
467
|
+
const toPoints = (points) => points.map((p) => ({
|
|
468
|
+
x: p.x,
|
|
469
|
+
y: p.y
|
|
470
|
+
}));
|
|
471
|
+
/** Shared zero offset for a tile object whose GID belongs to no tileset. */
|
|
472
|
+
const ORIGIN_OFFSET = {
|
|
473
|
+
x: 0,
|
|
474
|
+
y: 0
|
|
475
|
+
};
|
|
476
|
+
/**
|
|
477
|
+
* Offset from a tile object's stored anchor to the top-left corner of its
|
|
478
|
+
* bounding box, using the owning tileset's `objectalignment` (or Tiled's
|
|
479
|
+
* orientation-dependent default). Falls back to no offset when `gid` belongs
|
|
480
|
+
* to no tileset - such an object is dropped by the caller anyway.
|
|
481
|
+
*/
|
|
482
|
+
const tileObjectAnchorOffset = (gid, width, height, tiledTilesets, orientation) => {
|
|
483
|
+
const owningTs = tiledTilesets[findTilesetIndexForGid(maskTiledGid(gid), tiledTilesets)];
|
|
484
|
+
if (!owningTs) return ORIGIN_OFFSET;
|
|
485
|
+
const alignment = resolveTiledObjectAlignment(owningTs.objectAlignment, orientation);
|
|
486
|
+
return getTiledObjectAnchorOffset(alignment, width, height);
|
|
487
|
+
};
|
|
488
|
+
/** The identity style: what a layer at the top level of the document inherits. */
|
|
489
|
+
const rootGroupStyle = {
|
|
490
|
+
visible: true,
|
|
491
|
+
opacity: 1,
|
|
492
|
+
offsetX: 0,
|
|
493
|
+
offsetY: 0,
|
|
494
|
+
parallaxX: 1,
|
|
495
|
+
parallaxY: 1,
|
|
496
|
+
tintColor: null
|
|
497
|
+
};
|
|
498
|
+
/**
|
|
499
|
+
* Fold `group`'s own style into the style its children inherit, composing each
|
|
500
|
+
* channel the way Tiled derives a nested layer's effective value: visibility
|
|
501
|
+
* ANDs, opacity and parallax multiply, offsets add, and tints multiply per
|
|
502
|
+
* colour channel.
|
|
503
|
+
*/
|
|
504
|
+
const composeGroupStyle = (parent, group) => ({
|
|
505
|
+
visible: parent.visible && group.visible,
|
|
506
|
+
opacity: parent.opacity * group.opacity,
|
|
507
|
+
offsetX: parent.offsetX + group.offsetX,
|
|
508
|
+
offsetY: parent.offsetY + group.offsetY,
|
|
509
|
+
parallaxX: parent.parallaxX * group.parallaxX,
|
|
510
|
+
parallaxY: parent.parallaxY * group.parallaxY,
|
|
511
|
+
tintColor: multiplyTiledTint(parent.tintColor, parseTiledColor(group.tintColor))
|
|
512
|
+
});
|
|
307
513
|
/**
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
rotation: object.rotation,
|
|
321
|
-
visible: object.visible,
|
|
322
|
-
properties: convertProperties(object.properties),
|
|
323
|
-
};
|
|
324
|
-
if (object.text) {
|
|
325
|
-
const t = object.text;
|
|
326
|
-
const color = t.color !== undefined ? parseTiledColor(t.color) : undefined;
|
|
327
|
-
const textStyle = {
|
|
328
|
-
text: t.text,
|
|
329
|
-
...(color !== undefined && { color }),
|
|
330
|
-
...(t.fontfamily !== undefined && { fontFamily: t.fontfamily }),
|
|
331
|
-
...(t.pixelsize !== undefined && { pixelSize: t.pixelsize }),
|
|
332
|
-
...(t.bold !== undefined && { bold: t.bold }),
|
|
333
|
-
...(t.italic !== undefined && { italic: t.italic }),
|
|
334
|
-
...(t.underline !== undefined && { underline: t.underline }),
|
|
335
|
-
...(t.strikeout !== undefined && { strikeout: t.strikeout }),
|
|
336
|
-
...(t.wrap !== undefined && { wrap: t.wrap }),
|
|
337
|
-
...(t.halign !== undefined && { halign: t.halign }),
|
|
338
|
-
...(t.valign !== undefined && { valign: t.valign }),
|
|
339
|
-
};
|
|
340
|
-
return { ...base, kind: 'text', text: textStyle };
|
|
341
|
-
}
|
|
342
|
-
if (object.gid !== undefined) {
|
|
343
|
-
const tile = resolveGid(object.gid, tiledTilesets, indexToRuntime);
|
|
344
|
-
if (!tile)
|
|
345
|
-
return null;
|
|
346
|
-
return { ...base, kind: 'tile', tile };
|
|
347
|
-
}
|
|
348
|
-
if (object.point) {
|
|
349
|
-
return { ...base, kind: 'point' };
|
|
350
|
-
}
|
|
351
|
-
if (object.ellipse) {
|
|
352
|
-
return { ...base, kind: 'ellipse' };
|
|
353
|
-
}
|
|
354
|
-
if (object.polygon) {
|
|
355
|
-
return { ...base, kind: 'polygon', points: toPoints(object.polygon) };
|
|
356
|
-
}
|
|
357
|
-
if (object.polyline) {
|
|
358
|
-
return { ...base, kind: 'polyline', points: toPoints(object.polyline) };
|
|
359
|
-
}
|
|
360
|
-
return { ...base, kind: 'rectangle' };
|
|
361
|
-
}
|
|
362
|
-
const toPoints = (points) => points.map(p => ({ x: p.x, y: p.y }));
|
|
514
|
+
* Multiply two `0xRRGGBB` tints channel-wise (Tiled's own composition rule).
|
|
515
|
+
* `null` means "no tint" and acts as the identity, so an untinted group leaves
|
|
516
|
+
* its children's tints untouched - and vice versa.
|
|
517
|
+
*/
|
|
518
|
+
const multiplyTiledTint = (a, b) => {
|
|
519
|
+
if (a === null) return b;
|
|
520
|
+
if (b === null) return a;
|
|
521
|
+
const red = Math.round((a >> 16 & 255) * (b >> 16 & 255) / 255);
|
|
522
|
+
const green = Math.round((a >> 8 & 255) * (b >> 8 & 255) / 255);
|
|
523
|
+
const blue = Math.round((a & 255) * (b & 255) / 255);
|
|
524
|
+
return red << 16 | green << 8 | blue;
|
|
525
|
+
};
|
|
363
526
|
/**
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
return null;
|
|
380
|
-
}
|
|
381
|
-
const parsed = Number.parseInt(rrggbb, 16);
|
|
382
|
-
return Number.isNaN(parsed) ? null : parsed;
|
|
383
|
-
}
|
|
527
|
+
* Parse a Tiled colour string (`#RRGGBB` or `#AARRGGBB`) into a `0xRRGGBB`
|
|
528
|
+
* integer, dropping any alpha. Returns `null` for an absent or malformed value.
|
|
529
|
+
*/
|
|
530
|
+
const parseTiledColor = (value) => {
|
|
531
|
+
if (value === void 0 || value === "") return null;
|
|
532
|
+
const hex = value.startsWith("#") ? value.slice(1) : value;
|
|
533
|
+
let rrggbb;
|
|
534
|
+
if (hex.length === 8) rrggbb = hex.slice(2);
|
|
535
|
+
else if (hex.length === 6) rrggbb = hex;
|
|
536
|
+
else return null;
|
|
537
|
+
const parsed = Number.parseInt(rrggbb, 16);
|
|
538
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
539
|
+
};
|
|
384
540
|
/**
|
|
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
|
-
...(collision !== undefined && { collision }),
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
return defs;
|
|
424
|
-
}
|
|
541
|
+
* Build runtime {@link TileDefinition}s from a Tiled tileset's per-tile data,
|
|
542
|
+
* carrying over per-tile properties, animation frames, and collision shapes
|
|
543
|
+
* (the tile's `objectgroup`). Tiles whose `id` is out of range, and animation
|
|
544
|
+
* frames referencing out-of-range local ids, are skipped so the runtime tileset
|
|
545
|
+
* never receives malformed definitions.
|
|
546
|
+
*/
|
|
547
|
+
const buildTileDefinitions = (tiles, tileCount) => {
|
|
548
|
+
const defs = [];
|
|
549
|
+
for (const tile of tiles) {
|
|
550
|
+
if (tile.id < 0 || tile.id >= tileCount) continue;
|
|
551
|
+
const properties = tile.properties ? convertProperties(tile.properties) : void 0;
|
|
552
|
+
const hasProps = properties !== void 0 && Object.keys(properties).length > 0;
|
|
553
|
+
let animation;
|
|
554
|
+
if (tile.animation && tile.animation.length > 0) {
|
|
555
|
+
const frames = tile.animation.filter((frame) => frame.tileid >= 0 && frame.tileid < tileCount).map((frame) => ({
|
|
556
|
+
localTileId: frame.tileid,
|
|
557
|
+
duration: frame.duration
|
|
558
|
+
}));
|
|
559
|
+
if (frames.length > 0) animation = frames;
|
|
560
|
+
}
|
|
561
|
+
let collision;
|
|
562
|
+
if (tile.objectgroup && tile.objectgroup.objects.length > 0) {
|
|
563
|
+
const shapes = tile.objectgroup.objects.map((obj) => convertCollisionObject(obj)).filter((obj) => obj !== null);
|
|
564
|
+
if (shapes.length > 0) collision = shapes;
|
|
565
|
+
}
|
|
566
|
+
if (!hasProps && animation === void 0 && collision === void 0) continue;
|
|
567
|
+
defs.push({
|
|
568
|
+
localTileId: tile.id,
|
|
569
|
+
...hasProps && { properties },
|
|
570
|
+
...animation !== void 0 && { animation },
|
|
571
|
+
...collision !== void 0 && { collision }
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
return defs;
|
|
575
|
+
};
|
|
425
576
|
/**
|
|
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
|
-
|
|
577
|
+
* Convert a raw `TiledObjectData` to a runtime `TileMapObject` for use as a
|
|
578
|
+
* per-tile collision shape. Text objects and tile-object (gid) references are
|
|
579
|
+
* dropped - returns `null`. Does not perform GID resolution; collision shapes
|
|
580
|
+
* in tile objectgroups are almost exclusively plain geometry.
|
|
581
|
+
*/
|
|
582
|
+
const convertCollisionObject = (obj) => {
|
|
583
|
+
if (obj.text) return null;
|
|
584
|
+
if (obj.gid !== void 0) return null;
|
|
585
|
+
const base = {
|
|
586
|
+
id: obj.id,
|
|
587
|
+
name: obj.name,
|
|
588
|
+
type: obj.type,
|
|
589
|
+
x: obj.x,
|
|
590
|
+
y: obj.y,
|
|
591
|
+
width: obj.width,
|
|
592
|
+
height: obj.height,
|
|
593
|
+
rotation: obj.rotation,
|
|
594
|
+
visible: obj.visible,
|
|
595
|
+
properties: convertProperties(obj.properties ?? [])
|
|
596
|
+
};
|
|
597
|
+
if (obj.point === true) return {
|
|
598
|
+
...base,
|
|
599
|
+
kind: "point"
|
|
600
|
+
};
|
|
601
|
+
if (obj.ellipse === true) return {
|
|
602
|
+
...base,
|
|
603
|
+
kind: "ellipse"
|
|
604
|
+
};
|
|
605
|
+
if (obj.polygon) return {
|
|
606
|
+
...base,
|
|
607
|
+
kind: "polygon",
|
|
608
|
+
points: toPoints(obj.polygon)
|
|
609
|
+
};
|
|
610
|
+
if (obj.polyline) return {
|
|
611
|
+
...base,
|
|
612
|
+
kind: "polyline",
|
|
613
|
+
points: toPoints(obj.polyline)
|
|
614
|
+
};
|
|
615
|
+
return {
|
|
616
|
+
...base,
|
|
617
|
+
kind: "rectangle"
|
|
618
|
+
};
|
|
619
|
+
};
|
|
458
620
|
/**
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
return Object.freeze(out);
|
|
477
|
-
}
|
|
621
|
+
* Project Tiled custom properties to the generic flat property bag.
|
|
622
|
+
* `object`-typed properties become a {@link TilePropertyKind.ObjectRef}
|
|
623
|
+
* (the wire value is already the referenced object's numeric id - Tiled has
|
|
624
|
+
* no LDtk-style navigation fields, so those stay `undefined`). `class`-typed
|
|
625
|
+
* properties recursively convert their nested member bag into a
|
|
626
|
+
* {@link TileProperties}.
|
|
627
|
+
*/
|
|
628
|
+
const convertProperties = (properties) => {
|
|
629
|
+
if (properties.length === 0) return Object.freeze({});
|
|
630
|
+
const out = {};
|
|
631
|
+
for (const property of properties) {
|
|
632
|
+
const value = convertPropertyValue(property);
|
|
633
|
+
if (value !== void 0) out[property.name] = value;
|
|
634
|
+
}
|
|
635
|
+
return Object.freeze(out);
|
|
636
|
+
};
|
|
478
637
|
/** Convert one {@link TiledPropertyData} to its canonical {@link TilePropertyValue}. */
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
638
|
+
const convertPropertyValue = (property) => {
|
|
639
|
+
switch (property.type) {
|
|
640
|
+
case "string":
|
|
641
|
+
case "int":
|
|
642
|
+
case "float":
|
|
643
|
+
case "bool":
|
|
644
|
+
case "color":
|
|
645
|
+
case "file": return property.value;
|
|
646
|
+
case "object": return {
|
|
647
|
+
kind: TilePropertyKind.ObjectRef,
|
|
648
|
+
id: property.value
|
|
649
|
+
};
|
|
650
|
+
case "class": return convertClassPropertyValue(property.value, property.propertytype);
|
|
651
|
+
default:
|
|
652
|
+
property.type;
|
|
653
|
+
throw new Error(`convertProperties: unrecognised Tiled property type "${property.type}".`);
|
|
654
|
+
}
|
|
655
|
+
};
|
|
500
656
|
/**
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const tiledClassNameProperty =
|
|
657
|
+
* Reserved key under which a `class`-typed property's Tiled custom-class name
|
|
658
|
+
* ({@link TiledPropertyData.propertytype}, e.g. `"Stats"`) is stored inside the
|
|
659
|
+
* converted nested {@link TileProperties} bag, mirroring the `ldtkUid`/
|
|
660
|
+
* `ldtkWorldIid`-style reserved-metadata-key convention used by the LDtk
|
|
661
|
+
* adapter (`packages/exojs-ldtk/src/ldtkToTileMap.ts`). Only set on the
|
|
662
|
+
* top-level converted bag for a class-typed property that has a
|
|
663
|
+
* `propertytype` - nested-class members carry no `propertytype` of their own
|
|
664
|
+
* in Tiled's data model, so recursive calls never set it.
|
|
665
|
+
*/
|
|
666
|
+
const tiledClassNameProperty = "tiledClassName";
|
|
511
667
|
/**
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
function checkGidCoverage(map, source) {
|
|
571
|
-
walkLayers(map.layers, 'layers', (layer, layerPath) => {
|
|
572
|
-
if (layer instanceof TiledTileLayer) {
|
|
573
|
-
if (layer.data !== undefined) {
|
|
574
|
-
checkGidArray(layer.data, map, source, `${layerPath}.data`);
|
|
575
|
-
}
|
|
576
|
-
if (layer.chunks !== undefined) {
|
|
577
|
-
for (let c = 0; c < layer.chunks.length; c++) {
|
|
578
|
-
const chunk = layer.chunks[c];
|
|
579
|
-
if (chunk === undefined)
|
|
580
|
-
continue;
|
|
581
|
-
checkGidArray(chunk.data, map, source, `${layerPath}.chunks[${c}].data`);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
else if (layer instanceof TiledObjectLayer) {
|
|
586
|
-
for (let o = 0; o < layer.objects.length; o++) {
|
|
587
|
-
const object = layer.objects[o];
|
|
588
|
-
if (object === undefined)
|
|
589
|
-
continue;
|
|
590
|
-
const gid = object.gid;
|
|
591
|
-
// gid 0 (after masking the flip bits) is the empty-cell sentinel, the
|
|
592
|
-
// same as in tile-layer data — accept it as "no tile" instead of
|
|
593
|
-
// rejecting it as uncovered.
|
|
594
|
-
if (gid !== undefined && maskTiledGid(gid) !== 0 && map.findTilesetForGid(gid) === undefined) {
|
|
595
|
-
throw new TiledFormatError(source, `${layerPath}.objects[${o}].gid`, `gid ${gid} (masked: ${maskTiledGid(gid)}) is not covered by any tileset`);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
});
|
|
600
|
-
}
|
|
668
|
+
* Recursively convert a `class`-typed property's nested member bag into a
|
|
669
|
+
* {@link TileProperties}. Scalar members pass through; nested-class members
|
|
670
|
+
* recurse (Tiled classes may nest arbitrarily deep). When `propertytype` is
|
|
671
|
+
* given (the Tiled custom-class name), it is tagged onto the bag under the
|
|
672
|
+
* reserved {@link tiledClassNameProperty} key, applied last so it can never
|
|
673
|
+
* be clobbered by a same-named member.
|
|
674
|
+
*/
|
|
675
|
+
const convertClassPropertyValue = (value, propertytype) => {
|
|
676
|
+
const out = {};
|
|
677
|
+
for (const [name, member] of Object.entries(value)) out[name] = typeof member === "string" || typeof member === "number" || typeof member === "boolean" ? member : convertClassPropertyValue(member);
|
|
678
|
+
if (propertytype !== void 0) out[tiledClassNameProperty] = propertytype;
|
|
679
|
+
return Object.freeze(out);
|
|
680
|
+
};
|
|
681
|
+
const sortAndValidateTilesetRanges = (tilesets, source) => {
|
|
682
|
+
const sorted = [...tilesets].sort((a, b) => a.firstGid - b.firstGid);
|
|
683
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
684
|
+
const previous = sorted[i - 1];
|
|
685
|
+
const current = sorted[i];
|
|
686
|
+
if (previous === void 0 || current === void 0) continue;
|
|
687
|
+
if (current.firstGid === previous.firstGid) throw new TiledFormatError(source, "tilesets", `duplicate firstgid ${current.firstGid} (tilesets "${previous.name}" and "${current.name}")`);
|
|
688
|
+
if (current.firstGid <= previous.lastGid) throw new TiledFormatError(source, "tilesets", `tileset "${current.name}" (firstgid ${current.firstGid}) overlaps tileset "${previous.name}" (firstgid ${previous.firstGid}, last gid ${previous.lastGid})`);
|
|
689
|
+
}
|
|
690
|
+
return sorted;
|
|
691
|
+
};
|
|
692
|
+
const walkLayers = (layers, path, visit) => {
|
|
693
|
+
for (let i = 0; i < layers.length; i++) {
|
|
694
|
+
const layer = layers[i];
|
|
695
|
+
if (layer === void 0) continue;
|
|
696
|
+
const layerPath = `${path}[${i}]`;
|
|
697
|
+
visit(layer, layerPath);
|
|
698
|
+
if (layer instanceof TiledGroupLayer) walkLayers(layer.layers, `${layerPath}.layers`, visit);
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
const checkGidArray = (gids, map, source, path) => {
|
|
702
|
+
for (let i = 0; i < gids.length; i++) {
|
|
703
|
+
const gid = gids[i];
|
|
704
|
+
if (gid === void 0) continue;
|
|
705
|
+
if (gid !== 0 && map.findTilesetForGid(gid) === void 0) throw new TiledFormatError(source, `${path}[${i}]`, `gid ${gid} (masked: ${maskTiledGid(gid)}) is not covered by any tileset`);
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
const checkGidCoverage = (map, source) => {
|
|
709
|
+
walkLayers(map.layers, "layers", (layer, layerPath) => {
|
|
710
|
+
if (layer instanceof TiledTileLayer) {
|
|
711
|
+
if (layer.data !== void 0) checkGidArray(layer.data, map, source, `${layerPath}.data`);
|
|
712
|
+
if (layer.chunks !== void 0) for (let c = 0; c < layer.chunks.length; c++) {
|
|
713
|
+
const chunk = layer.chunks[c];
|
|
714
|
+
if (chunk === void 0) continue;
|
|
715
|
+
checkGidArray(chunk.data, map, source, `${layerPath}.chunks[${c}].data`);
|
|
716
|
+
}
|
|
717
|
+
} else if (layer instanceof TiledObjectLayer) for (let o = 0; o < layer.objects.length; o++) {
|
|
718
|
+
const object = layer.objects[o];
|
|
719
|
+
if (object === void 0) continue;
|
|
720
|
+
const gid = object.gid;
|
|
721
|
+
if (gid !== void 0 && maskTiledGid(gid) !== 0 && map.findTilesetForGid(gid) === void 0) throw new TiledFormatError(source, `${layerPath}.objects[${o}].gid`, `gid ${gid} (masked: ${maskTiledGid(gid)}) is not covered by any tileset`);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
};
|
|
601
725
|
|
|
726
|
+
//#endregion
|
|
602
727
|
export { TiledMap };
|
|
603
|
-
//# sourceMappingURL=TiledMap.js.map
|
|
728
|
+
//# sourceMappingURL=TiledMap.js.map
|