@codexo/exojs-tilemap 0.13.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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -0
  3. package/dist/esm/TileChunk.d.ts +136 -0
  4. package/dist/esm/TileChunk.js +192 -0
  5. package/dist/esm/TileChunk.js.map +1 -0
  6. package/dist/esm/TileChunkNode.d.ts +59 -0
  7. package/dist/esm/TileChunkNode.js +89 -0
  8. package/dist/esm/TileChunkNode.js.map +1 -0
  9. package/dist/esm/TileLayer.d.ts +229 -0
  10. package/dist/esm/TileLayer.js +450 -0
  11. package/dist/esm/TileLayer.js.map +1 -0
  12. package/dist/esm/TileLayerNode.d.ts +81 -0
  13. package/dist/esm/TileLayerNode.js +142 -0
  14. package/dist/esm/TileLayerNode.js.map +1 -0
  15. package/dist/esm/TileMap.d.ts +178 -0
  16. package/dist/esm/TileMap.js +262 -0
  17. package/dist/esm/TileMap.js.map +1 -0
  18. package/dist/esm/TileMapBand.d.ts +97 -0
  19. package/dist/esm/TileMapBand.js +161 -0
  20. package/dist/esm/TileMapBand.js.map +1 -0
  21. package/dist/esm/TileMapNode.d.ts +71 -0
  22. package/dist/esm/TileMapNode.js +113 -0
  23. package/dist/esm/TileMapNode.js.map +1 -0
  24. package/dist/esm/TileMapView.d.ts +182 -0
  25. package/dist/esm/TileMapView.js +342 -0
  26. package/dist/esm/TileMapView.js.map +1 -0
  27. package/dist/esm/TileSet.d.ts +99 -0
  28. package/dist/esm/TileSet.js +157 -0
  29. package/dist/esm/TileSet.js.map +1 -0
  30. package/dist/esm/chunkGeometry.d.ts +71 -0
  31. package/dist/esm/chunkGeometry.js +98 -0
  32. package/dist/esm/chunkGeometry.js.map +1 -0
  33. package/dist/esm/index.d.ts +1 -0
  34. package/dist/esm/index.js +10 -0
  35. package/dist/esm/index.js.map +1 -0
  36. package/dist/esm/pixelSnap.d.ts +12 -0
  37. package/dist/esm/pixelSnap.js +19 -0
  38. package/dist/esm/pixelSnap.js.map +1 -0
  39. package/dist/esm/public.d.ts +18 -0
  40. package/dist/esm/register.d.ts +1 -0
  41. package/dist/esm/register.js +20 -0
  42. package/dist/esm/register.js.map +1 -0
  43. package/dist/esm/tilemapExtension.d.ts +18 -0
  44. package/dist/esm/tilemapExtension.js +52 -0
  45. package/dist/esm/tilemapExtension.js.map +1 -0
  46. package/dist/esm/types.d.ts +139 -0
  47. package/dist/esm/types.js +121 -0
  48. package/dist/esm/types.js.map +1 -0
  49. package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts +39 -0
  50. package/dist/esm/webgl2/WebGl2TileChunkRenderer.js +339 -0
  51. package/dist/esm/webgl2/WebGl2TileChunkRenderer.js.map +1 -0
  52. package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts +42 -0
  53. package/dist/esm/webgpu/WebGpuTileChunkRenderer.js +390 -0
  54. package/dist/esm/webgpu/WebGpuTileChunkRenderer.js.map +1 -0
  55. package/package.json +48 -0
@@ -0,0 +1,97 @@
1
+ import { Container } from '@codexo/exojs';
2
+ import type { TileLayerNode } from './TileLayerNode';
3
+ /**
4
+ * A named, ordered group of {@link TileLayerNode}s produced by a
5
+ * {@link TileMapView} for ergonomic actor interleaving.
6
+ *
7
+ * A band is a plain {@link Container} scene node that the application parents
8
+ * wherever it wants (typically directly under its own world root). Bands are
9
+ * composition units only: a band owns **its generated tile-layer nodes** and
10
+ * never application actors. Placing actors *between* bands is done by parenting
11
+ * the bands and the actor containers as siblings in the desired document order:
12
+ *
13
+ * ```ts
14
+ * worldRoot.addChild(view.band('ground'), actors, view.band('roof'));
15
+ * ```
16
+ *
17
+ * **Rendering order within a band always follows map document order**, not the
18
+ * order layers were listed in the band definition — band membership *selects*
19
+ * layers; it never reorders them.
20
+ *
21
+ * **Ownership:** the band owns the {@link TileLayerNode}s it was created with.
22
+ * Destroying the band destroys those layer nodes (and their cached chunk
23
+ * geometry) and detaches the band from its application parent — it never
24
+ * touches application actors, sibling bands, the {@link TileMap}, the
25
+ * {@link TileLayer}s, or Loader-owned tileset textures.
26
+ *
27
+ * Bounds are the union of the band's tile-layer-node bounds (an empty band
28
+ * collapses to a degenerate rect at the band's transformed origin).
29
+ *
30
+ * Bands are created by {@link TileMapView}; construct them through
31
+ * {@link TileMapView} rather than directly.
32
+ *
33
+ * @advanced
34
+ */
35
+ export declare class TileMapBand extends Container {
36
+ private readonly _name;
37
+ private readonly _layerNodes;
38
+ private _destroyed;
39
+ /**
40
+ * @param name The band name (unique within its owning view).
41
+ * @param layerNodes The layer nodes composing this band, already ordered by
42
+ * map document order. Adopted as children.
43
+ * @internal Constructed by {@link TileMapView}.
44
+ */
45
+ constructor(name: string, layerNodes: readonly TileLayerNode[]);
46
+ /** The band name (unique within its owning {@link TileMapView}). */
47
+ get name(): string;
48
+ /**
49
+ * The tile-layer nodes that compose this band, in map document order. This
50
+ * is the band's **membership** list (the nodes it owns and destroys); it may
51
+ * differ from {@link Container.children} if the caller reparents an
52
+ * individual layer node elsewhere.
53
+ */
54
+ get layerNodes(): readonly TileLayerNode[];
55
+ /** Find this band's layer node rendering the layer with the given id. */
56
+ getLayerNodeById(id: number): TileLayerNode | undefined;
57
+ /** Whether this band currently owns no layer nodes. */
58
+ get isEmpty(): boolean;
59
+ /**
60
+ * Bounds are the union of the band's visible tile-layer-node bounds. With no
61
+ * visible children the band collapses to a degenerate rect at its transformed
62
+ * origin (rather than the whole-subtree-plus-origin union a bare Container
63
+ * would report).
64
+ */
65
+ updateBounds(): this;
66
+ /**
67
+ * Adopt a freshly created layer node into this band (used by
68
+ * {@link TileMapView.refreshLayers} when a newly added map layer joins this
69
+ * band). Appended; call {@link TileMapBand._reorder} afterwards to restore
70
+ * map document order.
71
+ * @internal
72
+ */
73
+ _adopt(node: TileLayerNode): void;
74
+ /**
75
+ * Drop a layer node from this band's membership without destroying it (used
76
+ * when its map layer was removed; the view destroys the node separately).
77
+ * @internal
78
+ */
79
+ _release(node: TileLayerNode): void;
80
+ /**
81
+ * Reorder this band's membership and its still-parented children to map
82
+ * document order. `documentIndexById` maps a layer id to its index in the
83
+ * map's layer list. Layer nodes the caller has reparented away from this band
84
+ * keep their membership (for ownership) but are not re-adopted.
85
+ * @internal
86
+ */
87
+ _reorder(documentIndexById: ReadonlyMap<number, number>): void;
88
+ /**
89
+ * Destroy the band: detach it from its application parent, then destroy the
90
+ * tile-layer nodes it owns (freeing their chunk geometry). Application actors,
91
+ * sibling bands, the map, layers, and tileset textures are untouched.
92
+ *
93
+ * Idempotent: a second call (e.g. when {@link TileMapView.destroy} runs after
94
+ * the application already destroyed this band directly) is a safe no-op.
95
+ */
96
+ destroy(): void;
97
+ }
@@ -0,0 +1,161 @@
1
+ import { Container } from '@codexo/exojs';
2
+
3
+ /**
4
+ * A named, ordered group of {@link TileLayerNode}s produced by a
5
+ * {@link TileMapView} for ergonomic actor interleaving.
6
+ *
7
+ * A band is a plain {@link Container} scene node that the application parents
8
+ * wherever it wants (typically directly under its own world root). Bands are
9
+ * composition units only: a band owns **its generated tile-layer nodes** and
10
+ * never application actors. Placing actors *between* bands is done by parenting
11
+ * the bands and the actor containers as siblings in the desired document order:
12
+ *
13
+ * ```ts
14
+ * worldRoot.addChild(view.band('ground'), actors, view.band('roof'));
15
+ * ```
16
+ *
17
+ * **Rendering order within a band always follows map document order**, not the
18
+ * order layers were listed in the band definition — band membership *selects*
19
+ * layers; it never reorders them.
20
+ *
21
+ * **Ownership:** the band owns the {@link TileLayerNode}s it was created with.
22
+ * Destroying the band destroys those layer nodes (and their cached chunk
23
+ * geometry) and detaches the band from its application parent — it never
24
+ * touches application actors, sibling bands, the {@link TileMap}, the
25
+ * {@link TileLayer}s, or Loader-owned tileset textures.
26
+ *
27
+ * Bounds are the union of the band's tile-layer-node bounds (an empty band
28
+ * collapses to a degenerate rect at the band's transformed origin).
29
+ *
30
+ * Bands are created by {@link TileMapView}; construct them through
31
+ * {@link TileMapView} rather than directly.
32
+ *
33
+ * @advanced
34
+ */
35
+ class TileMapBand extends Container {
36
+ _name;
37
+ _layerNodes;
38
+ _destroyed = false;
39
+ /**
40
+ * @param name The band name (unique within its owning view).
41
+ * @param layerNodes The layer nodes composing this band, already ordered by
42
+ * map document order. Adopted as children.
43
+ * @internal Constructed by {@link TileMapView}.
44
+ */
45
+ constructor(name, layerNodes) {
46
+ super();
47
+ this._name = name;
48
+ this._layerNodes = [...layerNodes];
49
+ for (const node of this._layerNodes) {
50
+ this.addChild(node);
51
+ }
52
+ }
53
+ /** The band name (unique within its owning {@link TileMapView}). */
54
+ get name() {
55
+ return this._name;
56
+ }
57
+ /**
58
+ * The tile-layer nodes that compose this band, in map document order. This
59
+ * is the band's **membership** list (the nodes it owns and destroys); it may
60
+ * differ from {@link Container.children} if the caller reparents an
61
+ * individual layer node elsewhere.
62
+ */
63
+ get layerNodes() {
64
+ return this._layerNodes;
65
+ }
66
+ /** Find this band's layer node rendering the layer with the given id. */
67
+ getLayerNodeById(id) {
68
+ return this._layerNodes.find(node => node.layer.id === id);
69
+ }
70
+ /** Whether this band currently owns no layer nodes. */
71
+ get isEmpty() {
72
+ return this._layerNodes.length === 0;
73
+ }
74
+ /**
75
+ * Bounds are the union of the band's visible tile-layer-node bounds. With no
76
+ * visible children the band collapses to a degenerate rect at its transformed
77
+ * origin (rather than the whole-subtree-plus-origin union a bare Container
78
+ * would report).
79
+ */
80
+ updateBounds() {
81
+ this._bounds.reset();
82
+ let hasVisibleChild = false;
83
+ for (const child of this.children) {
84
+ if (child.visible) {
85
+ this._bounds.addRect(child.getBounds());
86
+ hasVisibleChild = true;
87
+ }
88
+ }
89
+ if (!hasVisibleChild) {
90
+ this._bounds.addRect(this.getLocalBounds(), this.getGlobalTransform());
91
+ }
92
+ return this;
93
+ }
94
+ /**
95
+ * Adopt a freshly created layer node into this band (used by
96
+ * {@link TileMapView.refreshLayers} when a newly added map layer joins this
97
+ * band). Appended; call {@link TileMapBand._reorder} afterwards to restore
98
+ * map document order.
99
+ * @internal
100
+ */
101
+ _adopt(node) {
102
+ if (!this._layerNodes.includes(node)) {
103
+ this._layerNodes.push(node);
104
+ }
105
+ this.addChild(node);
106
+ }
107
+ /**
108
+ * Drop a layer node from this band's membership without destroying it (used
109
+ * when its map layer was removed; the view destroys the node separately).
110
+ * @internal
111
+ */
112
+ _release(node) {
113
+ const index = this._layerNodes.indexOf(node);
114
+ if (index !== -1) {
115
+ this._layerNodes.splice(index, 1);
116
+ }
117
+ if (node.parent === this) {
118
+ this.removeChild(node);
119
+ }
120
+ }
121
+ /**
122
+ * Reorder this band's membership and its still-parented children to map
123
+ * document order. `documentIndexById` maps a layer id to its index in the
124
+ * map's layer list. Layer nodes the caller has reparented away from this band
125
+ * keep their membership (for ownership) but are not re-adopted.
126
+ * @internal
127
+ */
128
+ _reorder(documentIndexById) {
129
+ this._layerNodes.sort((a, b) => (documentIndexById.get(a.layer.id) ?? 0) - (documentIndexById.get(b.layer.id) ?? 0));
130
+ for (const node of this._layerNodes) {
131
+ if (node.parent === this) {
132
+ this.addChild(node);
133
+ }
134
+ }
135
+ }
136
+ /**
137
+ * Destroy the band: detach it from its application parent, then destroy the
138
+ * tile-layer nodes it owns (freeing their chunk geometry). Application actors,
139
+ * sibling bands, the map, layers, and tileset textures are untouched.
140
+ *
141
+ * Idempotent: a second call (e.g. when {@link TileMapView.destroy} runs after
142
+ * the application already destroyed this band directly) is a safe no-op.
143
+ */
144
+ destroy() {
145
+ if (this._destroyed) {
146
+ return;
147
+ }
148
+ this._destroyed = true;
149
+ this.parent?.removeChild(this);
150
+ const owned = [...this._layerNodes];
151
+ this._layerNodes.length = 0;
152
+ super.destroy();
153
+ for (const node of owned) {
154
+ node.parent?.removeChild(node);
155
+ node.destroy();
156
+ }
157
+ }
158
+ }
159
+
160
+ export { TileMapBand };
161
+ //# sourceMappingURL=TileMapBand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TileMapBand.js","sources":["../../../src/TileMapBand.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import type { Rectangle } from '@codexo/exojs';
2
+ import { Container } from '@codexo/exojs';
3
+ import type { PixelSnapMode } from '@codexo/exojs/rendering';
4
+ import { TileLayerNode } from './TileLayerNode';
5
+ import type { TileMap } from './TileMap';
6
+ /**
7
+ * Options for a {@link TileMapNode}.
8
+ * @advanced
9
+ */
10
+ export interface TileMapNodeOptions {
11
+ /**
12
+ * Whether the map's chunk nodes participate in per-chunk view culling.
13
+ * Forwarded to every {@link TileLayerNode}. Defaults to `true`.
14
+ */
15
+ readonly cullable?: boolean;
16
+ }
17
+ /**
18
+ * A convenience scene node that renders a whole {@link TileMap} as a
19
+ * {@link Container} of one {@link TileLayerNode} per tile layer, in map layer
20
+ * order (back-to-front by document order).
21
+ *
22
+ * `TileMapNode` owns **only** its layer nodes — never application actors. Use
23
+ * it for the simple, non-interleaved case (no actors between layers); for
24
+ * actor interleaving, place individual `TileLayerNode`s into your own scene
25
+ * graph instead.
26
+ *
27
+ * The node references — but never owns — the {@link TileMap}: destroying the
28
+ * node frees its layer/chunk nodes and their cached GPU geometry, while the
29
+ * `TileMap` data and Loader-owned tileset textures survive (free them via
30
+ * `TileMap.destroy()` / `Loader.destroy()` respectively).
31
+ *
32
+ * Layers added to or removed from the map after construction are reflected only
33
+ * after {@link TileMapNode.refreshLayers}.
34
+ *
35
+ * @advanced
36
+ */
37
+ export declare class TileMapNode extends Container {
38
+ private readonly _map;
39
+ private readonly _cullChunks;
40
+ private readonly _layerNodes;
41
+ private _pixelSnapMode;
42
+ constructor(map: TileMap, options?: TileMapNodeOptions);
43
+ /** The runtime map this node renders. */
44
+ get map(): TileMap;
45
+ /**
46
+ * Render-only pixel-snap mode applied to every layer (and forwarded to every
47
+ * chunk node, current and rebuilt). Snaps tile chunk origins to the active
48
+ * render target's device-pixel grid for crisp tiles; with integer tile pitch
49
+ * the grid stays exact and adjacent chunks/layers cannot drift apart. Purely
50
+ * visual — tile data, layer offsets, chunk revisions, and culling are
51
+ * unchanged. Setting the current value is a no-op; an invalid value throws and
52
+ * leaves the prior mode unchanged.
53
+ *
54
+ * @default 'none'
55
+ * @stable
56
+ */
57
+ get pixelSnapMode(): PixelSnapMode;
58
+ set pixelSnapMode(mode: PixelSnapMode);
59
+ /** The layer render nodes, in map layer order. */
60
+ get layerNodes(): readonly TileLayerNode[];
61
+ /** Find the layer node rendering the named layer, or `undefined`. */
62
+ getLayerNode(name: string): TileLayerNode | undefined;
63
+ /**
64
+ * Rebuild the layer-node children from the map's current layers. Call after
65
+ * layers are structurally added to or removed from the map.
66
+ */
67
+ refreshLayers(): this;
68
+ getLocalBounds(): Rectangle;
69
+ destroy(): void;
70
+ private _buildLayerNodes;
71
+ }
@@ -0,0 +1,113 @@
1
+ import { Container } from '@codexo/exojs';
2
+ import { assertPixelSnapMode } from './pixelSnap.js';
3
+ import { TileLayerNode } from './TileLayerNode.js';
4
+
5
+ /**
6
+ * A convenience scene node that renders a whole {@link TileMap} as a
7
+ * {@link Container} of one {@link TileLayerNode} per tile layer, in map layer
8
+ * order (back-to-front by document order).
9
+ *
10
+ * `TileMapNode` owns **only** its layer nodes — never application actors. Use
11
+ * it for the simple, non-interleaved case (no actors between layers); for
12
+ * actor interleaving, place individual `TileLayerNode`s into your own scene
13
+ * graph instead.
14
+ *
15
+ * The node references — but never owns — the {@link TileMap}: destroying the
16
+ * node frees its layer/chunk nodes and their cached GPU geometry, while the
17
+ * `TileMap` data and Loader-owned tileset textures survive (free them via
18
+ * `TileMap.destroy()` / `Loader.destroy()` respectively).
19
+ *
20
+ * Layers added to or removed from the map after construction are reflected only
21
+ * after {@link TileMapNode.refreshLayers}.
22
+ *
23
+ * @advanced
24
+ */
25
+ class TileMapNode extends Container {
26
+ _map;
27
+ _cullChunks;
28
+ _layerNodes = [];
29
+ _pixelSnapMode = 'none';
30
+ constructor(map, options) {
31
+ super();
32
+ this._map = map;
33
+ this._cullChunks = options?.cullable ?? true;
34
+ this._buildLayerNodes();
35
+ }
36
+ /** The runtime map this node renders. */
37
+ get map() {
38
+ return this._map;
39
+ }
40
+ /**
41
+ * Render-only pixel-snap mode applied to every layer (and forwarded to every
42
+ * chunk node, current and rebuilt). Snaps tile chunk origins to the active
43
+ * render target's device-pixel grid for crisp tiles; with integer tile pitch
44
+ * the grid stays exact and adjacent chunks/layers cannot drift apart. Purely
45
+ * visual — tile data, layer offsets, chunk revisions, and culling are
46
+ * unchanged. Setting the current value is a no-op; an invalid value throws and
47
+ * leaves the prior mode unchanged.
48
+ *
49
+ * @default 'none'
50
+ * @stable
51
+ */
52
+ get pixelSnapMode() {
53
+ return this._pixelSnapMode;
54
+ }
55
+ set pixelSnapMode(mode) {
56
+ if (mode === this._pixelSnapMode) {
57
+ return;
58
+ }
59
+ assertPixelSnapMode(mode);
60
+ this._pixelSnapMode = mode;
61
+ for (const node of this._layerNodes) {
62
+ node.pixelSnapMode = mode;
63
+ }
64
+ }
65
+ /** The layer render nodes, in map layer order. */
66
+ get layerNodes() {
67
+ return this._layerNodes;
68
+ }
69
+ /** Find the layer node rendering the named layer, or `undefined`. */
70
+ getLayerNode(name) {
71
+ return this._layerNodes.find(node => node.layer.name === name);
72
+ }
73
+ /**
74
+ * Rebuild the layer-node children from the map's current layers. Call after
75
+ * layers are structurally added to or removed from the map.
76
+ */
77
+ refreshLayers() {
78
+ const previous = [...this._layerNodes];
79
+ this.removeChildren();
80
+ this._layerNodes.length = 0;
81
+ for (const node of previous) {
82
+ node.destroy();
83
+ }
84
+ this._buildLayerNodes();
85
+ return this;
86
+ }
87
+ getLocalBounds() {
88
+ const bounds = super.getLocalBounds();
89
+ bounds.set(0, 0, this._map.pixelWidth, this._map.pixelHeight);
90
+ return bounds;
91
+ }
92
+ destroy() {
93
+ const layerNodes = [...this._layerNodes];
94
+ this._layerNodes.length = 0;
95
+ super.destroy();
96
+ for (const node of layerNodes) {
97
+ node.destroy();
98
+ }
99
+ }
100
+ _buildLayerNodes() {
101
+ for (const layer of this._map.layers) {
102
+ const node = new TileLayerNode(layer, { cullable: this._cullChunks });
103
+ if (this._pixelSnapMode !== 'none') {
104
+ node.pixelSnapMode = this._pixelSnapMode;
105
+ }
106
+ this._layerNodes.push(node);
107
+ this.addChild(node);
108
+ }
109
+ }
110
+ }
111
+
112
+ export { TileMapNode };
113
+ //# sourceMappingURL=TileMapNode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TileMapNode.js","sources":["../../../src/TileMapNode.ts"],"sourcesContent":[null],"names":[],"mappings":";;;;AAoBA;;;;;;;;;;;;;;;;;;;AAmBG;AACG,MAAO,WAAY,SAAQ,SAAS,CAAA;AACvB,IAAA,IAAI;AACJ,IAAA,WAAW;IACX,WAAW,GAAoB,EAAE;IAC1C,cAAc,GAAkB,MAAM;IAE9C,WAAA,CAAmB,GAAY,EAAE,OAA4B,EAAA;AAC3D,QAAA,KAAK,EAAE;AAEP,QAAA,IAAI,CAAC,IAAI,GAAG,GAAG;QACf,IAAI,CAAC,WAAW,GAAG,OAAO,EAAE,QAAQ,IAAI,IAAI;QAE5C,IAAI,CAAC,gBAAgB,EAAE;IACzB;;AAGA,IAAA,IAAW,GAAG,GAAA;QACZ,OAAO,IAAI,CAAC,IAAI;IAClB;AAEA;;;;;;;;;;;AAWG;AACH,IAAA,IAAW,aAAa,GAAA;QACtB,OAAO,IAAI,CAAC,cAAc;IAC5B;IAEA,IAAW,aAAa,CAAC,IAAmB,EAAA;AAC1C,QAAA,IAAI,IAAI,KAAK,IAAI,CAAC,cAAc,EAAE;YAChC;QACF;QAEA,mBAAmB,CAAC,IAAI,CAAC;AACzB,QAAA,IAAI,CAAC,cAAc,GAAG,IAAI;AAE1B,QAAA,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,WAAW,EAAE;AACnC,YAAA,IAAI,CAAC,aAAa,GAAG,IAAI;QAC3B;IACF;;AAGA,IAAA,IAAW,UAAU,GAAA;QACnB,OAAO,IAAI,CAAC,WAAW;IACzB;;AAGO,IAAA,YAAY,CAAC,IAAY,EAAA;AAC9B,QAAA,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;IAChE;AAEA;;;AAGG;IACI,aAAa,GAAA;QAClB,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC;QAEtC,IAAI,CAAC,cAAc,EAAE;AACrB,QAAA,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;AAE3B,QAAA,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE;YAC3B,IAAI,CAAC,OAAO,EAAE;QAChB;QAEA,IAAI,CAAC,gBAAgB,EAAE;AAEvB,QAAA,OAAO,IAAI;IACb;IAEgB,cAAc,GAAA;AAC5B,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,cAAc,EAAE;AAErC,QAAA,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC;AAE7D,QAAA,OAAO,MAAM;IACf;IAEgB,OAAO,GAAA;QACrB,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC;AAExC,QAAA,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;QAE3B,KAAK,CAAC,OAAO,EAAE;AAEf,QAAA,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE;YAC7B,IAAI,CAAC,OAAO,EAAE;QAChB;IACF;IAEQ,gBAAgB,GAAA;QACtB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;AACpC,YAAA,MAAM,IAAI,GAAG,IAAI,aAAa,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC;AAErE,YAAA,IAAI,IAAI,CAAC,cAAc,KAAK,MAAM,EAAE;AAClC,gBAAA,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,cAAc;YAC1C;AAEA,YAAA,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;AAC3B,YAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QACrB;IACF;AACD;;;;"}
@@ -0,0 +1,182 @@
1
+ import type { PixelSnapMode } from '@codexo/exojs/rendering';
2
+ import { TileLayerNode } from './TileLayerNode';
3
+ import type { TileMap } from './TileMap';
4
+ import { TileMapBand } from './TileMapBand';
5
+ /**
6
+ * A layer selector inside a {@link TileMapBandDefinition}: either a stable
7
+ * layer **id** (`number`) or a **unique** layer **name** (`string`). Names that
8
+ * are shared by more than one layer are rejected as ambiguous — reference such
9
+ * layers by id.
10
+ * @advanced
11
+ */
12
+ export type TileLayerSelector = number | string;
13
+ /**
14
+ * The layers composing one band, by id or unique name, in any order. Rendering
15
+ * order always follows map document order regardless of the order listed here —
16
+ * a definition *selects* layers, it never reorders them. The band's name is the
17
+ * key it is registered under in {@link TileMapViewOptions.bands}.
18
+ * @advanced
19
+ */
20
+ export type TileMapBandDefinition = readonly TileLayerSelector[];
21
+ /**
22
+ * Options for a {@link TileMapView}.
23
+ * @advanced
24
+ */
25
+ export interface TileMapViewOptions {
26
+ /**
27
+ * Named bands, keyed by band name (keys are unique, so duplicate band names
28
+ * cannot occur). Each value lists the layers that compose the band. Layers
29
+ * not listed in any band remain reachable through
30
+ * {@link TileMapView.getLayerNodeById} and friends and are owned directly by
31
+ * the view (no implicit fallback band). The definition is copied and frozen;
32
+ * mutating the caller's object afterwards does not change band assignments.
33
+ *
34
+ * @throws (at construction) on an unknown layer id, an unknown or ambiguous
35
+ * layer name, a layer listed twice within one band, or a layer assigned to
36
+ * more than one band.
37
+ */
38
+ readonly bands?: Readonly<Record<string, TileMapBandDefinition>>;
39
+ /**
40
+ * Whether the generated {@link TileLayerNode}s' chunks participate in
41
+ * per-chunk view culling. Forwarded to every layer node. Default `true`.
42
+ */
43
+ readonly cullable?: boolean;
44
+ }
45
+ /**
46
+ * Groups a {@link TileMap}'s layers into independently placeable scene nodes so
47
+ * application actors can be interleaved **between** tile layers.
48
+ *
49
+ * A view produces exactly one canonical {@link TileLayerNode} per map layer
50
+ * (stable identity, map document order) and, optionally, named {@link TileMapBand}s
51
+ * grouping subsets of those nodes. The application parents the bands / layer
52
+ * nodes wherever it wants — typically as siblings of its own actor containers:
53
+ *
54
+ * ```ts
55
+ * const view = map.createView({ bands: { ground: ['background', 'ground'], roof: ['roofs'] } });
56
+ * worldRoot.addChild(view.band('ground'), actors, view.band('roof'));
57
+ * // or without bands:
58
+ * worldRoot.addChild(view.getLayerNodeById(groundId)!, actors, view.getLayerNodeById(roofId)!);
59
+ * ```
60
+ *
61
+ * **Actors are application-owned siblings.** A `TileMapView` never adopts or
62
+ * destroys actors. The view is a helper, not a scene node — it does not own the
63
+ * world container, the {@link TileMap}, the {@link TileLayer}s, or tileset
64
+ * textures.
65
+ *
66
+ * **Ownership:** the view owns its generated layer nodes and bands. A layer
67
+ * assigned to a band is owned by that band; an unbanded layer is owned by the
68
+ * view directly. {@link TileMapView.destroy} destroys every band and layer node
69
+ * (detaching them from their application parents) but leaves actors, the map,
70
+ * its layers, and Loader-owned textures untouched. There is no map-replacement
71
+ * mutation API: to swap maps, destroy the old view, construct a new one, and
72
+ * re-parent its bands — the actor tree is never involved.
73
+ *
74
+ * @advanced
75
+ */
76
+ export declare class TileMapView {
77
+ private readonly _map;
78
+ private readonly _cullable;
79
+ /** All canonical layer nodes, in map document order. */
80
+ private readonly _layerNodes;
81
+ /** Layer id → its canonical layer node. */
82
+ private readonly _layerNodeById;
83
+ /** Bands in definition (insertion) order. */
84
+ private readonly _bands;
85
+ /** Band name → band. */
86
+ private readonly _bandByName;
87
+ /** Original band definitions (frozen) for re-resolution on refresh. */
88
+ private readonly _bandDefs;
89
+ /** Layer node → its owning band (absent = view-owned / unbanded). */
90
+ private readonly _nodeBand;
91
+ /** Unbanded layer nodes owned directly by the view, in map document order. */
92
+ private readonly _directLayerNodes;
93
+ private _destroyed;
94
+ private _pixelSnapMode;
95
+ /**
96
+ * @param map The runtime map to compose. Referenced, never owned.
97
+ * @param options Band definitions and culling.
98
+ * @throws When a band definition references an unknown/ambiguous layer or
99
+ * assigns a layer twice / to multiple bands.
100
+ */
101
+ constructor(map: TileMap, options?: TileMapViewOptions);
102
+ /** The runtime map this view composes. Referenced, never owned. */
103
+ get map(): TileMap;
104
+ /** All canonical layer nodes, one per map layer, in map document order. */
105
+ get layers(): readonly TileLayerNode[];
106
+ /** The bands, in definition (insertion) order. */
107
+ get bands(): readonly TileMapBand[];
108
+ /** Whether this view has been destroyed. */
109
+ get destroyed(): boolean;
110
+ /**
111
+ * Render-only pixel-snap mode applied to every layer node this view owns (and
112
+ * forwarded to every chunk drawable, current and rebuilt by
113
+ * {@link refreshLayers}). Snaps tile chunk origins to the active render
114
+ * target's device-pixel grid for crisp tiles; with integer tile pitch the grid
115
+ * stays exact and adjacent chunks cannot drift apart. Purely visual — tile
116
+ * data, layer offsets, chunk revisions, and culling are unchanged. Setting the
117
+ * current value is a no-op; an invalid value throws and leaves the prior mode
118
+ * unchanged.
119
+ *
120
+ * @default 'none'
121
+ * @stable
122
+ */
123
+ get pixelSnapMode(): PixelSnapMode;
124
+ set pixelSnapMode(mode: PixelSnapMode);
125
+ /**
126
+ * The canonical layer node for the layer with the given **id**, or
127
+ * `undefined`. Ids are authoritative and unique — this is the unambiguous
128
+ * lookup. The returned node may be reparented into the caller's own
129
+ * containers; the view still tracks it for refresh and destruction.
130
+ */
131
+ getLayerNodeById(id: number): TileLayerNode | undefined;
132
+ /**
133
+ * Every canonical layer node whose layer has the given **name**, in map
134
+ * document order. Layer names are not guaranteed unique, so this returns an
135
+ * array (empty when no layer matches). Prefer {@link getLayerNodeById} when
136
+ * you have the id.
137
+ */
138
+ getLayerNodesByName(name: string): readonly TileLayerNode[];
139
+ /**
140
+ * The band registered under `name`.
141
+ * @throws If no band with that name was defined.
142
+ */
143
+ band(name: string): TileMapBand;
144
+ /** Whether a band with the given name was defined. */
145
+ hasBand(name: string): boolean;
146
+ /**
147
+ * Rebuild the view after **structural** map changes (layers added to or
148
+ * removed from the map). Ordinary tile edits and chunk creation/removal do
149
+ * NOT need this — those are handled by chunk revisions and
150
+ * {@link TileLayerNode.refresh} respectively.
151
+ *
152
+ * - Removed layers: their generated layer node is detached and destroyed.
153
+ * - Unchanged layers: keep their existing layer node (stable identity).
154
+ * - Added layers: a new layer node is created and assigned to the first band
155
+ * whose definition selects it (by id, or by a currently-unambiguous name),
156
+ * otherwise owned directly by the view.
157
+ * - Every band's children are re-ordered to map document order.
158
+ *
159
+ * Application actors are never touched, and bands keep their placement in the
160
+ * application scene graph.
161
+ *
162
+ * @throws If the view has been destroyed.
163
+ */
164
+ refreshLayers(): this;
165
+ /**
166
+ * Destroy the view: every band and generated layer node is destroyed (and
167
+ * detached from its application parent), freeing cached chunk geometry.
168
+ * Idempotent. Application actors, sibling content, the {@link TileMap}, its
169
+ * {@link TileLayer}s, and Loader-owned tileset textures all survive.
170
+ */
171
+ destroy(): void;
172
+ /** Resolve one band definition into a {@link TileMapBand} and record it. */
173
+ private _defineBand;
174
+ /** Resolve a single selector to a layer id, throwing on unknown/ambiguous. */
175
+ private _resolveSelector;
176
+ /** Assign a freshly created node to the first band that selects its layer. */
177
+ private _assignNewNode;
178
+ /** Whether a band definition selects the given layer (unambiguously by name). */
179
+ private _definitionSelects;
180
+ /** Detach + destroy a node, dropping it from band membership and registries. */
181
+ private _removeNode;
182
+ }