@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.
- package/README.md +33 -26
- package/dist/esm/ChunkSource.d.ts +33 -0
- package/dist/esm/ChunkSource.d.ts.map +1 -0
- package/dist/esm/ChunkStreamer.d.ts +98 -0
- package/dist/esm/ChunkStreamer.d.ts.map +1 -0
- package/dist/esm/ChunkStreamer.js +201 -0
- package/dist/esm/ChunkStreamer.js.map +1 -0
- package/dist/esm/ImageLayer.d.ts +11 -3
- package/dist/esm/ImageLayer.d.ts.map +1 -0
- package/dist/esm/ImageLayer.js +71 -64
- package/dist/esm/ImageLayer.js.map +1 -1
- package/dist/esm/ImageLayerNode.d.ts +89 -0
- package/dist/esm/ImageLayerNode.d.ts.map +1 -0
- package/dist/esm/ImageLayerNode.js +205 -0
- package/dist/esm/ImageLayerNode.js.map +1 -0
- package/dist/esm/MapObject.d.ts +70 -0
- package/dist/esm/MapObject.d.ts.map +1 -0
- package/dist/esm/MapObject.js +34 -0
- package/dist/esm/MapObject.js.map +1 -0
- package/dist/esm/MapObjectSpawner.d.ts +133 -0
- package/dist/esm/MapObjectSpawner.d.ts.map +1 -0
- package/dist/esm/MapObjectSpawner.js +147 -0
- package/dist/esm/MapObjectSpawner.js.map +1 -0
- package/dist/esm/MapSpawnSession.d.ts +53 -0
- package/dist/esm/MapSpawnSession.d.ts.map +1 -0
- package/dist/esm/MapSpawnSession.js +88 -0
- package/dist/esm/MapSpawnSession.js.map +1 -0
- package/dist/esm/MapWorld.d.ts +134 -0
- package/dist/esm/MapWorld.d.ts.map +1 -0
- package/dist/esm/MapWorld.js +150 -0
- package/dist/esm/MapWorld.js.map +1 -0
- package/dist/esm/MapWorldRuntime.d.ts +182 -0
- package/dist/esm/MapWorldRuntime.d.ts.map +1 -0
- package/dist/esm/MapWorldRuntime.js +261 -0
- package/dist/esm/MapWorldRuntime.js.map +1 -0
- package/dist/esm/ObjectLayer.d.ts +93 -7
- package/dist/esm/ObjectLayer.d.ts.map +1 -0
- package/dist/esm/ObjectLayer.js +184 -167
- package/dist/esm/ObjectLayer.js.map +1 -1
- package/dist/esm/SampledChunkSource.d.ts +43 -0
- package/dist/esm/SampledChunkSource.d.ts.map +1 -0
- package/dist/esm/SampledChunkSource.js +48 -0
- package/dist/esm/SampledChunkSource.js.map +1 -0
- package/dist/esm/TileAnimator.d.ts +12 -2
- package/dist/esm/TileAnimator.d.ts.map +1 -0
- package/dist/esm/TileAnimator.js +154 -165
- package/dist/esm/TileAnimator.js.map +1 -1
- package/dist/esm/TileChunk.d.ts +30 -5
- package/dist/esm/TileChunk.d.ts.map +1 -0
- package/dist/esm/TileChunk.js +190 -188
- package/dist/esm/TileChunk.js.map +1 -1
- package/dist/esm/TileChunkNode.d.ts +18 -3
- package/dist/esm/TileChunkNode.d.ts.map +1 -0
- package/dist/esm/TileChunkNode.js +104 -85
- package/dist/esm/TileChunkNode.js.map +1 -1
- package/dist/esm/TileLayer.d.ts +104 -24
- package/dist/esm/TileLayer.d.ts.map +1 -0
- package/dist/esm/TileLayer.js +511 -469
- package/dist/esm/TileLayer.js.map +1 -1
- package/dist/esm/TileLayerNode.d.ts +52 -11
- package/dist/esm/TileLayerNode.d.ts.map +1 -0
- package/dist/esm/TileLayerNode.js +224 -166
- package/dist/esm/TileLayerNode.js.map +1 -1
- package/dist/esm/TileMap.d.ts +96 -31
- package/dist/esm/TileMap.d.ts.map +1 -0
- package/dist/esm/TileMap.js +401 -357
- package/dist/esm/TileMap.js.map +1 -1
- package/dist/esm/TileMapBand.d.ts +42 -29
- package/dist/esm/TileMapBand.d.ts.map +1 -0
- package/dist/esm/TileMapBand.js +151 -158
- package/dist/esm/TileMapBand.js.map +1 -1
- package/dist/esm/TileMapNode.d.ts +34 -18
- package/dist/esm/TileMapNode.d.ts.map +1 -0
- package/dist/esm/TileMapNode.js +122 -109
- package/dist/esm/TileMapNode.js.map +1 -1
- package/dist/esm/TileMapView.d.ts +108 -47
- package/dist/esm/TileMapView.d.ts.map +1 -0
- package/dist/esm/TileMapView.js +385 -338
- package/dist/esm/TileMapView.js.map +1 -1
- package/dist/esm/TileSet.d.ts +15 -6
- package/dist/esm/TileSet.d.ts.map +1 -0
- package/dist/esm/TileSet.js +161 -181
- package/dist/esm/TileSet.js.map +1 -1
- package/dist/esm/WangSet.d.ts +5 -4
- package/dist/esm/WangSet.d.ts.map +1 -0
- package/dist/esm/WangSet.js +70 -78
- package/dist/esm/WangSet.js.map +1 -1
- package/dist/esm/WorkerSampledChunkSource.d.ts +95 -0
- package/dist/esm/WorkerSampledChunkSource.d.ts.map +1 -0
- package/dist/esm/WorkerSampledChunkSource.js +117 -0
- package/dist/esm/WorkerSampledChunkSource.js.map +1 -0
- package/dist/esm/autoTile.d.ts +10 -5
- package/dist/esm/autoTile.d.ts.map +1 -0
- package/dist/esm/autoTile.js +164 -205
- package/dist/esm/autoTile.js.map +1 -1
- package/dist/esm/chunkGeometry.d.ts +8 -7
- package/dist/esm/chunkGeometry.d.ts.map +1 -0
- package/dist/esm/chunkGeometry.js +83 -94
- package/dist/esm/chunkGeometry.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +26 -15
- package/dist/esm/nodeBounds.d.ts +20 -0
- package/dist/esm/nodeBounds.d.ts.map +1 -0
- package/dist/esm/nodeBounds.js +40 -0
- package/dist/esm/nodeBounds.js.map +1 -0
- package/dist/esm/pixelSnap.d.ts +4 -3
- package/dist/esm/pixelSnap.d.ts.map +1 -0
- package/dist/esm/pixelSnap.js +18 -16
- package/dist/esm/pixelSnap.js.map +1 -1
- package/dist/esm/public.d.ts +23 -3
- package/dist/esm/public.d.ts.map +1 -0
- package/dist/esm/tileCollision.d.ts +190 -0
- package/dist/esm/tileCollision.d.ts.map +1 -0
- package/dist/esm/tileCollision.js +367 -0
- package/dist/esm/tileCollision.js.map +1 -0
- package/dist/esm/tileWord.d.ts +12 -0
- package/dist/esm/tileWord.d.ts.map +1 -0
- package/dist/esm/tileWord.js +16 -0
- package/dist/esm/tileWord.js.map +1 -0
- package/dist/esm/tilemapExtension.d.ts +4 -3
- package/dist/esm/tilemapExtension.d.ts.map +1 -0
- package/dist/esm/tilemapExtension.js +47 -47
- package/dist/esm/tilemapExtension.js.map +1 -1
- package/dist/esm/tilemapSerializers.d.ts +5 -4
- package/dist/esm/tilemapSerializers.d.ts.map +1 -0
- package/dist/esm/tilemapSerializers.js +48 -41
- package/dist/esm/tilemapSerializers.js.map +1 -1
- package/dist/esm/types.d.ts +34 -18
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +111 -120
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts +50 -2
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.d.ts.map +1 -0
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.js +366 -332
- package/dist/esm/webgl2/WebGl2TileChunkRenderer.js.map +1 -1
- package/dist/esm/webgl2/shaders/tile-chunk.frag.js +6 -0
- package/dist/esm/webgl2/shaders/tile-chunk.frag.js.map +1 -0
- package/dist/esm/webgl2/shaders/tile-chunk.vert.js +6 -0
- package/dist/esm/webgl2/shaders/tile-chunk.vert.js.map +1 -0
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts +70 -4
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.d.ts.map +1 -0
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.js +496 -383
- package/dist/esm/webgpu/WebGpuTileChunkRenderer.js.map +1 -1
- package/dist/esm/webgpu/shaders/tile-chunk.wgsl.js +6 -0
- package/dist/esm/webgpu/shaders/tile-chunk.wgsl.js.map +1 -0
- package/package.json +8 -15
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/register.d.ts +0 -1
- package/dist/esm/register.js +0 -25
- package/dist/esm/register.js.map +0 -1
package/dist/esm/WangSet.js
CHANGED
|
@@ -1,81 +1,73 @@
|
|
|
1
|
+
//#region src/WangSet.ts
|
|
1
2
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
/** Read-only view of the full bitmask → tile-ID mapping. */
|
|
71
|
-
get blobMap() {
|
|
72
|
-
return this._map;
|
|
73
|
-
}
|
|
74
|
-
/** Read-only view of the local tile IDs that count as group members. */
|
|
75
|
-
get members() {
|
|
76
|
-
return this._members;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
3
|
+
* Describes a Wang autotile set: a mapping from a neighbor bitmask to a
|
|
4
|
+
* local tile ID within a specific tileset.
|
|
5
|
+
*
|
|
6
|
+
* Blob bitmask bit layout (powers of 2):
|
|
7
|
+
* - Bit 0 (1): Top-left
|
|
8
|
+
* - Bit 1 (2): Top
|
|
9
|
+
* - Bit 2 (4): Top-right
|
|
10
|
+
* - Bit 3 (8): Left
|
|
11
|
+
* - Bit 4 (16): Right
|
|
12
|
+
* - Bit 5 (32): Bottom-left
|
|
13
|
+
* - Bit 6 (64): Bottom
|
|
14
|
+
* - Bit 7 (128): Bottom-right
|
|
15
|
+
*
|
|
16
|
+
* Diagonal (corner) bits are only set when both adjacent cardinal directions
|
|
17
|
+
* are also set - reducing 256 raw combinations to 47 meaningful blob states.
|
|
18
|
+
*
|
|
19
|
+
* Edge bitmask bit layout:
|
|
20
|
+
* - Bit 0 (1): Top
|
|
21
|
+
* - Bit 1 (2): Right
|
|
22
|
+
* - Bit 2 (4): Bottom
|
|
23
|
+
* - Bit 3 (8): Left
|
|
24
|
+
*/
|
|
25
|
+
var WangSet = class {
|
|
26
|
+
/** Index of the tileset that contains the Wang tiles. */
|
|
27
|
+
tilesetIndex;
|
|
28
|
+
/** The Wang mode: `'blob'` (8-neighbor) or `'edge'` (4-neighbor). */
|
|
29
|
+
type;
|
|
30
|
+
_map;
|
|
31
|
+
_members;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.tilesetIndex = options.tilesetIndex;
|
|
34
|
+
this.type = options.type ?? "blob";
|
|
35
|
+
if (options.blobMap instanceof Map) this._map = options.blobMap;
|
|
36
|
+
else {
|
|
37
|
+
const map = /* @__PURE__ */ new Map();
|
|
38
|
+
for (const [k, v] of Object.entries(options.blobMap)) map.set(Number(k), v);
|
|
39
|
+
this._map = map;
|
|
40
|
+
}
|
|
41
|
+
const members = new Set(this._map.values());
|
|
42
|
+
if (options.members) for (const id of options.members) members.add(id);
|
|
43
|
+
this._members = members;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Look up the local tile ID for a given neighbor bitmask.
|
|
47
|
+
* Returns `undefined` if the mask has no mapping in this set.
|
|
48
|
+
*/
|
|
49
|
+
getTileId(mask) {
|
|
50
|
+
return this._map.get(mask);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Whether a local tile ID belongs to this Wang group. True for every variant
|
|
54
|
+
* the set produces plus any explicit {@link WangSetOptions.members}. Used as
|
|
55
|
+
* the default, variant-stable neighbour-membership test by {@link autoTile}
|
|
56
|
+
* and `refreshCell`.
|
|
57
|
+
*/
|
|
58
|
+
isMember(localTileId) {
|
|
59
|
+
return this._members.has(localTileId);
|
|
60
|
+
}
|
|
61
|
+
/** Read-only view of the full bitmask → tile-ID mapping. */
|
|
62
|
+
get blobMap() {
|
|
63
|
+
return this._map;
|
|
64
|
+
}
|
|
65
|
+
/** Read-only view of the local tile IDs that count as group members. */
|
|
66
|
+
get members() {
|
|
67
|
+
return this._members;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
79
70
|
|
|
71
|
+
//#endregion
|
|
80
72
|
export { WangSet };
|
|
81
|
-
//# sourceMappingURL=WangSet.js.map
|
|
73
|
+
//# sourceMappingURL=WangSet.js.map
|
package/dist/esm/WangSet.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WangSet.js","sources":["
|
|
1
|
+
{"version":3,"file":"WangSet.js","names":[],"sources":["../../src/WangSet.ts"],"sourcesContent":["/**\n * Options for constructing a {@link WangSet}.\n */\nexport interface WangSetOptions {\n /** Which TileSet contains the Wang tiles (index into the layer's tilesets array). */\n tilesetIndex: number;\n /**\n * Map from Wang bitmask to local tile ID within the tileset.\n *\n * For blob mode (8-neighbor), keys are 0-255; the 47 valid combinations\n * of the blob encoding must be covered at minimum.\n * For edge mode (4-neighbor), keys are 0-15.\n *\n * Accepts either a {@link ReadonlyMap} or a plain `Record<number, number>`.\n */\n blobMap: ReadonlyMap<number, number> | Record<number, number>;\n /** Whether this is a blob (8-neighbor, default) or edge (4-neighbor) Wang set. */\n type?: 'blob' | 'edge';\n /**\n * Extra local tile IDs that count as belonging to this Wang group, beyond\n * the variant IDs already present as {@link blobMap} *values*.\n *\n * Membership is what {@link autoTile} / `refreshCell` use to decide whether a\n * neighbouring cell is \"part of the terrain\". Because every value in\n * `blobMap` is itself a member, group membership stays stable no matter which\n * variant a cell currently shows - that is what makes incremental\n * `refreshCell` correct. Add the *base* tile ID you paint with here if it is\n * not already one of the blobMap variants.\n */\n members?: Iterable<number>;\n}\n\n/**\n * Describes a Wang autotile set: a mapping from a neighbor bitmask to a\n * local tile ID within a specific tileset.\n *\n * Blob bitmask bit layout (powers of 2):\n * - Bit 0 (1): Top-left\n * - Bit 1 (2): Top\n * - Bit 2 (4): Top-right\n * - Bit 3 (8): Left\n * - Bit 4 (16): Right\n * - Bit 5 (32): Bottom-left\n * - Bit 6 (64): Bottom\n * - Bit 7 (128): Bottom-right\n *\n * Diagonal (corner) bits are only set when both adjacent cardinal directions\n * are also set - reducing 256 raw combinations to 47 meaningful blob states.\n *\n * Edge bitmask bit layout:\n * - Bit 0 (1): Top\n * - Bit 1 (2): Right\n * - Bit 2 (4): Bottom\n * - Bit 3 (8): Left\n */\nexport class WangSet {\n /** Index of the tileset that contains the Wang tiles. */\n public readonly tilesetIndex: number;\n\n /** The Wang mode: `'blob'` (8-neighbor) or `'edge'` (4-neighbor). */\n public readonly type: 'blob' | 'edge';\n\n private readonly _map: ReadonlyMap<number, number>;\n private readonly _members: ReadonlySet<number>;\n\n public constructor(options: WangSetOptions) {\n this.tilesetIndex = options.tilesetIndex;\n this.type = options.type ?? 'blob';\n\n if (options.blobMap instanceof Map) {\n this._map = options.blobMap;\n } else {\n const map = new Map<number, number>();\n for (const [k, v] of Object.entries(options.blobMap as Record<string, number>)) {\n map.set(Number(k), v);\n }\n this._map = map;\n }\n\n // Members default to every variant tile ID the set can produce, so that any\n // autotiled variant is recognised as part of the group (variant-stable\n // membership). Explicit `members` (e.g. the base paint ID) are added on top.\n const members = new Set<number>(this._map.values());\n if (options.members) {\n for (const id of options.members) members.add(id);\n }\n this._members = members;\n }\n\n /**\n * Look up the local tile ID for a given neighbor bitmask.\n * Returns `undefined` if the mask has no mapping in this set.\n */\n public getTileId(mask: number): number | undefined {\n return this._map.get(mask);\n }\n\n /**\n * Whether a local tile ID belongs to this Wang group. True for every variant\n * the set produces plus any explicit {@link WangSetOptions.members}. Used as\n * the default, variant-stable neighbour-membership test by {@link autoTile}\n * and `refreshCell`.\n */\n public isMember(localTileId: number): boolean {\n return this._members.has(localTileId);\n }\n\n /** Read-only view of the full bitmask → tile-ID mapping. */\n public get blobMap(): ReadonlyMap<number, number> {\n return this._map;\n }\n\n /** Read-only view of the local tile IDs that count as group members. */\n public get members(): ReadonlySet<number> {\n return this._members;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAuDA,IAAa,UAAb,MAAqB;;CAEnB,AAAgB;;CAGhB,AAAgB;CAEhB,AAAiB;CACjB,AAAiB;CAEjB,AAAO,YAAY,SAAyB;EAC1C,KAAK,eAAe,QAAQ;EAC5B,KAAK,OAAO,QAAQ,QAAQ;EAE5B,IAAI,QAAQ,mBAAmB,KAC7B,KAAK,OAAO,QAAQ;OACf;GACL,MAAM,sBAAM,IAAI,IAAoB;GACpC,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,QAAQ,OAAiC,GAC3E,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;GAEtB,KAAK,OAAO;EACd;EAKA,MAAM,UAAU,IAAI,IAAY,KAAK,KAAK,OAAO,CAAC;EAClD,IAAI,QAAQ,SACV,KAAK,MAAM,MAAM,QAAQ,SAAS,QAAQ,IAAI,EAAE;EAElD,KAAK,WAAW;CAClB;;;;;CAMA,AAAO,UAAU,MAAkC;EACjD,OAAO,KAAK,KAAK,IAAI,IAAI;CAC3B;;;;;;;CAQA,AAAO,SAAS,aAA8B;EAC5C,OAAO,KAAK,SAAS,IAAI,WAAW;CACtC;;CAGA,IAAW,UAAuC;EAChD,OAAO,KAAK;CACd;;CAGA,IAAW,UAA+B;EACxC,OAAO,KAAK;CACd;AACF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { ChunkSource } from './ChunkSource';
|
|
2
|
+
import type { TileLayer } from './TileLayer';
|
|
3
|
+
import type { ResolvedTile } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link createWorkerSampledChunkSource}.
|
|
6
|
+
* @advanced
|
|
7
|
+
*/
|
|
8
|
+
export interface WorkerSampledChunkSourceOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Complete worker script source as a single self-contained string, Blob-URL'd
|
|
11
|
+
* into a real `Worker` at construction time - the same technique
|
|
12
|
+
* {@link import('@codexo/exojs').WorkletEffect} uses for AudioWorklet
|
|
13
|
+
* processors. No separate worker asset is fetched and no bundler-specific
|
|
14
|
+
* worker-import syntax is involved.
|
|
15
|
+
*
|
|
16
|
+
* Producing that string by hand-writing JavaScript into a template literal
|
|
17
|
+
* works but gives up type checking, linting and shared code. Prefer a build
|
|
18
|
+
* step that bundles a real module into a string: this repository's own
|
|
19
|
+
* examples are authored as `*.worker.ts` and imported through a `?worker`
|
|
20
|
+
* query, which is why they can `import` the sampling functions the main
|
|
21
|
+
* thread uses instead of restating them.
|
|
22
|
+
*
|
|
23
|
+
* Must implement this request/response protocol:
|
|
24
|
+
* - Request (received via `self.onmessage`):
|
|
25
|
+
* `{ requestId: number; cx: number; cy: number; chunkWidth: number; chunkHeight: number }`.
|
|
26
|
+
* - Response, success (send via `self.postMessage`, transferring the
|
|
27
|
+
* buffer for a zero-copy handoff - `postMessage(response, [response.values.buffer])`):
|
|
28
|
+
* `{ requestId: number; values: Float64Array }`, `values.length ===
|
|
29
|
+
* chunkWidth * chunkHeight`, row-major (`localTy * chunkWidth + localTx`),
|
|
30
|
+
* one sampled value per tile in the requested chunk. Compute each tile's
|
|
31
|
+
* absolute coordinate as `cx * chunkWidth + localTx` /
|
|
32
|
+
* `cy * chunkHeight + localTy`.
|
|
33
|
+
* - Response, error (one request failed without crashing the worker):
|
|
34
|
+
* `{ requestId: number; error: string }`.
|
|
35
|
+
*
|
|
36
|
+
* Your worker must reply to every request with exactly one success or
|
|
37
|
+
* error message bearing the same `requestId`. A request left unanswered
|
|
38
|
+
* leaves that chunk permanently unfetched - there is no timeout - since
|
|
39
|
+
* `ChunkStreamer` tracks it as still in flight and never retries it.
|
|
40
|
+
*/
|
|
41
|
+
workerSource: string;
|
|
42
|
+
/**
|
|
43
|
+
* Posted to the worker once, before any chunk request, when present.
|
|
44
|
+
*
|
|
45
|
+
* The transport for whatever the sampler needs to know that is not per-chunk
|
|
46
|
+
* - a seed, a feature size, a cost knob. Without it those values would have
|
|
47
|
+
* to be interpolated into {@link WorkerSampledChunkSourceOptions.workerSource}
|
|
48
|
+
* as literals, which turns every parameter change into a source rebuild and a
|
|
49
|
+
* new `Worker`.
|
|
50
|
+
*
|
|
51
|
+
* `postMessage` delivery is ordered, so the worker is guaranteed to see this
|
|
52
|
+
* before the first request. Distinguish it from a chunk request by its own
|
|
53
|
+
* shape; requests carry a `requestId`.
|
|
54
|
+
*/
|
|
55
|
+
initMessage?: unknown;
|
|
56
|
+
/**
|
|
57
|
+
* Convert a sampled value (and its absolute tile coordinate) into a
|
|
58
|
+
* resolved tile, or `null` for an empty cell. Always runs on the main
|
|
59
|
+
* thread - a `ResolvedTile` references a `TileSet`/`Texture`, neither of
|
|
60
|
+
* which can cross a `postMessage` boundary. Must be pure and
|
|
61
|
+
* deterministic, same requirement as
|
|
62
|
+
* {@link import('./SampledChunkSource').SampledChunkSourceOptions.mapValueToTile}.
|
|
63
|
+
*/
|
|
64
|
+
mapValueToTile(value: number, tx: number, ty: number): ResolvedTile | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Build a {@link import('./ChunkStreamer').ChunkStreamer}-ready
|
|
68
|
+
* {@link ChunkSource} that samples off the main thread via a Web Worker -
|
|
69
|
+
* the async counterpart to
|
|
70
|
+
* {@link import('./SampledChunkSource').createSampledChunkSource}, for
|
|
71
|
+
* sampling functions expensive enough to want off-thread execution.
|
|
72
|
+
*
|
|
73
|
+
* Unlike the synchronous provider, sampling logic cannot be passed as a
|
|
74
|
+
* live function - functions cannot cross a `postMessage` boundary. Instead
|
|
75
|
+
* {@link WorkerSampledChunkSourceOptions.workerSource} is a self-contained
|
|
76
|
+
* worker script string; see its documentation for the protocol.
|
|
77
|
+
*
|
|
78
|
+
* The returned value's `destroy()` MUST be called when done with it (e.g.
|
|
79
|
+
* alongside your `ChunkStreamer.destroy()` call) - the underlying `Worker`
|
|
80
|
+
* is a real resource that leaks otherwise. `ChunkSource` itself has no
|
|
81
|
+
* lifecycle hook, so this is not automatic.
|
|
82
|
+
*
|
|
83
|
+
* Implementation note: `workerSource` is Blob-URL'd into a real `Worker`
|
|
84
|
+
* (`new Blob([workerSource])` → `URL.createObjectURL` → `new Worker(url)`) -
|
|
85
|
+
* the same technique {@link import('@codexo/exojs').WorkletEffect} already
|
|
86
|
+
* uses for AudioWorklet processors. This is bundler-agnostic (no special
|
|
87
|
+
* config needed in Vite/webpack/etc.) but requires a Content-Security-Policy
|
|
88
|
+
* that permits `blob:` in `worker-src` (or `script-src` as a fallback) if
|
|
89
|
+
* your deployment sets one.
|
|
90
|
+
* @advanced
|
|
91
|
+
*/
|
|
92
|
+
export declare const createWorkerSampledChunkSource: (layer: TileLayer, options: WorkerSampledChunkSourceOptions) => ChunkSource & {
|
|
93
|
+
destroy(): void;
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=WorkerSampledChunkSource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkerSampledChunkSource.d.ts","sourceRoot":"","sources":["../../src/WorkerSampledChunkSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,WAAW,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5C;;;GAGG;AACH,MAAM,WAAW,+BAA+B;IAC9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAAC;CAC5E;AAiCD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,8BAA8B,GAAI,OAAO,SAAS,EAAE,SAAS,+BAA+B,KAAG,WAAW,GAAG;IAAE,OAAO,IAAI,IAAI,CAAA;CA8F1I,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { packTile } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/WorkerSampledChunkSource.ts
|
|
4
|
+
const isWorkerErrorMessage = (message) => "error" in message;
|
|
5
|
+
/**
|
|
6
|
+
* Build a {@link import('./ChunkStreamer').ChunkStreamer}-ready
|
|
7
|
+
* {@link ChunkSource} that samples off the main thread via a Web Worker -
|
|
8
|
+
* the async counterpart to
|
|
9
|
+
* {@link import('./SampledChunkSource').createSampledChunkSource}, for
|
|
10
|
+
* sampling functions expensive enough to want off-thread execution.
|
|
11
|
+
*
|
|
12
|
+
* Unlike the synchronous provider, sampling logic cannot be passed as a
|
|
13
|
+
* live function - functions cannot cross a `postMessage` boundary. Instead
|
|
14
|
+
* {@link WorkerSampledChunkSourceOptions.workerSource} is a self-contained
|
|
15
|
+
* worker script string; see its documentation for the protocol.
|
|
16
|
+
*
|
|
17
|
+
* The returned value's `destroy()` MUST be called when done with it (e.g.
|
|
18
|
+
* alongside your `ChunkStreamer.destroy()` call) - the underlying `Worker`
|
|
19
|
+
* is a real resource that leaks otherwise. `ChunkSource` itself has no
|
|
20
|
+
* lifecycle hook, so this is not automatic.
|
|
21
|
+
*
|
|
22
|
+
* Implementation note: `workerSource` is Blob-URL'd into a real `Worker`
|
|
23
|
+
* (`new Blob([workerSource])` → `URL.createObjectURL` → `new Worker(url)`) -
|
|
24
|
+
* the same technique {@link import('@codexo/exojs').WorkletEffect} already
|
|
25
|
+
* uses for AudioWorklet processors. This is bundler-agnostic (no special
|
|
26
|
+
* config needed in Vite/webpack/etc.) but requires a Content-Security-Policy
|
|
27
|
+
* that permits `blob:` in `worker-src` (or `script-src` as a fallback) if
|
|
28
|
+
* your deployment sets one.
|
|
29
|
+
* @advanced
|
|
30
|
+
*/
|
|
31
|
+
const createWorkerSampledChunkSource = (layer, options) => {
|
|
32
|
+
const mapValueToTile = options.mapValueToTile.bind(options);
|
|
33
|
+
const blob = new Blob([options.workerSource], { type: "application/javascript" });
|
|
34
|
+
const url = URL.createObjectURL(blob);
|
|
35
|
+
const worker = new Worker(url);
|
|
36
|
+
URL.revokeObjectURL(url);
|
|
37
|
+
if (options.initMessage !== void 0) worker.postMessage(options.initMessage);
|
|
38
|
+
let requestCounter = 0;
|
|
39
|
+
const pending = /* @__PURE__ */ new Map();
|
|
40
|
+
let destroyed = false;
|
|
41
|
+
const composePayload = (values, request) => {
|
|
42
|
+
const { cx, cy, chunkWidth, chunkHeight } = request;
|
|
43
|
+
const startTx = cx * chunkWidth;
|
|
44
|
+
const startTy = cy * chunkHeight;
|
|
45
|
+
let out = null;
|
|
46
|
+
for (let ty = startTy; ty < startTy + chunkHeight; ty++) for (let tx = startTx; tx < startTx + chunkWidth; tx++) {
|
|
47
|
+
if (layer.width !== void 0 && layer.height !== void 0 && (tx >= layer.width || ty >= layer.height)) continue;
|
|
48
|
+
const localIndex = (ty - startTy) * chunkWidth + (tx - startTx);
|
|
49
|
+
const value = values[localIndex];
|
|
50
|
+
if (value === void 0) continue;
|
|
51
|
+
const resolved = mapValueToTile(value, tx, ty);
|
|
52
|
+
if (!resolved) continue;
|
|
53
|
+
out ??= new Uint32Array(chunkWidth * chunkHeight);
|
|
54
|
+
const tilesetIndex = layer.tilesets.indexOf(resolved.tileset);
|
|
55
|
+
out[localIndex] = packTile(tilesetIndex, resolved.localTileId, resolved.transform);
|
|
56
|
+
}
|
|
57
|
+
return out === null ? null : {
|
|
58
|
+
width: chunkWidth,
|
|
59
|
+
height: chunkHeight,
|
|
60
|
+
tiles: out
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
worker.onmessage = (event) => {
|
|
64
|
+
const message = event.data;
|
|
65
|
+
const entry = pending.get(message.requestId);
|
|
66
|
+
if (!entry) return;
|
|
67
|
+
pending.delete(message.requestId);
|
|
68
|
+
if (isWorkerErrorMessage(message)) {
|
|
69
|
+
entry.reject(new Error(message.error));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
entry.resolve(composePayload(message.values, entry));
|
|
73
|
+
};
|
|
74
|
+
worker.onerror = (event) => {
|
|
75
|
+
const error = /* @__PURE__ */ new Error(`WorkerSampledChunkSource worker error: ${event.message}`);
|
|
76
|
+
for (const entry of pending.values()) entry.reject(error);
|
|
77
|
+
pending.clear();
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
getChunk(cx, cy) {
|
|
81
|
+
if (destroyed) return Promise.reject(/* @__PURE__ */ new Error("WorkerSampledChunkSource: getChunk() called after destroy()."));
|
|
82
|
+
const chunkWidth = layer.chunkWidth;
|
|
83
|
+
const chunkHeight = layer.chunkHeight;
|
|
84
|
+
const requestId = ++requestCounter;
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
pending.set(requestId, {
|
|
87
|
+
resolve,
|
|
88
|
+
reject,
|
|
89
|
+
cx,
|
|
90
|
+
cy,
|
|
91
|
+
chunkWidth,
|
|
92
|
+
chunkHeight
|
|
93
|
+
});
|
|
94
|
+
const request = {
|
|
95
|
+
requestId,
|
|
96
|
+
cx,
|
|
97
|
+
cy,
|
|
98
|
+
chunkWidth,
|
|
99
|
+
chunkHeight
|
|
100
|
+
};
|
|
101
|
+
worker.postMessage(request);
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
destroy() {
|
|
105
|
+
if (destroyed) return;
|
|
106
|
+
destroyed = true;
|
|
107
|
+
const error = /* @__PURE__ */ new Error("WorkerSampledChunkSource destroyed.");
|
|
108
|
+
for (const entry of pending.values()) entry.reject(error);
|
|
109
|
+
pending.clear();
|
|
110
|
+
worker.terminate();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
export { createWorkerSampledChunkSource };
|
|
117
|
+
//# sourceMappingURL=WorkerSampledChunkSource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkerSampledChunkSource.js","names":[],"sources":["../../src/WorkerSampledChunkSource.ts"],"sourcesContent":["import type { ChunkPayload, ChunkSource } from './ChunkSource';\nimport type { TileLayer } from './TileLayer';\nimport type { ResolvedTile } from './types';\nimport { packTile } from './types';\n\n/**\n * Options for {@link createWorkerSampledChunkSource}.\n * @advanced\n */\nexport interface WorkerSampledChunkSourceOptions {\n /**\n * Complete worker script source as a single self-contained string, Blob-URL'd\n * into a real `Worker` at construction time - the same technique\n * {@link import('@codexo/exojs').WorkletEffect} uses for AudioWorklet\n * processors. No separate worker asset is fetched and no bundler-specific\n * worker-import syntax is involved.\n *\n * Producing that string by hand-writing JavaScript into a template literal\n * works but gives up type checking, linting and shared code. Prefer a build\n * step that bundles a real module into a string: this repository's own\n * examples are authored as `*.worker.ts` and imported through a `?worker`\n * query, which is why they can `import` the sampling functions the main\n * thread uses instead of restating them.\n *\n * Must implement this request/response protocol:\n * - Request (received via `self.onmessage`):\n * `{ requestId: number; cx: number; cy: number; chunkWidth: number; chunkHeight: number }`.\n * - Response, success (send via `self.postMessage`, transferring the\n * buffer for a zero-copy handoff - `postMessage(response, [response.values.buffer])`):\n * `{ requestId: number; values: Float64Array }`, `values.length ===\n * chunkWidth * chunkHeight`, row-major (`localTy * chunkWidth + localTx`),\n * one sampled value per tile in the requested chunk. Compute each tile's\n * absolute coordinate as `cx * chunkWidth + localTx` /\n * `cy * chunkHeight + localTy`.\n * - Response, error (one request failed without crashing the worker):\n * `{ requestId: number; error: string }`.\n *\n * Your worker must reply to every request with exactly one success or\n * error message bearing the same `requestId`. A request left unanswered\n * leaves that chunk permanently unfetched - there is no timeout - since\n * `ChunkStreamer` tracks it as still in flight and never retries it.\n */\n workerSource: string;\n /**\n * Posted to the worker once, before any chunk request, when present.\n *\n * The transport for whatever the sampler needs to know that is not per-chunk\n * - a seed, a feature size, a cost knob. Without it those values would have\n * to be interpolated into {@link WorkerSampledChunkSourceOptions.workerSource}\n * as literals, which turns every parameter change into a source rebuild and a\n * new `Worker`.\n *\n * `postMessage` delivery is ordered, so the worker is guaranteed to see this\n * before the first request. Distinguish it from a chunk request by its own\n * shape; requests carry a `requestId`.\n */\n initMessage?: unknown;\n /**\n * Convert a sampled value (and its absolute tile coordinate) into a\n * resolved tile, or `null` for an empty cell. Always runs on the main\n * thread - a `ResolvedTile` references a `TileSet`/`Texture`, neither of\n * which can cross a `postMessage` boundary. Must be pure and\n * deterministic, same requirement as\n * {@link import('./SampledChunkSource').SampledChunkSourceOptions.mapValueToTile}.\n */\n mapValueToTile(value: number, tx: number, ty: number): ResolvedTile | null;\n}\n\ninterface WorkerRequestMessage {\n readonly requestId: number;\n readonly cx: number;\n readonly cy: number;\n readonly chunkWidth: number;\n readonly chunkHeight: number;\n}\n\ninterface WorkerSuccessMessage {\n readonly requestId: number;\n readonly values: Float64Array;\n}\n\ninterface WorkerErrorMessage {\n readonly requestId: number;\n readonly error: string;\n}\n\ntype WorkerResponseMessage = WorkerSuccessMessage | WorkerErrorMessage;\n\nconst isWorkerErrorMessage = (message: WorkerResponseMessage): message is WorkerErrorMessage => 'error' in message;\n\ninterface PendingRequest {\n readonly resolve: (payload: ChunkPayload | null) => void;\n readonly reject: (error: Error) => void;\n readonly cx: number;\n readonly cy: number;\n readonly chunkWidth: number;\n readonly chunkHeight: number;\n}\n\n/**\n * Build a {@link import('./ChunkStreamer').ChunkStreamer}-ready\n * {@link ChunkSource} that samples off the main thread via a Web Worker -\n * the async counterpart to\n * {@link import('./SampledChunkSource').createSampledChunkSource}, for\n * sampling functions expensive enough to want off-thread execution.\n *\n * Unlike the synchronous provider, sampling logic cannot be passed as a\n * live function - functions cannot cross a `postMessage` boundary. Instead\n * {@link WorkerSampledChunkSourceOptions.workerSource} is a self-contained\n * worker script string; see its documentation for the protocol.\n *\n * The returned value's `destroy()` MUST be called when done with it (e.g.\n * alongside your `ChunkStreamer.destroy()` call) - the underlying `Worker`\n * is a real resource that leaks otherwise. `ChunkSource` itself has no\n * lifecycle hook, so this is not automatic.\n *\n * Implementation note: `workerSource` is Blob-URL'd into a real `Worker`\n * (`new Blob([workerSource])` → `URL.createObjectURL` → `new Worker(url)`) -\n * the same technique {@link import('@codexo/exojs').WorkletEffect} already\n * uses for AudioWorklet processors. This is bundler-agnostic (no special\n * config needed in Vite/webpack/etc.) but requires a Content-Security-Policy\n * that permits `blob:` in `worker-src` (or `script-src` as a fallback) if\n * your deployment sets one.\n * @advanced\n */\nexport const createWorkerSampledChunkSource = (layer: TileLayer, options: WorkerSampledChunkSourceOptions): ChunkSource & { destroy(): void } => {\n const mapValueToTile = options.mapValueToTile.bind(options);\n\n const blob = new Blob([options.workerSource], { type: 'application/javascript' });\n const url = URL.createObjectURL(blob);\n const worker = new Worker(url);\n URL.revokeObjectURL(url);\n\n if (options.initMessage !== undefined) {\n worker.postMessage(options.initMessage);\n }\n\n let requestCounter = 0;\n const pending = new Map<number, PendingRequest>();\n let destroyed = false;\n\n const composePayload = (values: Float64Array, request: PendingRequest): ChunkPayload | null => {\n const { cx, cy, chunkWidth, chunkHeight } = request;\n const startTx = cx * chunkWidth;\n const startTy = cy * chunkHeight;\n\n let out: Uint32Array | null = null;\n for (let ty = startTy; ty < startTy + chunkHeight; ty++) {\n for (let tx = startTx; tx < startTx + chunkWidth; tx++) {\n // Bounded-layer edge chunks may be smaller than a full chunk - same\n // clamp createSampledChunkSource applies, computed here (not asked\n // of the worker) since only this side knows the layer's bounds.\n if (layer.width !== undefined && layer.height !== undefined && (tx >= layer.width || ty >= layer.height)) continue;\n\n const localIndex = (ty - startTy) * chunkWidth + (tx - startTx);\n const value = values[localIndex];\n if (value === undefined) continue; // defensive: malformed/short values array\n const resolved = mapValueToTile(value, tx, ty);\n if (!resolved) continue;\n\n out ??= new Uint32Array(chunkWidth * chunkHeight);\n const tilesetIndex = layer.tilesets.indexOf(resolved.tileset);\n out[localIndex] = packTile(tilesetIndex, resolved.localTileId, resolved.transform);\n }\n }\n\n return out === null ? null : { width: chunkWidth, height: chunkHeight, tiles: out };\n };\n\n worker.onmessage = (event: MessageEvent<WorkerResponseMessage>): void => {\n const message = event.data;\n const entry = pending.get(message.requestId);\n if (!entry) return; // stale/unknown requestId (e.g. arrived after destroy())\n pending.delete(message.requestId);\n\n if (isWorkerErrorMessage(message)) {\n entry.reject(new Error(message.error));\n return;\n }\n\n entry.resolve(composePayload(message.values, entry));\n };\n\n worker.onerror = (event: ErrorEvent): void => {\n const error = new Error(`WorkerSampledChunkSource worker error: ${event.message}`);\n for (const entry of pending.values()) {\n entry.reject(error);\n }\n pending.clear();\n };\n\n return {\n getChunk(cx: number, cy: number): Promise<ChunkPayload | null> {\n if (destroyed) {\n return Promise.reject(new Error('WorkerSampledChunkSource: getChunk() called after destroy().'));\n }\n\n const chunkWidth = layer.chunkWidth;\n const chunkHeight = layer.chunkHeight;\n const requestId = ++requestCounter;\n\n return new Promise<ChunkPayload | null>((resolve, reject) => {\n pending.set(requestId, { resolve, reject, cx, cy, chunkWidth, chunkHeight });\n const request: WorkerRequestMessage = { requestId, cx, cy, chunkWidth, chunkHeight };\n worker.postMessage(request);\n });\n },\n\n destroy(): void {\n if (destroyed) return;\n destroyed = true;\n const error = new Error('WorkerSampledChunkSource destroyed.');\n for (const entry of pending.values()) {\n entry.reject(error);\n }\n pending.clear();\n worker.terminate();\n },\n };\n};\n"],"mappings":";;;AAwFA,MAAM,wBAAwB,YAAkE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqC3G,MAAa,kCAAkC,OAAkB,YAAgF;CAC/I,MAAM,iBAAiB,QAAQ,eAAe,KAAK,OAAO;CAE1D,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,YAAY,GAAG,EAAE,MAAM,yBAAyB,CAAC;CAChF,MAAM,MAAM,IAAI,gBAAgB,IAAI;CACpC,MAAM,SAAS,IAAI,OAAO,GAAG;CAC7B,IAAI,gBAAgB,GAAG;CAEvB,IAAI,QAAQ,gBAAgB,QAC1B,OAAO,YAAY,QAAQ,WAAW;CAGxC,IAAI,iBAAiB;CACrB,MAAM,0BAAU,IAAI,IAA4B;CAChD,IAAI,YAAY;CAEhB,MAAM,kBAAkB,QAAsB,YAAiD;EAC7F,MAAM,EAAE,IAAI,IAAI,YAAY,gBAAgB;EAC5C,MAAM,UAAU,KAAK;EACrB,MAAM,UAAU,KAAK;EAErB,IAAI,MAA0B;EAC9B,KAAK,IAAI,KAAK,SAAS,KAAK,UAAU,aAAa,MACjD,KAAK,IAAI,KAAK,SAAS,KAAK,UAAU,YAAY,MAAM;GAItD,IAAI,MAAM,UAAU,UAAa,MAAM,WAAW,WAAc,MAAM,MAAM,SAAS,MAAM,MAAM,SAAS;GAE1G,MAAM,cAAc,KAAK,WAAW,cAAc,KAAK;GACvD,MAAM,QAAQ,OAAO;GACrB,IAAI,UAAU,QAAW;GACzB,MAAM,WAAW,eAAe,OAAO,IAAI,EAAE;GAC7C,IAAI,CAAC,UAAU;GAEf,QAAQ,IAAI,YAAY,aAAa,WAAW;GAChD,MAAM,eAAe,MAAM,SAAS,QAAQ,SAAS,OAAO;GAC5D,IAAI,cAAc,SAAS,cAAc,SAAS,aAAa,SAAS,SAAS;EACnF;EAGF,OAAO,QAAQ,OAAO,OAAO;GAAE,OAAO;GAAY,QAAQ;GAAa,OAAO;EAAI;CACpF;CAEA,OAAO,aAAa,UAAqD;EACvE,MAAM,UAAU,MAAM;EACtB,MAAM,QAAQ,QAAQ,IAAI,QAAQ,SAAS;EAC3C,IAAI,CAAC,OAAO;EACZ,QAAQ,OAAO,QAAQ,SAAS;EAEhC,IAAI,qBAAqB,OAAO,GAAG;GACjC,MAAM,OAAO,IAAI,MAAM,QAAQ,KAAK,CAAC;GACrC;EACF;EAEA,MAAM,QAAQ,eAAe,QAAQ,QAAQ,KAAK,CAAC;CACrD;CAEA,OAAO,WAAW,UAA4B;EAC5C,MAAM,wBAAQ,IAAI,MAAM,0CAA0C,MAAM,SAAS;EACjF,KAAK,MAAM,SAAS,QAAQ,OAAO,GACjC,MAAM,OAAO,KAAK;EAEpB,QAAQ,MAAM;CAChB;CAEA,OAAO;EACL,SAAS,IAAY,IAA0C;GAC7D,IAAI,WACF,OAAO,QAAQ,uBAAO,IAAI,MAAM,8DAA8D,CAAC;GAGjG,MAAM,aAAa,MAAM;GACzB,MAAM,cAAc,MAAM;GAC1B,MAAM,YAAY,EAAE;GAEpB,OAAO,IAAI,SAA8B,SAAS,WAAW;IAC3D,QAAQ,IAAI,WAAW;KAAE;KAAS;KAAQ;KAAI;KAAI;KAAY;IAAY,CAAC;IAC3E,MAAM,UAAgC;KAAE;KAAW;KAAI;KAAI;KAAY;IAAY;IACnF,OAAO,YAAY,OAAO;GAC5B,CAAC;EACH;EAEA,UAAgB;GACd,IAAI,WAAW;GACf,YAAY;GACZ,MAAM,wBAAQ,IAAI,MAAM,qCAAqC;GAC7D,KAAK,MAAM,SAAS,QAAQ,OAAO,GACjC,MAAM,OAAO,KAAK;GAEpB,QAAQ,MAAM;GACd,OAAO,UAAU;EACnB;CACF;AACF"}
|
package/dist/esm/autoTile.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface AutoTileOptions {
|
|
|
11
11
|
*
|
|
12
12
|
* If omitted, group membership defaults to {@link WangSet.isMember} on the
|
|
13
13
|
* cell's `localTileId` (and a matching `tilesetIndex`). That default is
|
|
14
|
-
* *variant-stable*
|
|
14
|
+
* *variant-stable* - every autotiled variant counts as a member - which is
|
|
15
15
|
* what makes {@link refreshCell} correct. If you supply a `matchFn` for use
|
|
16
16
|
* with `refreshCell`, it MUST likewise be variant-stable (independent of the
|
|
17
17
|
* currently-rendered variant), e.g. keyed on a separate logical-terrain grid
|
|
@@ -54,16 +54,20 @@ export interface AutoTileOptions {
|
|
|
54
54
|
* combinations to 47 valid blob states).
|
|
55
55
|
*
|
|
56
56
|
* **Edge mode bitmask (bit positions):** Top=1, Right=2, Bottom=4, Left=8.
|
|
57
|
+
*
|
|
58
|
+
* @throws If `layer` is unbounded (no fixed width/height) - there is no
|
|
59
|
+
* "whole layer" to sweep. Use {@link refreshCell} to autotile an unbounded
|
|
60
|
+
* layer incrementally, one edited cell at a time.
|
|
57
61
|
*/
|
|
58
|
-
export declare
|
|
62
|
+
export declare const autoTile: (layer: TileLayer, wangSet: WangSet, options?: AutoTileOptions) => void;
|
|
59
63
|
/**
|
|
60
64
|
* Incrementally re-autotile a single cell and its eight neighbours after an
|
|
61
65
|
* edit, instead of re-running {@link autoTile} over the whole layer.
|
|
62
66
|
*
|
|
63
67
|
* A cell's blob/edge mask depends only on its immediate neighbours, so painting
|
|
64
68
|
* or erasing cell `(x, y)` can change the variant of that cell and of the (up
|
|
65
|
-
* to) eight cells that have it as a neighbour
|
|
66
|
-
* recomputes exactly that 3×3 neighbourhood, touching only the 1
|
|
69
|
+
* to) eight cells that have it as a neighbour - but nothing further out. This
|
|
70
|
+
* recomputes exactly that 3×3 neighbourhood, touching only the 1-4 chunks it
|
|
67
71
|
* spans (and rebuilding geometry only for chunks whose tiles actually change).
|
|
68
72
|
* For a paint operation this is O(1) work versus `autoTile`'s O(width·height).
|
|
69
73
|
*
|
|
@@ -80,4 +84,5 @@ export declare function autoTile(layer: TileLayer, wangSet: WangSet, options?: A
|
|
|
80
84
|
* @param wangSet The Wang set to resolve variants from.
|
|
81
85
|
* @param options Membership / border options (shared with {@link autoTile}).
|
|
82
86
|
*/
|
|
83
|
-
export declare
|
|
87
|
+
export declare const refreshCell: (layer: TileLayer, x: number, y: number, wangSet: WangSet, options?: AutoTileOptions) => void;
|
|
88
|
+
//# sourceMappingURL=autoTile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoTile.d.ts","sourceRoot":"","sources":["../../src/autoTile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IACvF;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA0ED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,QAAQ,GAAI,OAAO,SAAS,EAAE,SAAS,OAAO,EAAE,UAAU,eAAe,KAAG,IAuDxF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,SAAS,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,SAAS,OAAO,EAAE,UAAU,eAAe,KAAG,IA0BjH,CAAC"}
|