@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Codexo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # @codexo/exojs-tilemap
2
+
3
+ Generic, format-independent tilemap runtime **and** WebGL2/WebGPU chunk renderer for
4
+ [ExoJS](https://exojs.dev). No Tiled (or any other on-disk format) vocabulary leaks into this
5
+ package — adapters such as [`@codexo/exojs-tiled`](https://www.npmjs.com/package/@codexo/exojs-tiled)
6
+ parse their format and hand this runtime fully-resolved tiles.
7
+
8
+ ## Installation
9
+
10
+ ```sh
11
+ npm install @codexo/exojs @codexo/exojs-tilemap
12
+ ```
13
+
14
+ `@codexo/exojs` is a peer dependency. Most users load Tiled maps and get this package
15
+ transitively through `@codexo/exojs-tiled` — install it directly only for procedural or
16
+ custom-format maps.
17
+
18
+ ## What this package provides
19
+
20
+ **Runtime (data model)** — pure data, no scene graph:
21
+
22
+ - `TileMap` — a finite, chunk-first map: dimensions, tile size, `tilesets`, ordered `layers`.
23
+ - `TileLayer` — one tile layer; packed `Uint32Array` chunk storage, `visible` / `opacity` /
24
+ `offsetX/Y`, queries, and revision tracking.
25
+ - `TileSet` — a resolved tileset grid over a Core `TextureRegion` (Loader-owned texture).
26
+ - `ResolvedTile` / `TileTransform` — value-typed tile references with `flipX` / `flipY` /
27
+ `diagonal` orientation.
28
+
29
+ **Scene / rendering (`@advanced`)**:
30
+
31
+ - `TileMapNode` — convenience root that renders a whole `TileMap`.
32
+ - `TileLayerNode` — renders one `TileLayer`; generated per layer by `TileMapView` (or construct
33
+ directly).
34
+ - `TileMapView` — groups a map's layers into independently placeable layer nodes and named
35
+ `TileMapBand`s for actor interleaving (a helper, not a scene node).
36
+ - `TileMapBand` — a `Container` of tile-layer nodes produced by `TileMapView`.
37
+ - `tilemapExtension` — extension descriptor carrying the WebGL2/WebGPU renderer bindings.
38
+
39
+ ## Usage — procedural map
40
+
41
+ ```ts
42
+ import { Application, Texture, TextureRegion } from '@codexo/exojs';
43
+ import {
44
+ TileLayer,
45
+ TileMap,
46
+ TileMapNode,
47
+ TileSet,
48
+ tilemapExtension,
49
+ TILE_TRANSFORM_IDENTITY,
50
+ } from '@codexo/exojs-tilemap';
51
+
52
+ const app = new Application({ extensions: [tilemapExtension] /* canvas, … */ });
53
+
54
+ // Tileset over a Loader-owned atlas texture (the runtime never destroys it).
55
+ const atlas = await app.loader.load(Texture, 'tiles.png');
56
+ const terrain = new TileSet({
57
+ name: 'terrain',
58
+ texture: new TextureRegion(atlas, { x: 0, y: 0, width: atlas.width, height: atlas.height }),
59
+ tileWidth: 16,
60
+ tileHeight: 16,
61
+ tileCount: 256,
62
+ });
63
+
64
+ const ground = new TileLayer({
65
+ id: 1,
66
+ name: 'ground',
67
+ width: 64,
68
+ height: 64,
69
+ tileWidth: 16,
70
+ tileHeight: 16,
71
+ tilesets: [terrain],
72
+ });
73
+ ground.setTileAt(0, 0, { tileset: terrain, localTileId: 5, transform: TILE_TRANSFORM_IDENTITY });
74
+
75
+ const map = new TileMap({
76
+ name: 'world',
77
+ width: 64,
78
+ height: 64,
79
+ tileWidth: 16,
80
+ tileHeight: 16,
81
+ tilesets: [terrain],
82
+ layers: [ground],
83
+ });
84
+
85
+ // Render the whole map.
86
+ app.scene.root.addChild(new TileMapNode(map));
87
+ ```
88
+
89
+ ### Interleaving actors between layers — `TileMapView`
90
+
91
+ `TileMapNode` owns **only** the map's layer nodes and renders them back-to-front — use it when
92
+ nothing renders between layers. To draw application actors *between* tile layers, create a
93
+ `TileMapView`: it generates one `TileLayerNode` per map layer (stable identity, map document
94
+ order) and groups them into named `TileMapBand`s that you parent yourself, as siblings of your
95
+ own actor containers:
96
+
97
+ ```ts
98
+ const view = map.createView({
99
+ bands: {
100
+ ground: ['background', 'ground'],
101
+ roof: ['roofs', 'foreground'],
102
+ },
103
+ });
104
+
105
+ worldRoot.addChild(
106
+ view.band('ground'),
107
+ actors, // app-owned actor container — drawn between ground and roof
108
+ view.band('roof'),
109
+ );
110
+ ```
111
+
112
+ Or, without bands, place the generated per-layer nodes directly:
113
+
114
+ ```ts
115
+ const view = map.createView();
116
+
117
+ worldRoot.addChild(view.getLayerNodeById(groundId)!, actors, view.getLayerNodeById(roofId)!);
118
+ ```
119
+
120
+ Actors are application-owned siblings. `TileMapView` never adopts or destroys actors.
121
+
122
+ - A band definition **selects** layers (by id, or by unique layer name); rendering order within
123
+ a band always follows map document order — definitions never reorder layers.
124
+ - Layers not listed in any band stay reachable via `view.getLayerNodeById(id)` /
125
+ `view.getLayerNodesByName(name)`; they are not auto-added to any band.
126
+ - Destroying a view or a band destroys only the tile nodes it generated — never actors, the
127
+ `TileMap`, its `TileLayer`s, or tileset textures.
128
+ - There is no map-replacement API: to swap maps, destroy the old view and create a new view from
129
+ the new map — the actor tree is untouched.
130
+ - After layers are structurally added to or removed from the map, call `view.refreshLayers()`:
131
+ unchanged layer nodes keep their identity and bands keep their placement in your scene graph.
132
+
133
+ ## `/register` convenience entry
134
+
135
+ ```ts
136
+ // Side effect: registers tilemapExtension in the global ExtensionRegistry.
137
+ import '@codexo/exojs-tilemap/register';
138
+ ```
139
+
140
+ ## Renderer model
141
+
142
+ - **Chunk-first.** A `TileLayerNode` is a container of per-chunk `TileChunkNode` drawables (one
143
+ per non-empty loaded chunk). The renderer batches tiles by `(shader, tileset texture)` and
144
+ issues one instanced draw per batch — draw calls scale with *visible chunks × layers*, not
145
+ total tile count.
146
+ - **Revision-cached geometry.** Each chunk's quad geometry is built once and cached against the
147
+ source chunk's `revision`. Unchanged chunks never rebuild; a camera pan rebuilds nothing —
148
+ off-screen chunks are culled by their local bounds before any geometry is touched.
149
+ - **Orientation.** `flipX` / `flipY` / `diagonal` are baked into the chunk geometry / resolved in
150
+ the shader (all 8 combinations), with no per-tile matrix or per-frame cost.
151
+ - **Multiple tilesets** with differing tile sizes are first-class; tiles taller than the map grid
152
+ are bottom-left aligned (Tiled orthogonal convention).
153
+ - **WebGL2 and WebGPU** share one CPU geometry builder and produce identical output (golden
154
+ parity tested on both backends).
155
+ - **Sampling.** Tile UVs are exact (no half-texel inset), which assumes **nearest** atlas
156
+ filtering — the typical pixel-art case. Under linear or mipmap filtering, author tilesets with
157
+ extruded tile margins to avoid neighbour bleed at tile edges (extrusion-aware tilemap UV
158
+ insetting is a planned follow-up; the `NineSlice` / `RepeatingSprite` geometry paths already
159
+ inset).
160
+
161
+ ## Ownership & lifecycle
162
+
163
+ - Tileset **textures are Loader-owned**. The runtime and renderer never destroy them.
164
+ - `TileMapNode` / `TileMapView` / `TileMapBand` / `TileLayerNode` reference — but never own —
165
+ the `TileMap`. Destroying a map node, view, or band frees only the tile nodes it generated
166
+ (detaching them from their application parents) and their cached geometry; application actors,
167
+ the `TileMap` data, its `TileLayer`s, and textures all survive. Free those via
168
+ `TileMap.destroy()` and `Loader.destroy()` respectively.
169
+ - A `TileMapNode` or `TileMapView` reflects the layer set at construction time. After layers are
170
+ structurally added to or removed from the map call `node.refreshLayers()` /
171
+ `view.refreshLayers()`; after tiles are written into previously-empty chunks call
172
+ `layerNode.refresh()`. In-place edits to existing chunks are picked up automatically.
173
+
174
+ ## Core compatibility
175
+
176
+ | `@codexo/exojs-tilemap` | `@codexo/exojs` |
177
+ |---|---|
178
+ | 0.x | matching `0.x` |
179
+
180
+ ## Links
181
+
182
+ - [API reference](https://exojs.dev/api/exojs-tilemap)
183
+ - [`@codexo/exojs-tiled`](https://www.npmjs.com/package/@codexo/exojs-tiled) — load Tiled `.tmj` maps into this runtime
184
+
185
+ ## License
186
+
187
+ MIT
@@ -0,0 +1,136 @@
1
+ import type { PackedTile } from './types';
2
+ /**
3
+ * Public readonly view of a tile chunk.
4
+ *
5
+ * Provides read-only inspection access for iteration, queries, and the
6
+ * future renderer. The underlying storage is never exposed directly.
7
+ *
8
+ * @advanced
9
+ */
10
+ export interface ReadonlyTileChunk {
11
+ /** Signed chunk X coordinate. */
12
+ readonly cx: number;
13
+ /** Signed chunk Y coordinate. */
14
+ readonly cy: number;
15
+ /** Width of this chunk in tiles. */
16
+ readonly width: number;
17
+ /** Height of this chunk in tiles. */
18
+ readonly height: number;
19
+ /** Whether every cell is empty (all-zero). */
20
+ readonly empty: boolean;
21
+ /**
22
+ * Monotonic revision counter. Increments on every cell mutation
23
+ * within this chunk. No-ops (writing same value) do NOT increment.
24
+ */
25
+ readonly revision: number;
26
+ /**
27
+ * Read the raw packed tile word at local-in-chunk coordinates.
28
+ * Returns 0 for empty.
29
+ * @throws If coordinates are invalid.
30
+ */
31
+ getRawAt(lx: number, ly: number): PackedTile;
32
+ /**
33
+ * Create a defensive copy of the tile data. The caller owns the copy;
34
+ * mutation of the returned array cannot affect the chunk.
35
+ */
36
+ cloneTiles(): Uint32Array;
37
+ }
38
+ /**
39
+ * A compact, fixed-size chunk of tile data within a layer.
40
+ *
41
+ * Storage is a single {@link Uint32Array} of length `width * height`,
42
+ * laid out row-major. Each cell is a packed tile word (0 = empty).
43
+ * Chunk coordinates are signed to support future infinite maps.
44
+ *
45
+ * The backing array is **private** — external code cannot mutate storage
46
+ * directly. All mutation must flow through {@link TileLayer} public APIs,
47
+ * which call the package-internal `_setRawAt` method.
48
+ *
49
+ * The chunk tracks a revision counter that increments on every mutation
50
+ * so the future renderer can detect which chunks need GPU rebuilds.
51
+ *
52
+ * @advanced
53
+ */
54
+ export declare class TileChunk implements ReadonlyTileChunk {
55
+ /** Signed chunk X coordinate. */
56
+ readonly cx: number;
57
+ /** Signed chunk Y coordinate. */
58
+ readonly cy: number;
59
+ /** Width of this chunk in tiles. */
60
+ readonly width: number;
61
+ /** Height of this chunk in tiles. */
62
+ readonly height: number;
63
+ /** Packed tile storage (row-major). Private — never exposed directly. */
64
+ private readonly _tiles;
65
+ /** Whether every cell in this chunk is empty (0). Lazy-computed. */
66
+ private _empty;
67
+ /** Monotonic revision counter — incremented on every mutation. */
68
+ private _revision;
69
+ /**
70
+ * @param cx Signed chunk X coordinate (must be finite safe integer).
71
+ * @param cy Signed chunk Y coordinate (must be finite safe integer).
72
+ * @param width Chunk width in tiles (positive safe integer).
73
+ * @param height Chunk height in tiles (positive safe integer).
74
+ * @param source Optional source data to copy (must match length).
75
+ */
76
+ constructor(cx: number, cy: number, width: number, height: number, source?: Uint32Array);
77
+ /** Row-major index for cell (lx, ly) within this chunk. */
78
+ private _index;
79
+ /**
80
+ * Validate local-in-chunk coordinates before access.
81
+ * @throws If lx or ly is not a finite integer or out of [0, dimension).
82
+ */
83
+ private _validateLocalCoord;
84
+ /** @inheritdoc */
85
+ getRawAt(lx: number, ly: number): PackedTile;
86
+ /** @inheritdoc */
87
+ get empty(): boolean;
88
+ /** @inheritdoc */
89
+ get revision(): number;
90
+ /** @inheritdoc */
91
+ cloneTiles(): Uint32Array;
92
+ /**
93
+ * Package-internal access to the underlying tile storage.
94
+ * Returns the actual mutable `Uint32Array` — DO NOT USE publicly.
95
+ *
96
+ * Provided for the future tilemap renderer (same package) so it can
97
+ * read chunk data efficiently without a full `cloneTiles()` copy per frame.
98
+ *
99
+ * External consumers and other packages MUST NOT call this.
100
+ * @internal
101
+ */
102
+ _getRawStorage(): Uint32Array;
103
+ /**
104
+ * Validate a packed tile value before storing.
105
+ * Accepts any finite integer — `Uint32Array` stores via unsigned 32-bit
106
+ * truncation (`ToUint32`). Negative values produced by JavaScript's
107
+ * signed bitwise ops (e.g. when the transform bits set bit 31) are valid.
108
+ * @throws If the value is NaN, Infinity, or non-integer.
109
+ * @internal
110
+ */
111
+ private _validatePacked;
112
+ /**
113
+ * Write a packed tile word at local-in-chunk coordinates.
114
+ * Returns true if the stored value actually changed.
115
+ *
116
+ * Package-internal: only {@link TileLayer} may call this.
117
+ * @internal
118
+ */
119
+ _setRawAt(lx: number, ly: number, packed: PackedTile): boolean;
120
+ /**
121
+ * Mark the chunk as needing a GPU rebuild.
122
+ * Invalidates the empty cache and increments revision.
123
+ *
124
+ * Package-internal: for future renderer use only.
125
+ * @internal
126
+ */
127
+ _markDirty(): void;
128
+ /**
129
+ * Clear every cell in this chunk. Does not re-allocate storage.
130
+ * Increments revision if any cell was non-zero.
131
+ *
132
+ * Package-internal: only {@link TileLayer} may call this.
133
+ * @internal
134
+ */
135
+ _clear(): void;
136
+ }
@@ -0,0 +1,192 @@
1
+ import { validateInteger } from './types.js';
2
+
3
+ /**
4
+ * A compact, fixed-size chunk of tile data within a layer.
5
+ *
6
+ * Storage is a single {@link Uint32Array} of length `width * height`,
7
+ * laid out row-major. Each cell is a packed tile word (0 = empty).
8
+ * Chunk coordinates are signed to support future infinite maps.
9
+ *
10
+ * The backing array is **private** — external code cannot mutate storage
11
+ * directly. All mutation must flow through {@link TileLayer} public APIs,
12
+ * which call the package-internal `_setRawAt` method.
13
+ *
14
+ * The chunk tracks a revision counter that increments on every mutation
15
+ * so the future renderer can detect which chunks need GPU rebuilds.
16
+ *
17
+ * @advanced
18
+ */
19
+ class TileChunk {
20
+ /** Signed chunk X coordinate. */
21
+ cx;
22
+ /** Signed chunk Y coordinate. */
23
+ cy;
24
+ /** Width of this chunk in tiles. */
25
+ width;
26
+ /** Height of this chunk in tiles. */
27
+ height;
28
+ /** Packed tile storage (row-major). Private — never exposed directly. */
29
+ _tiles;
30
+ /** Whether every cell in this chunk is empty (0). Lazy-computed. */
31
+ _empty = null;
32
+ /** Monotonic revision counter — incremented on every mutation. */
33
+ _revision = 0;
34
+ /**
35
+ * @param cx Signed chunk X coordinate (must be finite safe integer).
36
+ * @param cy Signed chunk Y coordinate (must be finite safe integer).
37
+ * @param width Chunk width in tiles (positive safe integer).
38
+ * @param height Chunk height in tiles (positive safe integer).
39
+ * @param source Optional source data to copy (must match length).
40
+ */
41
+ constructor(cx, cy, width, height, source) {
42
+ // Validate chunk coordinates as finite safe integers (negative OK).
43
+ if (!Number.isFinite(cx) || !Number.isInteger(cx) || !Number.isSafeInteger(cx)) {
44
+ throw new Error(`TileChunk cx must be a finite safe integer (got ${cx}).`);
45
+ }
46
+ if (!Number.isFinite(cy) || !Number.isInteger(cy) || !Number.isSafeInteger(cy)) {
47
+ throw new Error(`TileChunk cy must be a finite safe integer (got ${cy}).`);
48
+ }
49
+ if (width <= 0 || height <= 0) {
50
+ throw new Error(`TileChunk dimensions must be positive (got ${width}x${height}).`);
51
+ }
52
+ if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height)) {
53
+ throw new Error(`TileChunk dimensions must be safe integers (got ${width}x${height}).`);
54
+ }
55
+ const size = width * height;
56
+ if (!Number.isSafeInteger(size)) {
57
+ throw new Error(`TileChunk size overflow: ${width} * ${height} is not safe.`);
58
+ }
59
+ // Guard against TypedArray length limits.
60
+ if (size > 0xFFFFFFFF) {
61
+ throw new Error(`TileChunk size ${size} exceeds TypedArray maximum length.`);
62
+ }
63
+ this.cx = cx;
64
+ this.cy = cy;
65
+ this.width = width;
66
+ this.height = height;
67
+ if (source) {
68
+ if (source.length !== size) {
69
+ throw new Error(`TileChunk source length ${source.length} != ${size}.`);
70
+ }
71
+ // Defensive copy — caller mutation of source array will not affect storage.
72
+ this._tiles = new Uint32Array(source);
73
+ }
74
+ else {
75
+ this._tiles = new Uint32Array(size);
76
+ }
77
+ }
78
+ /** Row-major index for cell (lx, ly) within this chunk. */
79
+ _index(lx, ly) {
80
+ return ly * this.width + lx;
81
+ }
82
+ /**
83
+ * Validate local-in-chunk coordinates before access.
84
+ * @throws If lx or ly is not a finite integer or out of [0, dimension).
85
+ */
86
+ _validateLocalCoord(lx, ly) {
87
+ validateInteger(lx, 'lx');
88
+ validateInteger(ly, 'ly');
89
+ if (lx < 0 || lx >= this.width) {
90
+ throw new Error(`lx ${lx} out of chunk bounds [0, ${this.width - 1}].`);
91
+ }
92
+ if (ly < 0 || ly >= this.height) {
93
+ throw new Error(`ly ${ly} out of chunk bounds [0, ${this.height - 1}].`);
94
+ }
95
+ }
96
+ // ── Readonly public API (ReadonlyTileChunk) ──────────────────────────
97
+ /** @inheritdoc */
98
+ getRawAt(lx, ly) {
99
+ this._validateLocalCoord(lx, ly);
100
+ return this._tiles[this._index(lx, ly)];
101
+ }
102
+ /** @inheritdoc */
103
+ get empty() {
104
+ if (this._empty === null) {
105
+ this._empty = this._tiles.every(v => v === 0);
106
+ }
107
+ return this._empty;
108
+ }
109
+ /** @inheritdoc */
110
+ get revision() {
111
+ return this._revision;
112
+ }
113
+ /** @inheritdoc */
114
+ cloneTiles() {
115
+ return new Uint32Array(this._tiles);
116
+ }
117
+ // ── Internal mutation (package-private, NOT public API) ──────────────
118
+ /**
119
+ * Package-internal access to the underlying tile storage.
120
+ * Returns the actual mutable `Uint32Array` — DO NOT USE publicly.
121
+ *
122
+ * Provided for the future tilemap renderer (same package) so it can
123
+ * read chunk data efficiently without a full `cloneTiles()` copy per frame.
124
+ *
125
+ * External consumers and other packages MUST NOT call this.
126
+ * @internal
127
+ */
128
+ _getRawStorage() {
129
+ return this._tiles;
130
+ }
131
+ /**
132
+ * Validate a packed tile value before storing.
133
+ * Accepts any finite integer — `Uint32Array` stores via unsigned 32-bit
134
+ * truncation (`ToUint32`). Negative values produced by JavaScript's
135
+ * signed bitwise ops (e.g. when the transform bits set bit 31) are valid.
136
+ * @throws If the value is NaN, Infinity, or non-integer.
137
+ * @internal
138
+ */
139
+ _validatePacked(packed) {
140
+ if (typeof packed !== 'number' || !Number.isFinite(packed) || !Number.isInteger(packed)) {
141
+ throw new Error(`Packed tile must be a finite integer (got ${packed}).`);
142
+ }
143
+ }
144
+ /**
145
+ * Write a packed tile word at local-in-chunk coordinates.
146
+ * Returns true if the stored value actually changed.
147
+ *
148
+ * Package-internal: only {@link TileLayer} may call this.
149
+ * @internal
150
+ */
151
+ _setRawAt(lx, ly, packed) {
152
+ this._validateLocalCoord(lx, ly);
153
+ this._validatePacked(packed);
154
+ const i = this._index(lx, ly);
155
+ const prev = this._tiles[i];
156
+ if (prev === packed)
157
+ return false;
158
+ this._tiles[i] = packed;
159
+ this._revision++;
160
+ this._empty = null; // invalidate cache
161
+ return true;
162
+ }
163
+ /**
164
+ * Mark the chunk as needing a GPU rebuild.
165
+ * Invalidates the empty cache and increments revision.
166
+ *
167
+ * Package-internal: for future renderer use only.
168
+ * @internal
169
+ */
170
+ _markDirty() {
171
+ this._empty = null;
172
+ this._revision++;
173
+ }
174
+ /**
175
+ * Clear every cell in this chunk. Does not re-allocate storage.
176
+ * Increments revision if any cell was non-zero.
177
+ *
178
+ * Package-internal: only {@link TileLayer} may call this.
179
+ * @internal
180
+ */
181
+ _clear() {
182
+ const hadContent = this._tiles.some(v => v !== 0);
183
+ if (!hadContent)
184
+ return;
185
+ this._tiles.fill(0);
186
+ this._revision++;
187
+ this._empty = true;
188
+ }
189
+ }
190
+
191
+ export { TileChunk };
192
+ //# sourceMappingURL=TileChunk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TileChunk.js","sources":["../../../src/TileChunk.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import type { Rectangle } from '@codexo/exojs';
2
+ import { Drawable } from '@codexo/exojs/rendering';
3
+ import type { ChunkPage } from './chunkGeometry';
4
+ import type { ReadonlyTileChunk } from './TileChunk';
5
+ import type { TileSet } from './TileSet';
6
+ /**
7
+ * A single renderable tile chunk. One `TileChunkNode` is a {@link Drawable}
8
+ * that owns the batched quad geometry for exactly one {@link ReadonlyTileChunk}
9
+ * of one {@link import('./TileLayer').TileLayer}, positioned at the chunk's
10
+ * pixel origin within the owning {@link TileLayerNode}.
11
+ *
12
+ * Geometry is built lazily and cached against the source chunk's `revision`:
13
+ * the renderer reads {@link TileChunkNode.pages} on each visible frame, but a
14
+ * rebuild only happens when the underlying chunk actually changed. Off-screen
15
+ * chunks are culled by their accurate {@link getLocalBounds} before `render`
16
+ * is ever called, so a camera pan rebuilds nothing.
17
+ *
18
+ * The node references — but never owns — the runtime chunk, tilesets, and
19
+ * tileset textures. Destroying it releases only its cached CPU geometry; the
20
+ * `TileMap`/`TileLayer` data and Loader-owned textures are untouched.
21
+ *
22
+ * @internal Package-internal render node; the renderer is registered for this
23
+ * class. Applications use {@link TileMapNode} / {@link TileLayerNode}.
24
+ */
25
+ export declare class TileChunkNode extends Drawable {
26
+ private readonly _chunk;
27
+ private readonly _tilesets;
28
+ private readonly _tileWidth;
29
+ private readonly _tileHeight;
30
+ private readonly _pixelWidth;
31
+ private readonly _pixelHeight;
32
+ private _pages;
33
+ private _builtRevision;
34
+ /**
35
+ * @param chunk The readonly chunk this node renders.
36
+ * @param tilesets The owning layer's tileset array.
37
+ * @param tileWidth Map/layer tile cell width in pixels.
38
+ * @param tileHeight Map/layer tile cell height in pixels.
39
+ * @param chunkWidthTiles The layer's (unclamped) chunk width in tiles, used
40
+ * to compute the chunk's pixel origin. Edge chunks
41
+ * report a smaller `chunk.width`, but always start at
42
+ * `cx * chunkWidthTiles`.
43
+ * @param chunkHeightTiles The layer's (unclamped) chunk height in tiles.
44
+ */
45
+ constructor(chunk: ReadonlyTileChunk, tilesets: readonly TileSet[], tileWidth: number, tileHeight: number, chunkWidthTiles: number, chunkHeightTiles: number);
46
+ /** The signed chunk coordinates this node renders. */
47
+ get chunkX(): number;
48
+ get chunkY(): number;
49
+ /**
50
+ * The revision-cached per-tileset page geometry. Rebuilt only when the source
51
+ * chunk's `revision` advances; otherwise the same arrays are returned.
52
+ * @internal Read by the per-backend tile chunk renderer.
53
+ */
54
+ get pages(): readonly ChunkPage[];
55
+ /** `true` when the chunk has no drawable tiles (no geometry, no draw calls). */
56
+ get isEmpty(): boolean;
57
+ getLocalBounds(): Rectangle;
58
+ destroy(): void;
59
+ }