@codexo/exojs-tilemap 0.15.3 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +33 -26
  2. package/dist/esm/ChunkSource.d.ts +33 -0
  3. package/dist/esm/ChunkSource.d.ts.map +1 -0
  4. package/dist/esm/ChunkStreamer.d.ts +98 -0
  5. package/dist/esm/ChunkStreamer.d.ts.map +1 -0
  6. package/dist/esm/ChunkStreamer.js +201 -0
  7. package/dist/esm/ChunkStreamer.js.map +1 -0
  8. package/dist/esm/ImageLayer.d.ts +11 -3
  9. package/dist/esm/ImageLayer.d.ts.map +1 -0
  10. package/dist/esm/ImageLayer.js +71 -64
  11. package/dist/esm/ImageLayer.js.map +1 -1
  12. package/dist/esm/ImageLayerNode.d.ts +89 -0
  13. package/dist/esm/ImageLayerNode.d.ts.map +1 -0
  14. package/dist/esm/ImageLayerNode.js +205 -0
  15. package/dist/esm/ImageLayerNode.js.map +1 -0
  16. package/dist/esm/MapObject.d.ts +70 -0
  17. package/dist/esm/MapObject.d.ts.map +1 -0
  18. package/dist/esm/MapObject.js +34 -0
  19. package/dist/esm/MapObject.js.map +1 -0
  20. package/dist/esm/MapObjectSpawner.d.ts +133 -0
  21. package/dist/esm/MapObjectSpawner.d.ts.map +1 -0
  22. package/dist/esm/MapObjectSpawner.js +147 -0
  23. package/dist/esm/MapObjectSpawner.js.map +1 -0
  24. package/dist/esm/MapSpawnSession.d.ts +53 -0
  25. package/dist/esm/MapSpawnSession.d.ts.map +1 -0
  26. package/dist/esm/MapSpawnSession.js +88 -0
  27. package/dist/esm/MapSpawnSession.js.map +1 -0
  28. package/dist/esm/MapWorld.d.ts +134 -0
  29. package/dist/esm/MapWorld.d.ts.map +1 -0
  30. package/dist/esm/MapWorld.js +150 -0
  31. package/dist/esm/MapWorld.js.map +1 -0
  32. package/dist/esm/MapWorldRuntime.d.ts +182 -0
  33. package/dist/esm/MapWorldRuntime.d.ts.map +1 -0
  34. package/dist/esm/MapWorldRuntime.js +261 -0
  35. package/dist/esm/MapWorldRuntime.js.map +1 -0
  36. package/dist/esm/ObjectLayer.d.ts +93 -7
  37. package/dist/esm/ObjectLayer.d.ts.map +1 -0
  38. package/dist/esm/ObjectLayer.js +184 -167
  39. package/dist/esm/ObjectLayer.js.map +1 -1
  40. package/dist/esm/SampledChunkSource.d.ts +43 -0
  41. package/dist/esm/SampledChunkSource.d.ts.map +1 -0
  42. package/dist/esm/SampledChunkSource.js +48 -0
  43. package/dist/esm/SampledChunkSource.js.map +1 -0
  44. package/dist/esm/TileAnimator.d.ts +12 -2
  45. package/dist/esm/TileAnimator.d.ts.map +1 -0
  46. package/dist/esm/TileAnimator.js +154 -165
  47. package/dist/esm/TileAnimator.js.map +1 -1
  48. package/dist/esm/TileChunk.d.ts +30 -5
  49. package/dist/esm/TileChunk.d.ts.map +1 -0
  50. package/dist/esm/TileChunk.js +190 -188
  51. package/dist/esm/TileChunk.js.map +1 -1
  52. package/dist/esm/TileChunkNode.d.ts +18 -3
  53. package/dist/esm/TileChunkNode.d.ts.map +1 -0
  54. package/dist/esm/TileChunkNode.js +104 -85
  55. package/dist/esm/TileChunkNode.js.map +1 -1
  56. package/dist/esm/TileLayer.d.ts +104 -24
  57. package/dist/esm/TileLayer.d.ts.map +1 -0
  58. package/dist/esm/TileLayer.js +511 -469
  59. package/dist/esm/TileLayer.js.map +1 -1
  60. package/dist/esm/TileLayerNode.d.ts +52 -11
  61. package/dist/esm/TileLayerNode.d.ts.map +1 -0
  62. package/dist/esm/TileLayerNode.js +224 -166
  63. package/dist/esm/TileLayerNode.js.map +1 -1
  64. package/dist/esm/TileMap.d.ts +96 -31
  65. package/dist/esm/TileMap.d.ts.map +1 -0
  66. package/dist/esm/TileMap.js +401 -357
  67. package/dist/esm/TileMap.js.map +1 -1
  68. package/dist/esm/TileMapBand.d.ts +42 -29
  69. package/dist/esm/TileMapBand.d.ts.map +1 -0
  70. package/dist/esm/TileMapBand.js +151 -158
  71. package/dist/esm/TileMapBand.js.map +1 -1
  72. package/dist/esm/TileMapNode.d.ts +34 -18
  73. package/dist/esm/TileMapNode.d.ts.map +1 -0
  74. package/dist/esm/TileMapNode.js +122 -109
  75. package/dist/esm/TileMapNode.js.map +1 -1
  76. package/dist/esm/TileMapView.d.ts +108 -47
  77. package/dist/esm/TileMapView.d.ts.map +1 -0
  78. package/dist/esm/TileMapView.js +385 -338
  79. package/dist/esm/TileMapView.js.map +1 -1
  80. package/dist/esm/TileSet.d.ts +15 -6
  81. package/dist/esm/TileSet.d.ts.map +1 -0
  82. package/dist/esm/TileSet.js +161 -181
  83. package/dist/esm/TileSet.js.map +1 -1
  84. package/dist/esm/WangSet.d.ts +5 -4
  85. package/dist/esm/WangSet.d.ts.map +1 -0
  86. package/dist/esm/WangSet.js +70 -78
  87. package/dist/esm/WangSet.js.map +1 -1
  88. package/dist/esm/WorkerSampledChunkSource.d.ts +95 -0
  89. package/dist/esm/WorkerSampledChunkSource.d.ts.map +1 -0
  90. package/dist/esm/WorkerSampledChunkSource.js +117 -0
  91. package/dist/esm/WorkerSampledChunkSource.js.map +1 -0
  92. package/dist/esm/autoTile.d.ts +10 -5
  93. package/dist/esm/autoTile.d.ts.map +1 -0
  94. package/dist/esm/autoTile.js +164 -205
  95. package/dist/esm/autoTile.js.map +1 -1
  96. package/dist/esm/chunkGeometry.d.ts +8 -7
  97. package/dist/esm/chunkGeometry.d.ts.map +1 -0
  98. package/dist/esm/chunkGeometry.js +83 -94
  99. package/dist/esm/chunkGeometry.js.map +1 -1
  100. package/dist/esm/index.d.ts +1 -0
  101. package/dist/esm/index.d.ts.map +1 -0
  102. package/dist/esm/index.js +26 -15
  103. package/dist/esm/nodeBounds.d.ts +20 -0
  104. package/dist/esm/nodeBounds.d.ts.map +1 -0
  105. package/dist/esm/nodeBounds.js +40 -0
  106. package/dist/esm/nodeBounds.js.map +1 -0
  107. package/dist/esm/pixelSnap.d.ts +4 -3
  108. package/dist/esm/pixelSnap.d.ts.map +1 -0
  109. package/dist/esm/pixelSnap.js +18 -16
  110. package/dist/esm/pixelSnap.js.map +1 -1
  111. package/dist/esm/public.d.ts +23 -3
  112. package/dist/esm/public.d.ts.map +1 -0
  113. package/dist/esm/tileCollision.d.ts +190 -0
  114. package/dist/esm/tileCollision.d.ts.map +1 -0
  115. package/dist/esm/tileCollision.js +367 -0
  116. package/dist/esm/tileCollision.js.map +1 -0
  117. package/dist/esm/tileWord.d.ts +12 -0
  118. package/dist/esm/tileWord.d.ts.map +1 -0
  119. package/dist/esm/tileWord.js +16 -0
  120. package/dist/esm/tileWord.js.map +1 -0
  121. package/dist/esm/tilemapExtension.d.ts +4 -3
  122. package/dist/esm/tilemapExtension.d.ts.map +1 -0
  123. package/dist/esm/tilemapExtension.js +47 -47
  124. package/dist/esm/tilemapExtension.js.map +1 -1
  125. package/dist/esm/tilemapSerializers.d.ts +5 -4
  126. package/dist/esm/tilemapSerializers.d.ts.map +1 -0
  127. package/dist/esm/tilemapSerializers.js +48 -41
  128. package/dist/esm/tilemapSerializers.js.map +1 -1
  129. package/dist/esm/types.d.ts +34 -18
  130. package/dist/esm/types.d.ts.map +1 -0
  131. package/dist/esm/types.js +111 -120
  132. package/dist/esm/types.js.map +1 -1
  133. package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts +50 -2
  134. package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts.map +1 -0
  135. package/dist/esm/webgl2/WebGl2TileChunkRenderer.js +366 -332
  136. package/dist/esm/webgl2/WebGl2TileChunkRenderer.js.map +1 -1
  137. package/dist/esm/webgl2/shaders/tile-chunk.frag.js +6 -0
  138. package/dist/esm/webgl2/shaders/tile-chunk.frag.js.map +1 -0
  139. package/dist/esm/webgl2/shaders/tile-chunk.vert.js +6 -0
  140. package/dist/esm/webgl2/shaders/tile-chunk.vert.js.map +1 -0
  141. package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts +70 -4
  142. package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts.map +1 -0
  143. package/dist/esm/webgpu/WebGpuTileChunkRenderer.js +496 -383
  144. package/dist/esm/webgpu/WebGpuTileChunkRenderer.js.map +1 -1
  145. package/dist/esm/webgpu/shaders/tile-chunk.wgsl.js +6 -0
  146. package/dist/esm/webgpu/shaders/tile-chunk.wgsl.js.map +1 -0
  147. package/package.json +8 -15
  148. package/dist/esm/index.js.map +0 -1
  149. package/dist/esm/register.d.ts +0 -1
  150. package/dist/esm/register.js +0 -25
  151. package/dist/esm/register.js.map +0 -1
package/README.md CHANGED
@@ -11,9 +11,10 @@ parse their format and hand this runtime fully-resolved tiles.
11
11
  npm install @codexo/exojs @codexo/exojs-tilemap
12
12
  ```
13
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.
14
+ `@codexo/exojs` is a peer dependency, so install it explicitly. This package is itself a peer of
15
+ the format adapters (`@codexo/exojs-tiled`, `@codexo/exojs-ldtk`) rather than something they pull
16
+ in transitively — install it whether you use an adapter or build procedural / custom-format maps
17
+ directly.
17
18
 
18
19
  ## What this package provides
19
20
 
@@ -26,6 +27,23 @@ custom-format maps.
26
27
  - `ResolvedTile` / `TileTransform` — value-typed tile references with `flipX` / `flipY` /
27
28
  `diagonal` orientation.
28
29
 
30
+ **Worlds and level lifetime** — format-neutral, driven by whichever adapter loaded the data:
31
+
32
+ - `MapWorld` / `MapLevel` — where levels sit: stable id, world bounds, neighbour graph,
33
+ externality. Metadata only; no tiles, no textures.
34
+ - `MapWorldRuntime` — explicit `loadLevel` / `unloadLevel`, one child `LoaderScope` per level,
35
+ at most one live level per id. No camera policy: the game decides what to load.
36
+ - `MapLevelRuntime` — one loaded level's map, scope and spawn session, with a single
37
+ `destroy()` that tears all three down in a safe order.
38
+
39
+ **Map object spawning**:
40
+
41
+ - `MapObjectSpawner` — a local (never global) dispatch table from object class to a factory.
42
+ Deterministic order, atomic rollback, `AbortSignal` cancellation.
43
+ - `MapObjectDescriptor` — the format-neutral view of one authored object handed to a factory.
44
+ - `MapSpawnSession` — owns what one spawn produced; constant-time lookup by stable source id,
45
+ reverse-order teardown.
46
+
29
47
  **Scene / rendering (`@advanced`)**:
30
48
 
31
49
  - `TileMapNode` — convenience root that renders a whole `TileMap`.
@@ -39,20 +57,13 @@ custom-format maps.
39
57
  ## Usage — procedural map
40
58
 
41
59
  ```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';
60
+ import { Application, TextureRegion } from '@codexo/exojs';
61
+ import { TileLayer, TileMap, TileMapNode, TileSet, tilemapExtension, TILE_TRANSFORM_IDENTITY } from '@codexo/exojs-tilemap';
51
62
 
52
63
  const app = new Application({ extensions: [tilemapExtension] /* canvas, … */ });
53
64
 
54
65
  // Tileset over a Loader-owned atlas texture (the runtime never destroys it).
55
- const atlas = await app.loader.load(Texture, 'tiles.png');
66
+ const atlas = await app.loader.load('tiles.png');
56
67
  const terrain = new TileSet({
57
68
  name: 'terrain',
58
69
  texture: new TextureRegion(atlas, { x: 0, y: 0, width: atlas.width, height: atlas.height }),
@@ -83,13 +94,13 @@ const map = new TileMap({
83
94
  });
84
95
 
85
96
  // Render the whole map.
86
- app.scene.root.addChild(new TileMapNode(map));
97
+ app.scenes.root.addChild(new TileMapNode(map));
87
98
  ```
88
99
 
89
100
  ### Interleaving actors between layers — `TileMapView`
90
101
 
91
102
  `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
103
+ nothing renders between layers. To draw application actors _between_ tile layers, create a
93
104
  `TileMapView`: it generates one `TileLayerNode` per map layer (stable identity, map document
94
105
  order) and groups them into named `TileMapBand`s that you parent yourself, as siblings of your
95
106
  own actor containers:
@@ -104,7 +115,7 @@ const view = map.createView({
104
115
 
105
116
  worldRoot.addChild(
106
117
  view.band('ground'),
107
- actors, // app-owned actor container — drawn between ground and roof
118
+ actors, // app-owned actor container — drawn between ground and roof
108
119
  view.band('roof'),
109
120
  );
110
121
  ```
@@ -130,18 +141,11 @@ Actors are application-owned siblings. `TileMapView` never adopts or destroys ac
130
141
  - After layers are structurally added to or removed from the map, call `view.refreshLayers()`:
131
142
  unchanged layer nodes keep their identity and bands keep their placement in your scene graph.
132
143
 
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
144
  ## Renderer model
141
145
 
142
146
  - **Chunk-first.** A `TileLayerNode` is a container of per-chunk `TileChunkNode` drawables (one
143
147
  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
148
+ issues one instanced draw per batch — draw calls scale with _visible chunks × layers_, not
145
149
  total tile count.
146
150
  - **Revision-cached geometry.** Each chunk's quad geometry is built once and cached against the
147
151
  source chunk's `revision`. Unchanged chunks never rebuild; a camera pan rebuilds nothing —
@@ -161,6 +165,9 @@ import '@codexo/exojs-tilemap/register';
161
165
  ## Ownership & lifecycle
162
166
 
163
167
  - Tileset **textures are Loader-owned**. The runtime and renderer never destroy them.
168
+ - A `MapWorldRuntime` owns the scope it creates under the one you give it, plus one child scope
169
+ per loaded level - never the scope you passed in. A `MapLevelRuntime` owns the `TileMap` its
170
+ provider returned, its spawn session, and its scope; `destroy()` releases them in that order.
164
171
  - `TileMapNode` / `TileMapView` / `TileMapBand` / `TileLayerNode` reference — but never own —
165
172
  the `TileMap`. Destroying a map node, view, or band frees only the tile nodes it generated
166
173
  (detaching them from their application parents) and their cached geometry; application actors,
@@ -174,8 +181,8 @@ import '@codexo/exojs-tilemap/register';
174
181
  ## Core compatibility
175
182
 
176
183
  | `@codexo/exojs-tilemap` | `@codexo/exojs` |
177
- |---|---|
178
- | 0.x | matching `0.x` |
184
+ | ----------------------- | --------------- |
185
+ | 0.x | matching `0.x` |
179
186
 
180
187
  ## Links
181
188
 
@@ -0,0 +1,33 @@
1
+ /**
2
+ * A chunk payload ready to install into a {@link import('./TileLayer').TileLayer}
3
+ * via {@link import('./TileLayer').TileLayer._adoptChunk} - the same shape
4
+ * {@link import('./TileChunk').TileChunk}'s constructor accepts as its
5
+ * `source` parameter, named and exported so provider authors have a type to
6
+ * import instead of matching an inline shape.
7
+ * @advanced
8
+ */
9
+ export interface ChunkPayload {
10
+ readonly width: number;
11
+ readonly height: number;
12
+ readonly tiles: Uint32Array;
13
+ }
14
+ /**
15
+ * Supplies chunk data for one {@link import('./TileLayer').TileLayer} on
16
+ * demand, keyed by signed chunk coordinates - the abstraction a
17
+ * {@link import('./ChunkStreamer').ChunkStreamer} drives to implement chunk
18
+ * streaming, and the seam a format adapter or a procedural generator
19
+ * implements.
20
+ *
21
+ * `getChunk` may return synchronously (e.g. slicing already-parsed source
22
+ * data) or a `Promise` (e.g. an expensive procedural-generation algorithm,
23
+ * plausibly off the main thread) - {@link import('./ChunkStreamer').ChunkStreamer}
24
+ * installs a synchronous result in the same tick it was requested, with
25
+ * zero pop-in. Returning `null` (synchronously or via a resolved `Promise`)
26
+ * means "no data for this coordinate" - e.g. outside a finite provider's
27
+ * authored extent, even though the layer itself may be unbounded.
28
+ * @advanced
29
+ */
30
+ export interface ChunkSource {
31
+ getChunk(cx: number, cy: number): ChunkPayload | null | Promise<ChunkPayload | null>;
32
+ }
33
+ //# sourceMappingURL=ChunkSource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChunkSource.d.ts","sourceRoot":"","sources":["../../src/ChunkSource.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;CACtF"}
@@ -0,0 +1,98 @@
1
+ import { type View } from '@codexo/exojs';
2
+ import type { ChunkSource } from './ChunkSource';
3
+ import type { TileLayer } from './TileLayer';
4
+ /**
5
+ * Options for a {@link ChunkStreamer}. All optional.
6
+ * @advanced
7
+ */
8
+ export interface ChunkStreamerOptions {
9
+ /**
10
+ * Chunks within this many chunk-units (Chebyshev distance to the visible
11
+ * chunk range) of the view are loaded. Default `1`.
12
+ */
13
+ readonly loadRadius?: number;
14
+ /**
15
+ * Own-resident chunks beyond this many chunk-units of the view are
16
+ * evicted. Must be `>= loadRadius` - the gap between the two is
17
+ * deliberate hysteresis that prevents load/unload thrashing when the
18
+ * view sits near a chunk boundary. Default `2`.
19
+ */
20
+ readonly unloadRadius?: number;
21
+ /**
22
+ * Maximum number of new chunk loads issued per {@link ChunkStreamer.update}
23
+ * call, after the very first call (which loads the entire initial wanted
24
+ * set unbudgeted, so the starting view never pops in). Default `8`.
25
+ */
26
+ readonly maxChunkLoadsPerFrame?: number;
27
+ }
28
+ /**
29
+ * Drives {@link TileLayer._adoptChunk}/{@link TileLayer._evictChunk} calls
30
+ * from a {@link View}'s position: chunks near the view are requested from a
31
+ * {@link ChunkSource} and installed; chunks that scroll far enough away are
32
+ * evicted. Works on both unbounded and bounded layers (a bounded-but-large
33
+ * layer still benefits from not keeping every chunk resident at once) - for
34
+ * a bounded layer, the wanted range is clamped to {@link TileLayer.chunkRange}.
35
+ *
36
+ * Touches only the {@link TileLayer} data layer -
37
+ * {@link import('./TileLayerNode').TileLayerNode} reacts to
38
+ * `_adoptChunk`/`_evictChunk` on its own via a structural listener, so this
39
+ * class has no rendering dependency and no reference to any scene node.
40
+ *
41
+ * Tracks its own resident set (chunks *this instance* has loaded), never
42
+ * touching chunks that predate its attachment or were installed by another
43
+ * source.
44
+ *
45
+ * Tick it from your update loop, like {@link import('./TileAnimator').TileAnimator}:
46
+ *
47
+ * ```ts
48
+ * const streamer = new ChunkStreamer(layer, mySource, view);
49
+ * scene.systems.add({ update: () => streamer.update() });
50
+ * ```
51
+ *
52
+ * @advanced
53
+ */
54
+ export declare class ChunkStreamer {
55
+ private readonly _layer;
56
+ private readonly _source;
57
+ private readonly _view;
58
+ private readonly _loadRadius;
59
+ private readonly _unloadRadius;
60
+ private readonly _maxChunkLoadsPerFrame;
61
+ private readonly _resident;
62
+ private readonly _inFlight;
63
+ private _requestCounter;
64
+ private _primed;
65
+ private _destroyed;
66
+ constructor(layer: TileLayer, source: ChunkSource, view: View, options?: ChunkStreamerOptions);
67
+ /** Number of chunks this instance currently has resident in the layer. */
68
+ get residentCount(): number;
69
+ /**
70
+ * Recompute the wanted chunk set from the view's current position; evict
71
+ * own-resident chunks that fell outside `unloadRadius`, then request
72
+ * chunks newly inside `loadRadius`. The very first call loads the entire
73
+ * initial wanted set unbudgeted; every later call is capped at
74
+ * {@link ChunkStreamerOptions.maxChunkLoadsPerFrame}. No-op once
75
+ * {@link destroy}ed.
76
+ */
77
+ update(): void;
78
+ /**
79
+ * Evict every chunk this instance has loaded and stop responding to
80
+ * {@link update}. Does not affect chunks predating attachment or
81
+ * installed by another source. Idempotent.
82
+ */
83
+ destroy(): void;
84
+ private _computeCoreRange;
85
+ private _rangeDistance;
86
+ private _unload;
87
+ private _request;
88
+ /**
89
+ * Ignore-on-resolve: only the `_destroyed` and in-flight-token checks
90
+ * guard installation - a chunk that scrolled out of the wanted set while
91
+ * its request was in flight is still installed if it resolves (installing
92
+ * it is cheap and self-correcting: the next {@link update} call's unload
93
+ * pass evicts it again if it's genuinely no longer wanted). There is no
94
+ * edit-persistence to protect, so discarding is never destructive.
95
+ */
96
+ private _onResolved;
97
+ }
98
+ //# sourceMappingURL=ChunkStreamer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChunkStreamer.d.ts","sourceRoot":"","sources":["../../src/ChunkStreamer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,aAAa,CAAC;AAGzD;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CACzC;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAO;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAS;IAEhD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmE;IAC7F,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;gBAER,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,oBAAoB;IAapG,0EAA0E;IAC1E,IAAW,aAAa,IAAI,MAAM,CAEjC;IAED;;;;;;;OAOG;IACI,MAAM,IAAI,IAAI;IA4CrB;;;;OAIG;IACI,OAAO,IAAI,IAAI;IAWtB,OAAO,CAAC,iBAAiB;IA6BzB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,QAAQ;IAqChB;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;CAUpB"}
@@ -0,0 +1,201 @@
1
+ import { tileToChunkCoord } from "./types.js";
2
+ import { logger } from "@codexo/exojs";
3
+
4
+ //#region src/ChunkStreamer.ts
5
+ const DEFAULT_LOAD_RADIUS = 1;
6
+ const DEFAULT_UNLOAD_RADIUS = 2;
7
+ const DEFAULT_MAX_CHUNK_LOADS_PER_FRAME = 8;
8
+ const chunkKey = (cx, cy) => `${cx},${cy}`;
9
+ const isThenable = (value) => value !== null && typeof value === "object" && typeof value.then === "function";
10
+ /**
11
+ * Drives {@link TileLayer._adoptChunk}/{@link TileLayer._evictChunk} calls
12
+ * from a {@link View}'s position: chunks near the view are requested from a
13
+ * {@link ChunkSource} and installed; chunks that scroll far enough away are
14
+ * evicted. Works on both unbounded and bounded layers (a bounded-but-large
15
+ * layer still benefits from not keeping every chunk resident at once) - for
16
+ * a bounded layer, the wanted range is clamped to {@link TileLayer.chunkRange}.
17
+ *
18
+ * Touches only the {@link TileLayer} data layer -
19
+ * {@link import('./TileLayerNode').TileLayerNode} reacts to
20
+ * `_adoptChunk`/`_evictChunk` on its own via a structural listener, so this
21
+ * class has no rendering dependency and no reference to any scene node.
22
+ *
23
+ * Tracks its own resident set (chunks *this instance* has loaded), never
24
+ * touching chunks that predate its attachment or were installed by another
25
+ * source.
26
+ *
27
+ * Tick it from your update loop, like {@link import('./TileAnimator').TileAnimator}:
28
+ *
29
+ * ```ts
30
+ * const streamer = new ChunkStreamer(layer, mySource, view);
31
+ * scene.systems.add({ update: () => streamer.update() });
32
+ * ```
33
+ *
34
+ * @advanced
35
+ */
36
+ var ChunkStreamer = class {
37
+ _layer;
38
+ _source;
39
+ _view;
40
+ _loadRadius;
41
+ _unloadRadius;
42
+ _maxChunkLoadsPerFrame;
43
+ _resident = /* @__PURE__ */ new Map();
44
+ _inFlight = /* @__PURE__ */ new Map();
45
+ _requestCounter = 0;
46
+ _primed = false;
47
+ _destroyed = false;
48
+ constructor(layer, source, view, options) {
49
+ this._layer = layer;
50
+ this._source = source;
51
+ this._view = view;
52
+ this._loadRadius = options?.loadRadius ?? DEFAULT_LOAD_RADIUS;
53
+ this._unloadRadius = options?.unloadRadius ?? DEFAULT_UNLOAD_RADIUS;
54
+ this._maxChunkLoadsPerFrame = options?.maxChunkLoadsPerFrame ?? DEFAULT_MAX_CHUNK_LOADS_PER_FRAME;
55
+ if (this._unloadRadius < this._loadRadius) throw new Error(`ChunkStreamer unloadRadius (${this._unloadRadius}) must be >= loadRadius (${this._loadRadius}).`);
56
+ }
57
+ /** Number of chunks this instance currently has resident in the layer. */
58
+ get residentCount() {
59
+ return this._resident.size;
60
+ }
61
+ /**
62
+ * Recompute the wanted chunk set from the view's current position; evict
63
+ * own-resident chunks that fell outside `unloadRadius`, then request
64
+ * chunks newly inside `loadRadius`. The very first call loads the entire
65
+ * initial wanted set unbudgeted; every later call is capped at
66
+ * {@link ChunkStreamerOptions.maxChunkLoadsPerFrame}. No-op once
67
+ * {@link destroy}ed.
68
+ */
69
+ update() {
70
+ if (this._destroyed) return;
71
+ const core = this._computeCoreRange();
72
+ this._unload(core);
73
+ const range = this._layer.chunkRange();
74
+ let minCx = core.minCx - this._loadRadius;
75
+ let minCy = core.minCy - this._loadRadius;
76
+ let maxCx = core.maxCx + this._loadRadius;
77
+ let maxCy = core.maxCy + this._loadRadius;
78
+ if (range !== null) {
79
+ minCx = Math.max(minCx, range.minCx);
80
+ minCy = Math.max(minCy, range.minCy);
81
+ maxCx = Math.min(maxCx, range.maxCx);
82
+ maxCy = Math.min(maxCy, range.maxCy);
83
+ }
84
+ const toLoad = [];
85
+ for (let cy = minCy; cy <= maxCy; cy++) for (let cx = minCx; cx <= maxCx; cx++) {
86
+ const key = chunkKey(cx, cy);
87
+ if (this._resident.has(key) || this._inFlight.has(key)) continue;
88
+ toLoad.push({
89
+ cx,
90
+ cy
91
+ });
92
+ }
93
+ if (!this._primed) {
94
+ this._primed = true;
95
+ for (const { cx, cy } of toLoad) this._request(cx, cy);
96
+ return;
97
+ }
98
+ toLoad.sort((a, b) => this._rangeDistance(a.cx, a.cy, core) - this._rangeDistance(b.cx, b.cy, core));
99
+ const budget = Math.min(this._maxChunkLoadsPerFrame, toLoad.length);
100
+ for (const { cx, cy } of toLoad.slice(0, budget)) this._request(cx, cy);
101
+ }
102
+ /**
103
+ * Evict every chunk this instance has loaded and stop responding to
104
+ * {@link update}. Does not affect chunks predating attachment or
105
+ * installed by another source. Idempotent.
106
+ */
107
+ destroy() {
108
+ if (this._destroyed) return;
109
+ this._destroyed = true;
110
+ for (const { cx, cy } of this._resident.values()) this._layer._evictChunk(cx, cy);
111
+ this._resident.clear();
112
+ this._inFlight.clear();
113
+ }
114
+ _computeCoreRange() {
115
+ const layer = this._layer;
116
+ const view = this._view;
117
+ const bounds = view.getBounds();
118
+ const centerX = view.center.x;
119
+ const centerY = view.center.y;
120
+ const shiftX = centerX * (1 - layer.parallaxX);
121
+ const shiftY = centerY * (1 - layer.parallaxY);
122
+ const scale = layer.parallaxScale;
123
+ const layerLeft = layer.offsetX + (bounds.left - shiftX - layer.offsetX) / scale;
124
+ const layerTop = layer.offsetY + (bounds.top - shiftY - layer.offsetY) / scale;
125
+ const layerRight = layer.offsetX + (bounds.right - shiftX - layer.offsetX) / scale;
126
+ const layerBottom = layer.offsetY + (bounds.bottom - shiftY - layer.offsetY) / scale;
127
+ const topLeftTile = layer.pixelToTile(layerLeft, layerTop);
128
+ const bottomRightTile = layer.pixelToTile(layerRight, layerBottom);
129
+ const topLeftChunk = tileToChunkCoord(topLeftTile.tx, topLeftTile.ty, layer.chunkWidth, layer.chunkHeight);
130
+ const bottomRightChunk = tileToChunkCoord(bottomRightTile.tx, bottomRightTile.ty, layer.chunkWidth, layer.chunkHeight);
131
+ return {
132
+ minCx: topLeftChunk.cx,
133
+ minCy: topLeftChunk.cy,
134
+ maxCx: bottomRightChunk.cx,
135
+ maxCy: bottomRightChunk.cy
136
+ };
137
+ }
138
+ _rangeDistance(cx, cy, range) {
139
+ const dx = Math.max(range.minCx - cx, 0, cx - range.maxCx);
140
+ const dy = Math.max(range.minCy - cy, 0, cy - range.maxCy);
141
+ return Math.max(dx, dy);
142
+ }
143
+ _unload(core) {
144
+ for (const [key, { cx, cy }] of this._resident) if (this._rangeDistance(cx, cy, core) > this._unloadRadius) {
145
+ this._layer._evictChunk(cx, cy);
146
+ this._resident.delete(key);
147
+ }
148
+ }
149
+ _request(cx, cy) {
150
+ const key = chunkKey(cx, cy);
151
+ const token = ++this._requestCounter;
152
+ this._inFlight.set(key, token);
153
+ let result;
154
+ try {
155
+ result = this._source.getChunk(cx, cy);
156
+ } catch (error) {
157
+ this._inFlight.delete(key);
158
+ logger.warn(`ChunkStreamer: source.getChunk(${cx}, ${cy}) threw synchronously.`, {
159
+ source: "ChunkStreamer",
160
+ error: error instanceof Error ? error : new Error(String(error))
161
+ });
162
+ return;
163
+ }
164
+ if (isThenable(result)) {
165
+ result.then((payload) => {
166
+ this._onResolved(key, cx, cy, token, payload);
167
+ }, (error) => {
168
+ this._inFlight.delete(key);
169
+ logger.warn(`ChunkStreamer: source.getChunk(${cx}, ${cy}) rejected.`, {
170
+ source: "ChunkStreamer",
171
+ error: error instanceof Error ? error : new Error(String(error))
172
+ });
173
+ });
174
+ return;
175
+ }
176
+ this._onResolved(key, cx, cy, token, result);
177
+ }
178
+ /**
179
+ * Ignore-on-resolve: only the `_destroyed` and in-flight-token checks
180
+ * guard installation - a chunk that scrolled out of the wanted set while
181
+ * its request was in flight is still installed if it resolves (installing
182
+ * it is cheap and self-correcting: the next {@link update} call's unload
183
+ * pass evicts it again if it's genuinely no longer wanted). There is no
184
+ * edit-persistence to protect, so discarding is never destructive.
185
+ */
186
+ _onResolved(key, cx, cy, token, payload) {
187
+ if (this._destroyed) return;
188
+ if (this._inFlight.get(key) !== token) return;
189
+ this._inFlight.delete(key);
190
+ if (payload === null) return;
191
+ this._layer._adoptChunk(cx, cy, payload);
192
+ this._resident.set(key, {
193
+ cx,
194
+ cy
195
+ });
196
+ }
197
+ };
198
+
199
+ //#endregion
200
+ export { ChunkStreamer };
201
+ //# sourceMappingURL=ChunkStreamer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChunkStreamer.js","names":[],"sources":["../../src/ChunkStreamer.ts"],"sourcesContent":["import { logger, type View } from '@codexo/exojs';\n\nimport type { ChunkPayload, ChunkSource } from './ChunkSource';\nimport type { ChunkRange, TileLayer } from './TileLayer';\nimport { tileToChunkCoord } from './types';\n\n/**\n * Options for a {@link ChunkStreamer}. All optional.\n * @advanced\n */\nexport interface ChunkStreamerOptions {\n /**\n * Chunks within this many chunk-units (Chebyshev distance to the visible\n * chunk range) of the view are loaded. Default `1`.\n */\n readonly loadRadius?: number;\n /**\n * Own-resident chunks beyond this many chunk-units of the view are\n * evicted. Must be `>= loadRadius` - the gap between the two is\n * deliberate hysteresis that prevents load/unload thrashing when the\n * view sits near a chunk boundary. Default `2`.\n */\n readonly unloadRadius?: number;\n /**\n * Maximum number of new chunk loads issued per {@link ChunkStreamer.update}\n * call, after the very first call (which loads the entire initial wanted\n * set unbudgeted, so the starting view never pops in). Default `8`.\n */\n readonly maxChunkLoadsPerFrame?: number;\n}\n\nconst DEFAULT_LOAD_RADIUS = 1;\nconst DEFAULT_UNLOAD_RADIUS = 2;\nconst DEFAULT_MAX_CHUNK_LOADS_PER_FRAME = 8;\n\nconst chunkKey = (cx: number, cy: number): string => `${cx},${cy}`;\n\nconst isThenable = (value: unknown): value is Promise<ChunkPayload | null> =>\n value !== null && typeof value === 'object' && typeof (value as { then?: unknown }).then === 'function';\n\n/**\n * Drives {@link TileLayer._adoptChunk}/{@link TileLayer._evictChunk} calls\n * from a {@link View}'s position: chunks near the view are requested from a\n * {@link ChunkSource} and installed; chunks that scroll far enough away are\n * evicted. Works on both unbounded and bounded layers (a bounded-but-large\n * layer still benefits from not keeping every chunk resident at once) - for\n * a bounded layer, the wanted range is clamped to {@link TileLayer.chunkRange}.\n *\n * Touches only the {@link TileLayer} data layer -\n * {@link import('./TileLayerNode').TileLayerNode} reacts to\n * `_adoptChunk`/`_evictChunk` on its own via a structural listener, so this\n * class has no rendering dependency and no reference to any scene node.\n *\n * Tracks its own resident set (chunks *this instance* has loaded), never\n * touching chunks that predate its attachment or were installed by another\n * source.\n *\n * Tick it from your update loop, like {@link import('./TileAnimator').TileAnimator}:\n *\n * ```ts\n * const streamer = new ChunkStreamer(layer, mySource, view);\n * scene.systems.add({ update: () => streamer.update() });\n * ```\n *\n * @advanced\n */\nexport class ChunkStreamer {\n private readonly _layer: TileLayer;\n private readonly _source: ChunkSource;\n private readonly _view: View;\n private readonly _loadRadius: number;\n private readonly _unloadRadius: number;\n private readonly _maxChunkLoadsPerFrame: number;\n\n private readonly _resident = new Map<string, { readonly cx: number; readonly cy: number }>();\n private readonly _inFlight = new Map<string, number>();\n private _requestCounter = 0;\n private _primed = false;\n private _destroyed = false;\n\n public constructor(layer: TileLayer, source: ChunkSource, view: View, options?: ChunkStreamerOptions) {\n this._layer = layer;\n this._source = source;\n this._view = view;\n this._loadRadius = options?.loadRadius ?? DEFAULT_LOAD_RADIUS;\n this._unloadRadius = options?.unloadRadius ?? DEFAULT_UNLOAD_RADIUS;\n this._maxChunkLoadsPerFrame = options?.maxChunkLoadsPerFrame ?? DEFAULT_MAX_CHUNK_LOADS_PER_FRAME;\n\n if (this._unloadRadius < this._loadRadius) {\n throw new Error(`ChunkStreamer unloadRadius (${this._unloadRadius}) must be >= loadRadius (${this._loadRadius}).`);\n }\n }\n\n /** Number of chunks this instance currently has resident in the layer. */\n public get residentCount(): number {\n return this._resident.size;\n }\n\n /**\n * Recompute the wanted chunk set from the view's current position; evict\n * own-resident chunks that fell outside `unloadRadius`, then request\n * chunks newly inside `loadRadius`. The very first call loads the entire\n * initial wanted set unbudgeted; every later call is capped at\n * {@link ChunkStreamerOptions.maxChunkLoadsPerFrame}. No-op once\n * {@link destroy}ed.\n */\n public update(): void {\n if (this._destroyed) return;\n\n const core = this._computeCoreRange();\n\n this._unload(core);\n\n const range = this._layer.chunkRange();\n let minCx = core.minCx - this._loadRadius;\n let minCy = core.minCy - this._loadRadius;\n let maxCx = core.maxCx + this._loadRadius;\n let maxCy = core.maxCy + this._loadRadius;\n if (range !== null) {\n minCx = Math.max(minCx, range.minCx);\n minCy = Math.max(minCy, range.minCy);\n maxCx = Math.min(maxCx, range.maxCx);\n maxCy = Math.min(maxCy, range.maxCy);\n }\n\n const toLoad: Array<{ cx: number; cy: number }> = [];\n for (let cy = minCy; cy <= maxCy; cy++) {\n for (let cx = minCx; cx <= maxCx; cx++) {\n const key = chunkKey(cx, cy);\n if (this._resident.has(key) || this._inFlight.has(key)) continue;\n toLoad.push({ cx, cy });\n }\n }\n\n if (!this._primed) {\n this._primed = true;\n for (const { cx, cy } of toLoad) {\n this._request(cx, cy);\n }\n return;\n }\n\n toLoad.sort((a, b) => this._rangeDistance(a.cx, a.cy, core) - this._rangeDistance(b.cx, b.cy, core));\n\n const budget = Math.min(this._maxChunkLoadsPerFrame, toLoad.length);\n for (const { cx, cy } of toLoad.slice(0, budget)) {\n this._request(cx, cy);\n }\n }\n\n /**\n * Evict every chunk this instance has loaded and stop responding to\n * {@link update}. Does not affect chunks predating attachment or\n * installed by another source. Idempotent.\n */\n public destroy(): void {\n if (this._destroyed) return;\n this._destroyed = true;\n\n for (const { cx, cy } of this._resident.values()) {\n this._layer._evictChunk(cx, cy);\n }\n this._resident.clear();\n this._inFlight.clear();\n }\n\n private _computeCoreRange(): ChunkRange {\n const layer = this._layer;\n const view = this._view;\n const bounds = view.getBounds();\n const centerX = view.center.x;\n const centerY = view.center.y;\n\n const shiftX = centerX * (1 - layer.parallaxX);\n const shiftY = centerY * (1 - layer.parallaxY);\n const scale = layer.parallaxScale;\n\n const layerLeft = layer.offsetX + (bounds.left - shiftX - layer.offsetX) / scale;\n const layerTop = layer.offsetY + (bounds.top - shiftY - layer.offsetY) / scale;\n const layerRight = layer.offsetX + (bounds.right - shiftX - layer.offsetX) / scale;\n const layerBottom = layer.offsetY + (bounds.bottom - shiftY - layer.offsetY) / scale;\n const topLeftTile = layer.pixelToTile(layerLeft, layerTop);\n const bottomRightTile = layer.pixelToTile(layerRight, layerBottom);\n\n const topLeftChunk = tileToChunkCoord(topLeftTile.tx, topLeftTile.ty, layer.chunkWidth, layer.chunkHeight);\n const bottomRightChunk = tileToChunkCoord(bottomRightTile.tx, bottomRightTile.ty, layer.chunkWidth, layer.chunkHeight);\n\n return {\n minCx: topLeftChunk.cx,\n minCy: topLeftChunk.cy,\n maxCx: bottomRightChunk.cx,\n maxCy: bottomRightChunk.cy,\n };\n }\n\n private _rangeDistance(cx: number, cy: number, range: ChunkRange): number {\n const dx = Math.max(range.minCx - cx, 0, cx - range.maxCx);\n const dy = Math.max(range.minCy - cy, 0, cy - range.maxCy);\n return Math.max(dx, dy);\n }\n\n private _unload(core: ChunkRange): void {\n for (const [key, { cx, cy }] of this._resident) {\n if (this._rangeDistance(cx, cy, core) > this._unloadRadius) {\n this._layer._evictChunk(cx, cy);\n this._resident.delete(key);\n }\n }\n }\n\n private _request(cx: number, cy: number): void {\n const key = chunkKey(cx, cy);\n const token = ++this._requestCounter;\n this._inFlight.set(key, token);\n\n let result: ChunkPayload | null | Promise<ChunkPayload | null>;\n try {\n result = this._source.getChunk(cx, cy);\n } catch (error) {\n this._inFlight.delete(key);\n logger.warn(`ChunkStreamer: source.getChunk(${cx}, ${cy}) threw synchronously.`, {\n source: 'ChunkStreamer',\n error: error instanceof Error ? error : new Error(String(error)),\n });\n\n return;\n }\n\n if (isThenable(result)) {\n result.then(\n payload => {\n this._onResolved(key, cx, cy, token, payload);\n },\n (error: unknown) => {\n this._inFlight.delete(key);\n logger.warn(`ChunkStreamer: source.getChunk(${cx}, ${cy}) rejected.`, {\n source: 'ChunkStreamer',\n error: error instanceof Error ? error : new Error(String(error)),\n });\n },\n );\n return;\n }\n\n this._onResolved(key, cx, cy, token, result);\n }\n\n /**\n * Ignore-on-resolve: only the `_destroyed` and in-flight-token checks\n * guard installation - a chunk that scrolled out of the wanted set while\n * its request was in flight is still installed if it resolves (installing\n * it is cheap and self-correcting: the next {@link update} call's unload\n * pass evicts it again if it's genuinely no longer wanted). There is no\n * edit-persistence to protect, so discarding is never destructive.\n */\n private _onResolved(key: string, cx: number, cy: number, token: number, payload: ChunkPayload | null): void {\n if (this._destroyed) return;\n if (this._inFlight.get(key) !== token) return;\n this._inFlight.delete(key);\n\n if (payload === null) return;\n\n this._layer._adoptChunk(cx, cy, payload);\n this._resident.set(key, { cx, cy });\n }\n}\n"],"mappings":"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"}
@@ -24,6 +24,8 @@ export interface ImageLayerOptions {
24
24
  readonly parallaxX?: number;
25
25
  /** Vertical parallax factor. Default `1`. */
26
26
  readonly parallaxY?: number;
27
+ /** Uniform parallax scale around the layer origin. Default `1`. */
28
+ readonly parallaxScale?: number;
27
29
  /** Tint colour as `0xRRGGBB`, or `null`. Default `null`. */
28
30
  readonly tintColor?: number | null;
29
31
  /** Whether the image repeats horizontally. Default `false`. */
@@ -35,9 +37,12 @@ export interface ImageLayerOptions {
35
37
  }
36
38
  /**
37
39
  * A data-only image layer: a single image (texture + resolved URL) placed as a
38
- * background or foreground layer. Image layers are not rendered by the tile
39
- * renderer they are exposed as data for a renderer or follow-up scene node to
40
- * consume.
40
+ * background or foreground layer. The layer itself holds no scene-graph state -
41
+ * it is rendered by an {@link import('./ImageLayerNode').ImageLayerNode}, which
42
+ * {@link import('./TileMapNode').TileMapNode} and
43
+ * {@link import('./TileMapView').TileMapView} generate and interleave with tile
44
+ * layers by the map's combined document order
45
+ * ({@link import('./TileMap').TileMap.renderableLayers}).
41
46
  *
42
47
  * Parallax, opacity, tint, offset, and repeat flags are carried from the
43
48
  * source Tiled map.
@@ -69,6 +74,8 @@ export declare class ImageLayer {
69
74
  readonly parallaxX: number;
70
75
  /** Vertical parallax factor. */
71
76
  readonly parallaxY: number;
77
+ /** Uniform scale applied around the layer origin during parallax rendering. */
78
+ readonly parallaxScale: number;
72
79
  /** Tint colour as `0xRRGGBB`, or `null`. */
73
80
  readonly tintColor: number | null;
74
81
  /** Whether the image repeats horizontally. */
@@ -79,3 +86,4 @@ export declare class ImageLayer {
79
86
  readonly properties: TileProperties;
80
87
  constructor(options: ImageLayerOptions);
81
88
  }
89
+ //# sourceMappingURL=ImageLayer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageLayer.d.ts","sourceRoot":"","sources":["../../src/ImageLayer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kBAAkB;IAClB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,oDAAoD;IACpD,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,+CAA+C;IAC/C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,6CAA6C;IAC7C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAC;CACtC;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,UAAU;IACrB,+BAA+B;IAC/B,SAAgB,IAAI,EAAG,OAAO,CAAU;IAExC,wCAAwC;IACxC,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,kBAAkB;IAClB,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,0BAA0B;IAC1B,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,uCAAuC;IACvC,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,8DAA8D;IAC9D,SAAgB,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxC,oCAAoC;IACpC,SAAgB,OAAO,EAAE,OAAO,CAAC;IACjC,iCAAiC;IACjC,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,4BAA4B;IAC5B,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,4BAA4B;IAC5B,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,kCAAkC;IAClC,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,gCAAgC;IAChC,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,+EAA+E;IAC/E,SAAgB,aAAa,EAAE,MAAM,CAAC;IACtC,4CAA4C;IAC5C,SAAgB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,8CAA8C;IAC9C,SAAgB,OAAO,EAAE,OAAO,CAAC;IACjC,4CAA4C;IAC5C,SAAgB,OAAO,EAAE,OAAO,CAAC;IACjC,kCAAkC;IAClC,SAAgB,UAAU,EAAE,cAAc,CAAC;gBAExB,OAAO,EAAE,iBAAiB;CAqB9C"}