@aethervtt/shard-sprite 0.0.1 → 0.2.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/dist/index.js CHANGED
@@ -23,6 +23,16 @@ import {
23
23
  SpriteClipStore,
24
24
  SpriteClips
25
25
  } from "./clip.js";
26
+ import {
27
+ GROUND_TILE_SHADERS,
28
+ GroundTiles,
29
+ syncGroundTiles,
30
+ TileChunk,
31
+ TileLit,
32
+ TileUnlit,
33
+ tileDetail,
34
+ tilemapOnGround
35
+ } from "./ground.js";
26
36
  import {
27
37
  Lighting2d,
28
38
  LightOccluder2d,
@@ -51,6 +61,12 @@ import {
51
61
  TILE_PIXELS,
52
62
  TILE_STRIDE
53
63
  } from "./lights2d.js";
64
+ import {
65
+ editTiles,
66
+ findTilemapData,
67
+ readTiles,
68
+ tilemapMethods
69
+ } from "./methods.js";
54
70
  import {
55
71
  boxPolygon,
56
72
  capsulePolygon,
@@ -68,6 +84,7 @@ import { spritePlugin } from "./plugin.js";
68
84
  import {
69
85
  describeSprites,
70
86
  prepareSprites,
87
+ resolveTiles,
71
88
  SPRITE_FLOATS,
72
89
  SpriteStore,
73
90
  Sprites,
@@ -94,12 +111,25 @@ import {
94
111
  TilemapDataSchema,
95
112
  TilemapDataStore,
96
113
  TilemapDatas,
97
- tileAt
114
+ tileAt,
115
+ tilemapToText,
116
+ tileNames,
117
+ unknownTiles
98
118
  } from "./tilemap.js";
119
+ import {
120
+ chunkRows,
121
+ readChunkRows,
122
+ rectRows,
123
+ rowsCanName,
124
+ TILE_FLAG_NAMES
125
+ } from "./tilemap-format.js";
126
+ import { validateTilemaps } from "./validate.js";
99
127
  export {
100
128
  AtlasPackImporter,
101
129
  AtlasPackSchema,
102
130
  CLIP_LOOPS,
131
+ GROUND_TILE_SHADERS,
132
+ GroundTiles,
103
133
  LIGHT2D_FLOATS,
104
134
  LightOccluder2d,
105
135
  LightView2d,
@@ -132,6 +162,7 @@ export {
132
162
  SpriteSlot,
133
163
  SpriteStore,
134
164
  Sprites,
165
+ TILE_FLAG_NAMES,
135
166
  TILE_MAX,
136
167
  TILE_PIXELS,
137
168
  TILE_STRIDE,
@@ -141,8 +172,11 @@ export {
141
172
  TextureAtlasSchema,
142
173
  TextureAtlasStore,
143
174
  TextureAtlases,
175
+ TileChunk,
144
176
  TileFlags,
145
177
  TileLayer,
178
+ TileLit,
179
+ TileUnlit,
146
180
  Tilemap,
147
181
  TilemapData,
148
182
  TilemapDataAssetType,
@@ -157,11 +191,14 @@ export {
157
191
  binLightsCpu,
158
192
  boxPolygon,
159
193
  capsulePolygon,
194
+ chunkRows,
160
195
  circlePolygon,
161
196
  convexHull,
162
197
  describeLights2d,
163
198
  describeSprites,
164
199
  distanceToLoop,
200
+ editTiles,
201
+ findTilemapData,
165
202
  layerBit,
166
203
  lightCircle,
167
204
  loopArea,
@@ -172,15 +209,28 @@ export {
172
209
  polygonSegments,
173
210
  prepareLights2d,
174
211
  prepareSprites,
212
+ readChunkRows,
213
+ readTiles,
214
+ rectRows,
175
215
  regionOutline,
216
+ resolveTiles,
217
+ rowsCanName,
176
218
  setTile,
177
219
  shadowFactorCpu,
178
220
  shadowRowCpu,
179
221
  simplifyLoop,
180
222
  spriteOutline,
181
223
  spritePlugin,
224
+ syncGroundTiles,
182
225
  tileAt,
183
226
  tileChunkEdges,
227
+ tileDetail,
228
+ tileNames,
229
+ tilemapMethods,
230
+ tilemapOnGround,
231
+ tilemapToText,
184
232
  traceAlpha,
185
- transformSegments
233
+ transformSegments,
234
+ unknownTiles,
235
+ validateTilemaps
186
236
  };
@@ -53,11 +53,11 @@ struct Segment2d {
53
53
  import shard::sprite::light2d::types::{ Light2d, Lighting2dView, TILE_PIXELS, TILE_STRIDE, TILE_MAX, SHADOW_RES, COARSE_RES, COARSE_SPAN, TAU, PI };
54
54
 
55
55
  @group(3) @binding(0) var<uniform> light_view: Lighting2dView;
56
- @group(3) @binding(1) var<storage, read> lights: array<Light2d>;
57
- @group(3) @binding(2) var<storage, read> light_tiles: array<u32>;
58
- @group(3) @binding(3) var<storage, read> shadow_map: array<u32>;
56
+ @data @group(3) @binding(1) var<storage, read> lights: array<Light2d>;
57
+ @data @group(3) @binding(2) var<storage, read> light_tiles: array<u32>;
58
+ @data @group(3) @binding(3) var<storage, read> shadow_map: array<u32>;
59
59
  /** Per row, per 32 angles: the nearest and farthest occluder distance. */
60
- @group(3) @binding(4) var<storage, read> shadow_coarse: array<vec2f>;
60
+ @data @group(3) @binding(4) var<storage, read> shadow_coarse: array<vec2f>;
61
61
 
62
62
  /** Min and max over the coarse bin holding continuous bin coordinate x. */
63
63
  fn coarse_at(row: u32, x: f32) -> vec2f {
@@ -1,6 +1,6 @@
1
1
  import { type Entity, type Query, type World } from '@aethervtt/shard-core';
2
2
  import { GpuBuffer, type GpuContext } from '@aethervtt/shard-gpu';
3
- import { type NodeDescriptor } from '@aethervtt/shard-render';
3
+ import { DataStore, type NodeDescriptor } from '@aethervtt/shard-render';
4
4
  import { type TileLayer } from './tilemap.js';
5
5
  /** Floats per light record (see `Light2d` in lighting-shaders.ts). */
6
6
  export declare const LIGHT2D_FLOATS = 16;
@@ -20,6 +20,11 @@ export declare const MAX_SHADOWED = 64;
20
20
  * reaches the view. Works for any camera: the radius is the larger of the projected x and y offsets.
21
21
  */
22
22
  export declare function lightCircle(viewProj: ArrayLike<number>, width: number, height: number, x: number, y: number, z: number, r: number, out: Float32Array, o: number): boolean;
23
+ /**
24
+ * Bins light circles into 16×16-pixel tiles, like the GPU, in its layout: per tile TILE_STRIDE
25
+ * u32s, the full count, then up to 64 light indices. Allocates nothing.
26
+ */
27
+ export declare function binLightsInto(circles: Float32Array, count: number, tilesX: number, tilesY: number, out: Uint32Array): void;
23
28
  /** Bins light circles into 16×16-pixel tiles, like the GPU: a full count and up to 64 indices. */
24
29
  export declare function binLightsCpu(circles: Float32Array, count: number, tilesX: number, tilesY: number): {
25
30
  counts: Uint32Array;
@@ -30,6 +35,11 @@ export declare function binLightsCpu(circles: Float32Array, count: number, tiles
30
35
  * distance plus its penetration. `segments` holds SEGMENT_FLOATS per segment.
31
36
  */
32
37
  export declare function shadowRowCpu(lx: number, ly: number, radius: number, layers: number, segments: Float32Array, count: number, out: Float32Array): Float32Array;
38
+ /**
39
+ * One light's shadow row written into `out` from `at`: shadowRowCpu's math, allocating nothing.
40
+ * `bits` views `segments` as u32 (their layer masks).
41
+ */
42
+ export declare function writeShadowRow(lx: number, ly: number, radius: number, layers: number, segments: Float32Array, bits: Uint32Array, count: number, out: Float32Array, at: number): void;
33
43
  /** How lit a point is by a shadow row: the fragment shader's `shadow2d`, on the CPU. */
34
44
  export declare function shadowFactorCpu(row: Float32Array, softness: number, d: number, toX: number, toY: number): number;
35
45
  /** One occluder entity's segments, cached in world space until it changes. */
@@ -117,17 +127,23 @@ export declare class LightView2d {
117
127
  constructor(name: string);
118
128
  ensureLights(n: number): void;
119
129
  ensureSegments(n: number): void;
120
- /** Uploads what changed. Segments go up only when the selection or an occluder changed. */
130
+ /**
131
+ * Uploads what changed. Segments go up only when the selection or an occluder changed. On
132
+ * baseline the tiles, shadow rows and coarse bins are made here on the CPU (baseline/lights2d.ts).
133
+ */
121
134
  upload(gpu: GpuContext, segmentKey: number): void;
122
135
  }
123
- interface ViewGpu {
136
+ /** Loads the baseline tier's CPU lighting (0064), once: the sprite plugin does, on a baseline device. */
137
+ export declare function loadBaselineLighting(): Promise<void>;
138
+ export interface ViewGpu {
124
139
  generation: number;
125
140
  uniform: GpuBuffer;
126
- lights: GpuBuffer;
141
+ /** What the fragment stage reads: storage on the full tier, data textures on baseline (0064). */
142
+ lights: DataStore;
127
143
  circles: GpuBuffer;
128
- tiles: GpuBuffer;
129
- shadowMap: GpuBuffer;
130
- shadowCoarse: GpuBuffer;
144
+ tiles: DataStore;
145
+ shadowMap: DataStore;
146
+ shadowCoarse: DataStore;
131
147
  segments: GpuBuffer;
132
148
  shadowed: GpuBuffer;
133
149
  computeGroup?: {
package/dist/lights2d.js CHANGED
@@ -9,6 +9,8 @@ import { GpuBuffer } from "@aethervtt/shard-gpu";
9
9
  import {
10
10
  Cameras,
11
11
  ComputedVisibility,
12
+ DataStore,
13
+ dataEntry,
12
14
  defineOverlay,
13
15
  Gpu,
14
16
  RenderPhase,
@@ -59,30 +61,50 @@ function lightCircle(viewProj, width, height, x, y, z, r, out, o) {
59
61
  out[o + 3] = 0;
60
62
  return px + rp > 0 && px - rp < width && py + rp > 0 && py - rp < height;
61
63
  }
62
- function binLightsCpu(circles, count, tilesX, tilesY) {
63
- const counts = new Uint32Array(tilesX * tilesY);
64
- const indices = new Uint32Array(tilesX * tilesY * TILE_MAX);
65
- for (let t = 0; t < tilesX * tilesY; t++) {
66
- const lx = t % tilesX * TILE_PIXELS;
67
- const ly = Math.floor(t / tilesX) * TILE_PIXELS;
68
- let n = 0;
69
- for (let i = 0; i < count; i++) {
70
- const cx = circles[i * 4];
71
- const cy = circles[i * 4 + 1];
72
- const r = circles[i * 4 + 2];
73
- const qx = Math.min(Math.max(cx, lx), lx + TILE_PIXELS) - cx;
64
+ function binLightsInto(circles, count, tilesX, tilesY, out) {
65
+ for (let t = 0; t < tilesX * tilesY; t++) out[t * TILE_STRIDE] = 0;
66
+ for (let i = 0; i < count; i++) {
67
+ const cx = circles[i * 4];
68
+ const cy = circles[i * 4 + 1];
69
+ const r = circles[i * 4 + 2];
70
+ const x0 = Math.max(0, Math.floor((cx - r) / TILE_PIXELS) - 1);
71
+ const x1 = Math.min(tilesX - 1, Math.floor((cx + r) / TILE_PIXELS));
72
+ const y0 = Math.max(0, Math.floor((cy - r) / TILE_PIXELS) - 1);
73
+ const y1 = Math.min(tilesY - 1, Math.floor((cy + r) / TILE_PIXELS));
74
+ for (let ty = y0; ty <= y1; ty++) {
75
+ const ly = ty * TILE_PIXELS;
74
76
  const qy = Math.min(Math.max(cy, ly), ly + TILE_PIXELS) - cy;
75
- if (qx * qx + qy * qy > r * r) continue;
76
- if (n < TILE_MAX) indices[t * TILE_MAX + n] = i;
77
- n++;
77
+ for (let tx = x0; tx <= x1; tx++) {
78
+ const lx = tx * TILE_PIXELS;
79
+ const qx = Math.min(Math.max(cx, lx), lx + TILE_PIXELS) - cx;
80
+ if (qx * qx + qy * qy > r * r) continue;
81
+ const base = (ty * tilesX + tx) * TILE_STRIDE;
82
+ const n = out[base];
83
+ if (n < TILE_MAX) out[base + 1 + n] = i;
84
+ out[base] = n + 1;
85
+ }
78
86
  }
79
- counts[t] = n;
87
+ }
88
+ }
89
+ function binLightsCpu(circles, count, tilesX, tilesY) {
90
+ const tiles = tilesX * tilesY;
91
+ const packed = new Uint32Array(tiles * TILE_STRIDE);
92
+ binLightsInto(circles, count, tilesX, tilesY, packed);
93
+ const counts = new Uint32Array(tiles);
94
+ const indices = new Uint32Array(tiles * TILE_MAX);
95
+ for (let t = 0; t < tiles; t++) {
96
+ counts[t] = packed[t * TILE_STRIDE];
97
+ indices.set(packed.subarray(t * TILE_STRIDE + 1, (t + 1) * TILE_STRIDE), t * TILE_MAX);
80
98
  }
81
99
  return { counts, indices };
82
100
  }
83
101
  function shadowRowCpu(lx, ly, radius, layers, segments, count, out) {
84
- out.fill(FAR, 0, SHADOW_RES);
85
102
  const bits = new Uint32Array(segments.buffer, segments.byteOffset, segments.length);
103
+ writeShadowRow(lx, ly, radius, layers, segments, bits, count, out, 0);
104
+ return out;
105
+ }
106
+ function writeShadowRow(lx, ly, radius, layers, segments, bits, count, out, at) {
107
+ out.fill(FAR, at, at + SHADOW_RES);
86
108
  const step = Math.PI * 2 / SHADOW_RES;
87
109
  for (let s = 0; s < count; s++) {
88
110
  const o = s * SEGMENT_FLOATS;
@@ -118,12 +140,11 @@ function shadowRowCpu(lx, ly, radius, layers, segments, count, out) {
118
140
  if (Math.abs(den) < 1e-12) continue;
119
141
  const t = (ax * ey - ay * ex) / den;
120
142
  if (t <= 0) continue;
121
- const w = (k % SHADOW_RES + SHADOW_RES) % SHADOW_RES;
143
+ const w = at + (k % SHADOW_RES + SHADOW_RES) % SHADOW_RES;
122
144
  const v = Math.fround(t + pen);
123
145
  if (v < out[w]) out[w] = v;
124
146
  }
125
147
  }
126
- return out;
127
148
  }
128
149
  function shadowFactorCpu(row, softness, d, toX, toY) {
129
150
  const step = Math.PI * 2 / SHADOW_RES;
@@ -441,7 +462,10 @@ class LightView2d {
441
462
  this.segments = s;
442
463
  this.segmentBits = new Uint32Array(s.buffer);
443
464
  }
444
- /** Uploads what changed. Segments go up only when the selection or an occluder changed. */
465
+ /**
466
+ * Uploads what changed. Segments go up only when the selection or an occluder changed. On
467
+ * baseline the tiles, shadow rows and coarse bins are made here on the CPU (baseline/lights2d.ts).
468
+ */
445
469
  upload(gpu, segmentKey) {
446
470
  let g = this.gpu;
447
471
  if (!g || g.generation !== gpu.generation) g = this.gpu = createViewGpu(gpu, this.name);
@@ -469,21 +493,28 @@ class LightView2d {
469
493
  g.segments.write(this.segments, 0, 0, this.segmentCount * SEGMENT_FLOATS);
470
494
  bytes += this.segmentCount * SEGMENT_FLOATS * 4;
471
495
  }
496
+ if (baseline && gpu.tier === "baseline")
497
+ bytes += baseline.lightOnCpu(this, g, segmentKey !== this.segmentKey);
472
498
  this.segmentKey = segmentKey;
473
499
  this.uploadedBytes = bytes;
474
500
  }
475
501
  }
502
+ let baseline;
503
+ async function loadBaselineLighting() {
504
+ baseline ??= await import("./baseline/lights2d.js");
505
+ }
476
506
  function createViewGpu(gpu, name) {
477
507
  const storage = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC;
478
508
  const buffer = (label, size, usage = storage) => new GpuBuffer(gpu, { label: `${name}/lights2d/${label}`, usage, size });
509
+ const data = (label, size) => new DataStore(gpu, { label: `${name}/lights2d/${label}`, usage: GPUBufferUsage.COPY_SRC, size });
479
510
  return {
480
511
  generation: gpu.generation,
481
512
  uniform: buffer("view", 48, GPUBufferUsage.UNIFORM),
482
- lights: buffer("lights", LIGHT2D_FLOATS * 4 * 64),
513
+ lights: data("lights", LIGHT2D_FLOATS * 4 * 64),
483
514
  circles: buffer("circles", 16 * 64),
484
- tiles: buffer("tiles", TILE_STRIDE * 4 * 1024),
485
- shadowMap: buffer("shadow-map", SHADOW_RES * 4 * 4),
486
- shadowCoarse: buffer("shadow-coarse", COARSE_RES * 8 * 4),
515
+ tiles: data("tiles", TILE_STRIDE * 4 * 1024),
516
+ shadowMap: data("shadow-map", SHADOW_RES * 4 * 4),
517
+ shadowCoarse: data("shadow-coarse", COARSE_RES * 8 * 4),
487
518
  segments: buffer("segments", SEGMENT_FLOATS * 4 * 256),
488
519
  shadowed: buffer("shadowed", MAX_SHADOWED * 4)
489
520
  };
@@ -825,10 +856,10 @@ function layoutsOf(gpu) {
825
856
  label: "lights2d/shade",
826
857
  entries: [
827
858
  { binding: 0, visibility: F, buffer: { type: "uniform" } },
828
- { binding: 1, visibility: F, buffer: read },
829
- { binding: 2, visibility: F, buffer: read },
830
- { binding: 3, visibility: F, buffer: read },
831
- { binding: 4, visibility: F, buffer: read }
859
+ dataEntry(gpu, 1, F),
860
+ dataEntry(gpu, 2, F),
861
+ dataEntry(gpu, 3, F),
862
+ dataEntry(gpu, 4, F)
832
863
  ]
833
864
  }),
834
865
  pipelines: /* @__PURE__ */ new Map()
@@ -855,7 +886,7 @@ function litView(world, view) {
855
886
  }
856
887
  function lights2dGroup(gpu, view) {
857
888
  const g = view.gpu;
858
- const key = `${idOf(g.uniform.buffer)}/${idOf(g.lights.buffer)}/${idOf(g.tiles.buffer)}/${idOf(g.shadowMap.buffer)}/${idOf(g.shadowCoarse.buffer)}`;
889
+ const key = `${idOf(g.uniform.buffer)}/${dataKey(g.lights)}/${dataKey(g.tiles)}/${dataKey(g.shadowMap)}/${dataKey(g.shadowCoarse)}`;
859
890
  if (!g.renderGroup || g.renderGroup.key !== key) {
860
891
  g.renderGroup = {
861
892
  key,
@@ -864,16 +895,19 @@ function lights2dGroup(gpu, view) {
864
895
  layout: layoutsOf(gpu).render,
865
896
  entries: [
866
897
  { binding: 0, resource: { buffer: g.uniform.buffer } },
867
- { binding: 1, resource: { buffer: g.lights.buffer } },
868
- { binding: 2, resource: { buffer: g.tiles.buffer } },
869
- { binding: 3, resource: { buffer: g.shadowMap.buffer } },
870
- { binding: 4, resource: { buffer: g.shadowCoarse.buffer } }
898
+ { binding: 1, resource: g.lights.resource() },
899
+ { binding: 2, resource: g.tiles.resource() },
900
+ { binding: 3, resource: g.shadowMap.resource() },
901
+ { binding: 4, resource: g.shadowCoarse.resource() }
871
902
  ]
872
903
  })
873
904
  };
874
905
  }
875
906
  return g.renderGroup.group;
876
907
  }
908
+ function dataKey(d) {
909
+ return `${idOf(d)}:${d.version}`;
910
+ }
877
911
  function computePipeline(ctx, entry) {
878
912
  const gpu = ctx.gpu;
879
913
  const s = layoutsOf(gpu);
@@ -896,7 +930,8 @@ function lights2dNode(world) {
896
930
  return {
897
931
  kind: "compute",
898
932
  phase: RenderPhase.Sprites - 10,
899
- enabled: (view) => litView(world, view.name) !== void 0,
933
+ // Compute is the full tier's: baseline lights on the CPU as it uploads (LightView2d.upload).
934
+ enabled: (view) => litView(world, view.name) !== void 0 && world.resource(Gpu).tier === "full",
900
935
  writes: ["lights2d"],
901
936
  run: (ctx) => {
902
937
  const view = litView(ctx.world, ctx.view.name);
@@ -907,7 +942,7 @@ function lights2dNode(world) {
907
942
  const coarse = computePipeline(ctx, "coarse");
908
943
  if (!bin || !clear || !shadows || !coarse) return;
909
944
  const g = view.gpu;
910
- const key = `${idOf(g.uniform.buffer)}/${idOf(g.lights.buffer)}/${idOf(g.circles.buffer)}/${idOf(g.tiles.buffer)}/${idOf(g.shadowMap.buffer)}/${idOf(g.segments.buffer)}/${idOf(g.shadowed.buffer)}/${idOf(g.shadowCoarse.buffer)}`;
945
+ const key = `${idOf(g.uniform.buffer)}/${idOf(g.lights.gpuBuffer)}/${idOf(g.circles.buffer)}/${idOf(g.tiles.gpuBuffer)}/${idOf(g.shadowMap.gpuBuffer)}/${idOf(g.segments.buffer)}/${idOf(g.shadowed.buffer)}/${idOf(g.shadowCoarse.gpuBuffer)}`;
911
946
  if (!g.computeGroup || g.computeGroup.key !== key) {
912
947
  g.computeGroup = {
913
948
  key,
@@ -916,13 +951,13 @@ function lights2dNode(world) {
916
951
  layout: layoutsOf(ctx.gpu).compute,
917
952
  entries: [
918
953
  { binding: 0, resource: { buffer: g.uniform.buffer } },
919
- { binding: 1, resource: { buffer: g.lights.buffer } },
954
+ { binding: 1, resource: { buffer: g.lights.gpuBuffer } },
920
955
  { binding: 2, resource: { buffer: g.circles.buffer } },
921
- { binding: 3, resource: { buffer: g.tiles.buffer } },
922
- { binding: 4, resource: { buffer: g.shadowMap.buffer } },
956
+ { binding: 3, resource: { buffer: g.tiles.gpuBuffer } },
957
+ { binding: 4, resource: { buffer: g.shadowMap.gpuBuffer } },
923
958
  { binding: 5, resource: { buffer: g.segments.buffer } },
924
959
  { binding: 6, resource: { buffer: g.shadowed.buffer } },
925
- { binding: 7, resource: { buffer: g.shadowCoarse.buffer } }
960
+ { binding: 7, resource: { buffer: g.shadowCoarse.gpuBuffer } }
926
961
  ]
927
962
  })
928
963
  };
@@ -1080,6 +1115,7 @@ export {
1080
1115
  TILE_PIXELS,
1081
1116
  TILE_STRIDE,
1082
1117
  binLightsCpu,
1118
+ binLightsInto,
1083
1119
  describeLights2d,
1084
1120
  lightCircle,
1085
1121
  lights2dGroup,
@@ -1087,7 +1123,9 @@ export {
1087
1123
  lights2dNode,
1088
1124
  lights2dOverlay,
1089
1125
  litView,
1126
+ loadBaselineLighting,
1090
1127
  prepareLights2d,
1091
1128
  shadowFactorCpu,
1092
- shadowRowCpu
1129
+ shadowRowCpu,
1130
+ writeShadowRow
1093
1131
  };
@@ -0,0 +1,82 @@
1
+ import { type World } from '@aethervtt/shard-core';
2
+ import type { AppMethod } from '@aethervtt/shard-runtime';
3
+ import { type TextureAtlas } from './atlas.js';
4
+ import { TilemapData } from './tilemap.js';
5
+ export interface TileRect {
6
+ x: number;
7
+ y: number;
8
+ w: number;
9
+ h: number;
10
+ }
11
+ export interface ReadTilesOptions {
12
+ /** Layer name (default: the first). */
13
+ layer?: string;
14
+ /** Just this chunk ([cx, cy], in the data's chunkSize). */
15
+ chunk?: readonly number[];
16
+ /** Just this rect. */
17
+ rect?: TileRect;
18
+ }
19
+ export interface EditTilesOptions {
20
+ layer?: string;
21
+ /** Cells to set: tile by palette name ('.' clears), flags as "fx+r90". */
22
+ cells?: readonly {
23
+ x: number;
24
+ y: number;
25
+ tile: string;
26
+ flags?: string | number;
27
+ }[];
28
+ /** A rect filled with one tile. */
29
+ fill?: {
30
+ rect: TileRect;
31
+ tile: string;
32
+ };
33
+ /** One chunk's cells replaced by rows, as readTiles gives them. */
34
+ rows?: {
35
+ chunk: readonly number[];
36
+ rows: readonly string[];
37
+ };
38
+ }
39
+ /**
40
+ * A layer's cells as rows: all non-empty chunks (keyed "cx,cy"), one chunk, or a rect. Version 1
41
+ * maps name their tiles through the atlas.
42
+ */
43
+ export declare function readTiles(data: TilemapData, options?: ReadTilesOptions, atlas?: {
44
+ names: readonly string[];
45
+ }): {
46
+ layer: string;
47
+ width: number;
48
+ height: number;
49
+ chunkSize: number;
50
+ encoding: import("./tilemap-format.js").TileEncoding;
51
+ rect: TileRect;
52
+ rows: string[];
53
+ } | {
54
+ layer: string;
55
+ width: number;
56
+ height: number;
57
+ chunkSize: number;
58
+ encoding: import("./tilemap-format.js").TileEncoding;
59
+ chunk: number[];
60
+ rows: string[];
61
+ } | {
62
+ layer: string;
63
+ width: number;
64
+ height: number;
65
+ chunkSize: number;
66
+ encoding: import("./tilemap-format.js").TileEncoding;
67
+ chunks: Record<string, string[]>;
68
+ };
69
+ /**
70
+ * Applies cell edits through TileLayer.set, so renderers re-upload only the touched chunks.
71
+ * Returns how many cells changed. A version 1 map first gets a palette of the atlas's names.
72
+ */
73
+ export declare function editTiles(data: TilemapData, options: EditTilesOptions, atlas?: {
74
+ names: readonly string[];
75
+ }): number;
76
+ /** A loaded tilemap data asset, and the atlas of a Tilemap drawing it (for version 1 names). */
77
+ export declare function findTilemapData(world: World, asset: string): {
78
+ data: TilemapData;
79
+ guid: string | undefined;
80
+ atlas: TextureAtlas | undefined;
81
+ };
82
+ export declare const tilemapMethods: AppMethod[];