@codexo/exojs-tilemap 0.15.2 → 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
@@ -0,0 +1,190 @@
1
+ import type { ObjectPoint, TileMapObject } from './ObjectLayer';
2
+ import { ObjectKind } from './ObjectLayer';
3
+ import type { TileLayer } from './TileLayer';
4
+ /**
5
+ * The geometry kinds a {@link TileCollisionShape} can carry. Tile (GID) and
6
+ * text objects describe no collision area and are never emitted, so they are
7
+ * excluded from the union.
8
+ * @advanced
9
+ */
10
+ export type TileCollisionShapeKind = typeof ObjectKind.Rectangle | typeof ObjectKind.Ellipse | typeof ObjectKind.Polygon | typeof ObjectKind.Polyline | typeof ObjectKind.Point;
11
+ /**
12
+ * A half-open tile-coordinate rectangle covering
13
+ * `[x, x + width)` × `[y, y + height)`.
14
+ *
15
+ * Used to scope {@link buildTileCollisionGeometry} to part of a layer - a
16
+ * streamed chunk, the tiles around the player, a hand-picked room - instead of
17
+ * always rebuilding the whole map.
18
+ * @advanced
19
+ */
20
+ export interface TileRegion {
21
+ /** Leftmost tile column (inclusive). */
22
+ readonly x: number;
23
+ /** Topmost tile row (inclusive). */
24
+ readonly y: number;
25
+ /** Width in tiles. */
26
+ readonly width: number;
27
+ /** Height in tiles. */
28
+ readonly height: number;
29
+ }
30
+ /**
31
+ * An axis-aligned collision rectangle in tile-layer pixel space (+Y down),
32
+ * covering one or more whole tile cells.
33
+ *
34
+ * Rectangles are synthesized geometry, not source objects: a merged run has no
35
+ * single originating object, so it carries no object `id`/`name`/`properties` -
36
+ * only the `type` string shared by every shape merged into it. Anything that
37
+ * does not exactly cover whole cells stays a {@link TileCollisionShape}, which
38
+ * does keep its source object.
39
+ * @advanced
40
+ */
41
+ export interface TileCollisionRect {
42
+ /** Left edge in layer pixel space. */
43
+ readonly x: number;
44
+ /** Top edge in layer pixel space. */
45
+ readonly y: number;
46
+ /** Width in pixels (a whole number of tile cells). */
47
+ readonly width: number;
48
+ /** Height in pixels (a whole number of tile cells). */
49
+ readonly height: number;
50
+ /** Class/type string shared by every source shape merged into this rectangle. */
51
+ readonly type: string;
52
+ }
53
+ /**
54
+ * A per-tile collision shape placed in tile-layer pixel space (+Y down).
55
+ *
56
+ * Emitted for every collision shape that does not exactly cover a whole tile
57
+ * cell: partial boxes, ellipses, polygons, polylines, points, and rotated
58
+ * geometry. Shapes are never merged with one another.
59
+ * @advanced
60
+ */
61
+ export interface TileCollisionShape {
62
+ /** Geometry discriminant. */
63
+ readonly kind: TileCollisionShapeKind;
64
+ /** X of the shape origin in layer pixel space. */
65
+ readonly x: number;
66
+ /** Y of the shape origin in layer pixel space. */
67
+ readonly y: number;
68
+ /** Bounding width in px (`0` for polygons, polylines and points). */
69
+ readonly width: number;
70
+ /** Bounding height in px (`0` for polygons, polylines and points). */
71
+ readonly height: number;
72
+ /**
73
+ * Rotation in degrees, clockwise, about `(x, y)`, normalised to `[0, 360)`.
74
+ * Always `0` for polygons and polylines - their rotation is baked into
75
+ * {@link points}.
76
+ */
77
+ readonly rotation: number;
78
+ /** Polygon/polyline vertices relative to `(x, y)`; absent for other kinds. */
79
+ readonly points?: readonly ObjectPoint[];
80
+ /** Tile column the shape came from. */
81
+ readonly tx: number;
82
+ /** Tile row the shape came from. */
83
+ readonly ty: number;
84
+ /** The tile-local source object, for `name`/`type`/`properties` lookups. */
85
+ readonly source: TileMapObject;
86
+ }
87
+ /**
88
+ * Collision geometry extracted from a tile layer: whole-cell boxes merged into
89
+ * as few {@link TileCollisionRect}s as a greedy pass can manage, plus every
90
+ * other shape passed through individually.
91
+ * @advanced
92
+ */
93
+ export interface TileCollisionGeometry {
94
+ /** Merged whole-cell rectangles, in row-major order. */
95
+ readonly rects: readonly TileCollisionRect[];
96
+ /** Non-whole-cell shapes, in the order their tiles were walked. */
97
+ readonly shapes: readonly TileCollisionShape[];
98
+ }
99
+ /**
100
+ * Per-cell collision classification for a tile layer, independent of the tiles
101
+ * the layer renders.
102
+ *
103
+ * Returns the class/type string for the cell, or `null` when the cell carries
104
+ * no collision. The string is opaque: it is the merge key for adjacent cells
105
+ * and reaches the consumer unchanged, exactly like the `type` of a tile
106
+ * collision object.
107
+ *
108
+ * A source is sampled while geometry is being built and is expected to answer
109
+ * identically for as long as the geometry is used. Changing what it returns
110
+ * does not invalidate geometry that was already built; rebuild instead.
111
+ * @advanced
112
+ */
113
+ export type TileCellSource = (tx: number, ty: number) => string | null;
114
+ /**
115
+ * Options for {@link buildTileCollisionGeometry}.
116
+ * @advanced
117
+ */
118
+ export interface TileCollisionOptions {
119
+ /**
120
+ * Tile-coordinate region to walk. Defaults to the bounding box of the
121
+ * layer's currently loaded chunks, so an unbounded (streamed) layer works
122
+ * without the caller computing anything - or, with {@link cells} on a
123
+ * bounded layer, to the layer's full tile extent.
124
+ */
125
+ readonly region?: TileRegion;
126
+ /**
127
+ * Second occupancy source, consulted once per cell of the walked region in
128
+ * addition to the tiles' own collision shapes. Lets collision authored per
129
+ * cell rather than per tile reach the same geometry, without the layer
130
+ * having to render a tile there.
131
+ */
132
+ readonly cells?: TileCellSource | undefined;
133
+ /**
134
+ * Merge adjacent whole-cell boxes that share a `type` into larger
135
+ * rectangles. Default `true`. With `false`, every whole-cell box becomes its
136
+ * own single-cell rectangle.
137
+ */
138
+ readonly merge?: boolean;
139
+ /**
140
+ * Drop source shapes before any geometry is built (e.g. by a custom
141
+ * property). Return `true` to keep a shape. Default keeps all of them.
142
+ */
143
+ readonly accept?: (object: TileMapObject, tx: number, ty: number) => boolean;
144
+ }
145
+ /**
146
+ * Extract collision geometry from a tile layer's per-tile
147
+ * {@link import('./types').TileDefinition.collision} shapes.
148
+ *
149
+ * Every placed tile in `region` is resolved back to its tileset definition, and
150
+ * each collision shape is transformed from tile-local pixel space into layer
151
+ * pixel space (+Y down) - applying the tile's flip/rotation transform, the
152
+ * tileset draw offset, and the layer's pixel offset, so the geometry lands
153
+ * exactly where the tile is drawn.
154
+ *
155
+ * Shapes that end up covering exactly one whole tile cell are collected into an
156
+ * occupancy grid and merged into as few {@link TileCollisionRect}s as a greedy
157
+ * pass manages - a 200×200 solid region becomes one rectangle instead of 40 000.
158
+ * Cells only merge with neighbours whose source object carries the same `type`
159
+ * string, so a `water` run never fuses into an adjacent `solid` run. Everything
160
+ * else (partial boxes, ellipses, polygons, polylines, points, rotated geometry)
161
+ * passes through individually as a {@link TileCollisionShape}.
162
+ *
163
+ * A {@link TileCollisionOptions.cells} source feeds the same occupancy grid
164
+ * from outside the tiles, for collision authored per cell rather than per tile.
165
+ * It is consulted first, so a whole-cell tile box landing on a cell the source
166
+ * already claimed passes through as a shape rather than overwriting it. With a
167
+ * cell source and no explicit region, a bounded layer walks its full tile
168
+ * extent - a layer that renders nothing at all still produces geometry.
169
+ *
170
+ * This package has no physics dependency and builds no bodies: the result is
171
+ * plain geometry. Turning it into colliders is a short loop in application code.
172
+ *
173
+ * @param layer - The layer to walk.
174
+ * @param options - Region scoping, merge toggle, a source-shape filter, and an
175
+ * optional per-cell occupancy source.
176
+ * @returns Merged rectangles plus pass-through shapes; both empty when the
177
+ * region holds no tile carrying collision data.
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const { rects, shapes } = buildTileCollisionGeometry(layer);
182
+ *
183
+ * for (const rect of rects) {
184
+ * world.add(makeStaticBox(rect.x, rect.y, rect.width, rect.height));
185
+ * }
186
+ * ```
187
+ * @advanced
188
+ */
189
+ export declare const buildTileCollisionGeometry: (layer: TileLayer, options?: TileCollisionOptions) => TileCollisionGeometry;
190
+ //# sourceMappingURL=tileCollision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tileCollision.d.ts","sourceRoot":"","sources":["../../src/tileCollision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,367 @@
1
+ import { ObjectKind } from "./ObjectLayer.js";
2
+
3
+ //#region src/tileCollision.ts
4
+ const DEGREES_TO_RADIANS = Math.PI / 180;
5
+ /** Normalise degrees into `[0, 360)`. */
6
+ const normaliseDegrees = (degrees) => {
7
+ const wrapped = degrees % 360;
8
+ return wrapped < 0 ? wrapped + 360 : wrapped;
9
+ };
10
+ /**
11
+ * Cosine of an angle in degrees, exact on the quadrant multiples.
12
+ * `Math.cos(Math.PI / 2)` is `6.1e-17`, not `0`; tile transforms produce those
13
+ * quadrant angles constantly, and the whole-cell test below compares
14
+ * coordinates exactly, so the noise has to stay out of the arithmetic.
15
+ */
16
+ const cosDegrees = (degrees) => {
17
+ const angle = normaliseDegrees(degrees);
18
+ if (angle === 0) return 1;
19
+ if (angle === 90 || angle === 270) return 0;
20
+ if (angle === 180) return -1;
21
+ return Math.cos(angle * DEGREES_TO_RADIANS);
22
+ };
23
+ /** Sine of an angle in degrees, exact on the quadrant multiples. */
24
+ const sinDegrees = (degrees) => {
25
+ const angle = normaliseDegrees(degrees);
26
+ if (angle === 0 || angle === 180) return 0;
27
+ if (angle === 90) return 1;
28
+ if (angle === 270) return -1;
29
+ return Math.sin(angle * DEGREES_TO_RADIANS);
30
+ };
31
+ /** `true` when the transform mirrors (an odd number of reflections). */
32
+ const isMirrored = (transform) => {
33
+ return ((transform.flipX ? 1 : 0) + (transform.flipY ? 1 : 0) + (transform.diagonal ? 1 : 0)) % 2 === 1;
34
+ };
35
+ /**
36
+ * Rotation in degrees contributed by a non-mirroring transform. Only the four
37
+ * even-parity transforms are rotations: identity, `flipX+flipY` (180°),
38
+ * `diagonal+flipX` (90°) and `diagonal+flipY` (270°).
39
+ */
40
+ const transformRotation = (transform) => {
41
+ if (transform.diagonal) return transform.flipX ? 90 : 270;
42
+ return transform.flipX ? 180 : 0;
43
+ };
44
+ /**
45
+ * Map a point from tile-local pixel space into the transformed tile-local
46
+ * space of a placed tile. The diagonal flip (a transpose of the axes, which
47
+ * also swaps the box dimensions) is applied first, then the axis mirrors -
48
+ * the same order the renderer's orientation code implies.
49
+ */
50
+ const mapLocalPoint = (px, py, boxWidth, boxHeight, transform) => {
51
+ let x = px;
52
+ let y = py;
53
+ let width = boxWidth;
54
+ let height = boxHeight;
55
+ if (transform.diagonal) {
56
+ const swapped = x;
57
+ x = y;
58
+ y = swapped;
59
+ width = boxHeight;
60
+ height = boxWidth;
61
+ }
62
+ if (transform.flipX) x = width - x;
63
+ if (transform.flipY) y = height - y;
64
+ return {
65
+ x,
66
+ y
67
+ };
68
+ };
69
+ /**
70
+ * Layer-space position of a placed tile's top-left image corner. Mirrors the
71
+ * renderer's placement (see `chunkGeometry`): the tileset's draw offset shifts
72
+ * every tile, and a tile taller than the layer's cell is bottom-aligned within
73
+ * it, so collision geometry lands exactly where the tile is drawn.
74
+ */
75
+ const tileAnchor = (layer, tileset, tx, ty) => ({
76
+ x: tx * layer.tileWidth + layer.offsetX + tileset.offsetX,
77
+ y: ty * layer.tileHeight + layer.offsetY + layer.tileHeight - tileset.tileHeight + tileset.offsetY
78
+ });
79
+ /**
80
+ * Place one tile-local collision object into layer pixel space, applying the
81
+ * tile's flip/rotation transform, the tileset draw offset and the layer offset.
82
+ * Returns `null` for kinds that carry no collision geometry.
83
+ */
84
+ const placeShape = (object, layer, tileset, transform, tx, ty) => {
85
+ if (object.kind === ObjectKind.Tile || object.kind === ObjectKind.Text) return null;
86
+ const anchor = tileAnchor(layer, tileset, tx, ty);
87
+ const boxWidth = tileset.tileWidth;
88
+ const boxHeight = tileset.tileHeight;
89
+ const mirrored = isMirrored(transform);
90
+ const sourceCos = cosDegrees(object.rotation);
91
+ const sourceSin = sinDegrees(object.rotation);
92
+ if (object.kind === ObjectKind.Polygon || object.kind === ObjectKind.Polyline) {
93
+ const origin = mapLocalPoint(object.x, object.y, boxWidth, boxHeight, transform);
94
+ const originX = anchor.x + origin.x;
95
+ const originY = anchor.y + origin.y;
96
+ const points = object.points.map((point) => {
97
+ const localX = object.x + (sourceCos * point.x - sourceSin * point.y);
98
+ const localY = object.y + (sourceSin * point.x + sourceCos * point.y);
99
+ const mapped = mapLocalPoint(localX, localY, boxWidth, boxHeight, transform);
100
+ return {
101
+ x: anchor.x + mapped.x - originX,
102
+ y: anchor.y + mapped.y - originY
103
+ };
104
+ });
105
+ return {
106
+ kind: object.kind,
107
+ x: originX,
108
+ y: originY,
109
+ width: 0,
110
+ height: 0,
111
+ rotation: 0,
112
+ points: mirrored ? points.reverse() : points
113
+ };
114
+ }
115
+ if (object.kind === ObjectKind.Point) {
116
+ const mapped = mapLocalPoint(object.x, object.y, boxWidth, boxHeight, transform);
117
+ return {
118
+ kind: ObjectKind.Point,
119
+ x: anchor.x + mapped.x,
120
+ y: anchor.y + mapped.y,
121
+ width: 0,
122
+ height: 0,
123
+ rotation: 0
124
+ };
125
+ }
126
+ const halfWidth = object.width / 2;
127
+ const halfHeight = object.height / 2;
128
+ const centreLocalX = object.x + (sourceCos * halfWidth - sourceSin * halfHeight);
129
+ const centreLocalY = object.y + (sourceSin * halfWidth + sourceCos * halfHeight);
130
+ const centre = mapLocalPoint(centreLocalX, centreLocalY, boxWidth, boxHeight, transform);
131
+ const centreX = anchor.x + centre.x;
132
+ const centreY = anchor.y + centre.y;
133
+ const rotation = normaliseDegrees(mirrored ? -object.rotation : object.rotation + transformRotation(transform));
134
+ const swapAxes = mirrored && transform.diagonal;
135
+ const width = swapAxes ? object.height : object.width;
136
+ const height = swapAxes ? object.width : object.height;
137
+ if (rotation % 90 === 0) {
138
+ const axisWidth = rotation % 180 === 0 ? width : height;
139
+ const axisHeight = rotation % 180 === 0 ? height : width;
140
+ return {
141
+ kind: object.kind,
142
+ x: centreX - axisWidth / 2,
143
+ y: centreY - axisHeight / 2,
144
+ width: axisWidth,
145
+ height: axisHeight,
146
+ rotation: 0
147
+ };
148
+ }
149
+ const cos = cosDegrees(rotation);
150
+ const sin = sinDegrees(rotation);
151
+ return {
152
+ kind: object.kind,
153
+ x: centreX - (cos * (width / 2) - sin * (height / 2)),
154
+ y: centreY - (sin * (width / 2) + cos * (height / 2)),
155
+ width,
156
+ height,
157
+ rotation
158
+ };
159
+ };
160
+ /** The tile-coordinate bounding box of the layer's loaded chunks, or `null`. */
161
+ const loadedTileRegion = (layer) => {
162
+ let minTx = Number.POSITIVE_INFINITY;
163
+ let minTy = Number.POSITIVE_INFINITY;
164
+ let maxTx = Number.NEGATIVE_INFINITY;
165
+ let maxTy = Number.NEGATIVE_INFINITY;
166
+ for (const chunk of layer.loadedChunks()) {
167
+ const startTx = chunk.cx * layer.chunkWidth;
168
+ const startTy = chunk.cy * layer.chunkHeight;
169
+ minTx = Math.min(minTx, startTx);
170
+ minTy = Math.min(minTy, startTy);
171
+ maxTx = Math.max(maxTx, startTx + chunk.width - 1);
172
+ maxTy = Math.max(maxTy, startTy + chunk.height - 1);
173
+ }
174
+ if (!Number.isFinite(minTx) || !Number.isFinite(minTy)) return null;
175
+ return {
176
+ x: minTx,
177
+ y: minTy,
178
+ width: maxTx - minTx + 1,
179
+ height: maxTy - minTy + 1
180
+ };
181
+ };
182
+ /**
183
+ * Region a cell source spans on `layer`: the full tile extent of a bounded
184
+ * layer, `null` when the layer is unbounded and the caller must scope the
185
+ * walk itself. Chunk residency deliberately plays no part - the classification
186
+ * lives outside the layer, so loaded chunks cannot define its domain.
187
+ */
188
+ const cellSourceRegion = (layer) => {
189
+ if (layer.width === void 0 || layer.height === void 0) return null;
190
+ return {
191
+ x: 0,
192
+ y: 0,
193
+ width: layer.width,
194
+ height: layer.height
195
+ };
196
+ };
197
+ /**
198
+ * The region to walk: the caller's, else the extent a cell source spans, else
199
+ * whatever is currently resident. `null` when there is nothing to walk.
200
+ */
201
+ const resolveRegion = (layer, options) => {
202
+ if (options.region !== void 0) return options.region;
203
+ return (options.cells !== void 0 ? cellSourceRegion(layer) : null) ?? loadedTileRegion(layer);
204
+ };
205
+ /** Claim every cell of `region` the source classifies, into the occupancy grid. */
206
+ const claimSourceCells = (cells, region, source) => {
207
+ const endTx = region.x + region.width;
208
+ const endTy = region.y + region.height;
209
+ for (let ty = region.y; ty < endTy; ty++) for (let tx = region.x; tx < endTx; tx++) {
210
+ const type = source(tx, ty);
211
+ if (type !== null) cells.set(cellKey(tx, ty), type);
212
+ }
213
+ };
214
+ /** Key of a claimed cell in the occupancy grid. */
215
+ const cellKey = (tx, ty) => `${tx},${ty}`;
216
+ /**
217
+ * `true` when a placed shape is an axis-aligned rectangle covering exactly the
218
+ * tile cell at `(cellX, cellY)` - the only geometry the merging pass handles.
219
+ */
220
+ const coversWholeCell = (placed, layer, cellX, cellY) => placed.kind === ObjectKind.Rectangle && placed.rotation === 0 && placed.x === cellX && placed.y === cellY && placed.width === layer.tileWidth && placed.height === layer.tileHeight;
221
+ /** One single-cell rectangle per claimed cell, for `merge: false`. */
222
+ const unmergedCells = (cells, layer) => {
223
+ const rects = [];
224
+ for (const [key, type] of cells) {
225
+ const [tx, ty] = key.split(",").map(Number);
226
+ rects.push({
227
+ x: tx * layer.tileWidth + layer.offsetX,
228
+ y: ty * layer.tileHeight + layer.offsetY,
229
+ width: layer.tileWidth,
230
+ height: layer.tileHeight,
231
+ type
232
+ });
233
+ }
234
+ return rects;
235
+ };
236
+ /**
237
+ * Greedy rectangle merging over the claimed cells: walk row-major, grow each
238
+ * unconsumed cell as far right as the type key allows, then as far down as a
239
+ * full row of that width allows, and consume the block. Deliberately the plain
240
+ * textbook pass - it is linear in the number of cells and always produces a
241
+ * valid, non-overlapping cover, which matters more here than minimality.
242
+ */
243
+ const mergeCells = (cells, region, layer) => {
244
+ const consumed = /* @__PURE__ */ new Set();
245
+ const rects = [];
246
+ const endTx = region.x + region.width;
247
+ const endTy = region.y + region.height;
248
+ for (let ty = region.y; ty < endTy; ty++) for (let tx = region.x; tx < endTx; tx++) {
249
+ const key = cells.get(cellKey(tx, ty));
250
+ if (key === void 0 || consumed.has(cellKey(tx, ty))) continue;
251
+ let width = 1;
252
+ while (tx + width < endTx && cells.get(cellKey(tx + width, ty)) === key && !consumed.has(cellKey(tx + width, ty))) width++;
253
+ let height = 1;
254
+ growDown: while (ty + height < endTy) {
255
+ for (let column = 0; column < width; column++) {
256
+ const candidate = cellKey(tx + column, ty + height);
257
+ if (cells.get(candidate) !== key || consumed.has(candidate)) break growDown;
258
+ }
259
+ height++;
260
+ }
261
+ for (let row = 0; row < height; row++) for (let column = 0; column < width; column++) consumed.add(cellKey(tx + column, ty + row));
262
+ rects.push({
263
+ x: tx * layer.tileWidth + layer.offsetX,
264
+ y: ty * layer.tileHeight + layer.offsetY,
265
+ width: width * layer.tileWidth,
266
+ height: height * layer.tileHeight,
267
+ type: key
268
+ });
269
+ }
270
+ return rects;
271
+ };
272
+ /**
273
+ * Extract collision geometry from a tile layer's per-tile
274
+ * {@link import('./types').TileDefinition.collision} shapes.
275
+ *
276
+ * Every placed tile in `region` is resolved back to its tileset definition, and
277
+ * each collision shape is transformed from tile-local pixel space into layer
278
+ * pixel space (+Y down) - applying the tile's flip/rotation transform, the
279
+ * tileset draw offset, and the layer's pixel offset, so the geometry lands
280
+ * exactly where the tile is drawn.
281
+ *
282
+ * Shapes that end up covering exactly one whole tile cell are collected into an
283
+ * occupancy grid and merged into as few {@link TileCollisionRect}s as a greedy
284
+ * pass manages - a 200×200 solid region becomes one rectangle instead of 40 000.
285
+ * Cells only merge with neighbours whose source object carries the same `type`
286
+ * string, so a `water` run never fuses into an adjacent `solid` run. Everything
287
+ * else (partial boxes, ellipses, polygons, polylines, points, rotated geometry)
288
+ * passes through individually as a {@link TileCollisionShape}.
289
+ *
290
+ * A {@link TileCollisionOptions.cells} source feeds the same occupancy grid
291
+ * from outside the tiles, for collision authored per cell rather than per tile.
292
+ * It is consulted first, so a whole-cell tile box landing on a cell the source
293
+ * already claimed passes through as a shape rather than overwriting it. With a
294
+ * cell source and no explicit region, a bounded layer walks its full tile
295
+ * extent - a layer that renders nothing at all still produces geometry.
296
+ *
297
+ * This package has no physics dependency and builds no bodies: the result is
298
+ * plain geometry. Turning it into colliders is a short loop in application code.
299
+ *
300
+ * @param layer - The layer to walk.
301
+ * @param options - Region scoping, merge toggle, a source-shape filter, and an
302
+ * optional per-cell occupancy source.
303
+ * @returns Merged rectangles plus pass-through shapes; both empty when the
304
+ * region holds no tile carrying collision data.
305
+ *
306
+ * @example
307
+ * ```ts
308
+ * const { rects, shapes } = buildTileCollisionGeometry(layer);
309
+ *
310
+ * for (const rect of rects) {
311
+ * world.add(makeStaticBox(rect.x, rect.y, rect.width, rect.height));
312
+ * }
313
+ * ```
314
+ * @advanced
315
+ */
316
+ const buildTileCollisionGeometry = (layer, options = {}) => {
317
+ const cellSource = options.cells;
318
+ const region = resolveRegion(layer, options);
319
+ if (region === null || region.width <= 0 || region.height <= 0) return {
320
+ rects: [],
321
+ shapes: []
322
+ };
323
+ const accept = options.accept;
324
+ const cells = /* @__PURE__ */ new Map();
325
+ const shapes = [];
326
+ if (cellSource !== void 0) claimSourceCells(cells, region, cellSource);
327
+ for (const { tx, ty, tile } of layer.tilesInRect(region.x, region.y, region.width, region.height)) {
328
+ const definition = tile.tileset.getTileDefinition(tile.localTileId);
329
+ if (definition?.collision === void 0) continue;
330
+ const cellX = tx * layer.tileWidth + layer.offsetX;
331
+ const cellY = ty * layer.tileHeight + layer.offsetY;
332
+ for (const object of definition.collision) {
333
+ if (accept !== void 0 && !accept(object, tx, ty)) continue;
334
+ const placed = placeShape(object, layer, tile.tileset, tile.transform, tx, ty);
335
+ if (placed === null) continue;
336
+ const key = cellKey(tx, ty);
337
+ if (coversWholeCell(placed, layer, cellX, cellY) && !cells.has(key)) {
338
+ cells.set(key, object.type);
339
+ continue;
340
+ }
341
+ shapes.push({
342
+ kind: placed.kind,
343
+ x: placed.x,
344
+ y: placed.y,
345
+ width: placed.width,
346
+ height: placed.height,
347
+ rotation: placed.rotation,
348
+ ...placed.points !== void 0 && { points: placed.points },
349
+ tx,
350
+ ty,
351
+ source: object
352
+ });
353
+ }
354
+ }
355
+ if (cells.size === 0) return {
356
+ rects: [],
357
+ shapes
358
+ };
359
+ return {
360
+ rects: options.merge === false ? unmergedCells(cells, layer) : mergeCells(cells, region, layer),
361
+ shapes
362
+ };
363
+ };
364
+
365
+ //#endregion
366
+ export { buildTileCollisionGeometry };
367
+ //# sourceMappingURL=tileCollision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tileCollision.js","names":[],"sources":["../../src/tileCollision.ts"],"sourcesContent":["import type { ObjectPoint, TileMapObject } from './ObjectLayer';\nimport { ObjectKind } from './ObjectLayer';\nimport type { TileLayer } from './TileLayer';\nimport type { TileSet } from './TileSet';\nimport type { TileTransform } from './types';\n\n/**\n * The geometry kinds a {@link TileCollisionShape} can carry. Tile (GID) and\n * text objects describe no collision area and are never emitted, so they are\n * excluded from the union.\n * @advanced\n */\nexport type TileCollisionShapeKind =\n typeof ObjectKind.Rectangle | typeof ObjectKind.Ellipse | typeof ObjectKind.Polygon | typeof ObjectKind.Polyline | typeof ObjectKind.Point;\n\n/**\n * A half-open tile-coordinate rectangle covering\n * `[x, x + width)` × `[y, y + height)`.\n *\n * Used to scope {@link buildTileCollisionGeometry} to part of a layer - a\n * streamed chunk, the tiles around the player, a hand-picked room - instead of\n * always rebuilding the whole map.\n * @advanced\n */\nexport interface TileRegion {\n /** Leftmost tile column (inclusive). */\n readonly x: number;\n /** Topmost tile row (inclusive). */\n readonly y: number;\n /** Width in tiles. */\n readonly width: number;\n /** Height in tiles. */\n readonly height: number;\n}\n\n/**\n * An axis-aligned collision rectangle in tile-layer pixel space (+Y down),\n * covering one or more whole tile cells.\n *\n * Rectangles are synthesized geometry, not source objects: a merged run has no\n * single originating object, so it carries no object `id`/`name`/`properties` -\n * only the `type` string shared by every shape merged into it. Anything that\n * does not exactly cover whole cells stays a {@link TileCollisionShape}, which\n * does keep its source object.\n * @advanced\n */\nexport interface TileCollisionRect {\n /** Left edge in layer pixel space. */\n readonly x: number;\n /** Top edge in layer pixel space. */\n readonly y: number;\n /** Width in pixels (a whole number of tile cells). */\n readonly width: number;\n /** Height in pixels (a whole number of tile cells). */\n readonly height: number;\n /** Class/type string shared by every source shape merged into this rectangle. */\n readonly type: string;\n}\n\n/**\n * A per-tile collision shape placed in tile-layer pixel space (+Y down).\n *\n * Emitted for every collision shape that does not exactly cover a whole tile\n * cell: partial boxes, ellipses, polygons, polylines, points, and rotated\n * geometry. Shapes are never merged with one another.\n * @advanced\n */\nexport interface TileCollisionShape {\n /** Geometry discriminant. */\n readonly kind: TileCollisionShapeKind;\n /** X of the shape origin in layer pixel space. */\n readonly x: number;\n /** Y of the shape origin in layer pixel space. */\n readonly y: number;\n /** Bounding width in px (`0` for polygons, polylines and points). */\n readonly width: number;\n /** Bounding height in px (`0` for polygons, polylines and points). */\n readonly height: number;\n /**\n * Rotation in degrees, clockwise, about `(x, y)`, normalised to `[0, 360)`.\n * Always `0` for polygons and polylines - their rotation is baked into\n * {@link points}.\n */\n readonly rotation: number;\n /** Polygon/polyline vertices relative to `(x, y)`; absent for other kinds. */\n readonly points?: readonly ObjectPoint[];\n /** Tile column the shape came from. */\n readonly tx: number;\n /** Tile row the shape came from. */\n readonly ty: number;\n /** The tile-local source object, for `name`/`type`/`properties` lookups. */\n readonly source: TileMapObject;\n}\n\n/**\n * Collision geometry extracted from a tile layer: whole-cell boxes merged into\n * as few {@link TileCollisionRect}s as a greedy pass can manage, plus every\n * other shape passed through individually.\n * @advanced\n */\nexport interface TileCollisionGeometry {\n /** Merged whole-cell rectangles, in row-major order. */\n readonly rects: readonly TileCollisionRect[];\n /** Non-whole-cell shapes, in the order their tiles were walked. */\n readonly shapes: readonly TileCollisionShape[];\n}\n\n/**\n * Per-cell collision classification for a tile layer, independent of the tiles\n * the layer renders.\n *\n * Returns the class/type string for the cell, or `null` when the cell carries\n * no collision. The string is opaque: it is the merge key for adjacent cells\n * and reaches the consumer unchanged, exactly like the `type` of a tile\n * collision object.\n *\n * A source is sampled while geometry is being built and is expected to answer\n * identically for as long as the geometry is used. Changing what it returns\n * does not invalidate geometry that was already built; rebuild instead.\n * @advanced\n */\nexport type TileCellSource = (tx: number, ty: number) => string | null;\n\n/**\n * Options for {@link buildTileCollisionGeometry}.\n * @advanced\n */\nexport interface TileCollisionOptions {\n /**\n * Tile-coordinate region to walk. Defaults to the bounding box of the\n * layer's currently loaded chunks, so an unbounded (streamed) layer works\n * without the caller computing anything - or, with {@link cells} on a\n * bounded layer, to the layer's full tile extent.\n */\n readonly region?: TileRegion;\n /**\n * Second occupancy source, consulted once per cell of the walked region in\n * addition to the tiles' own collision shapes. Lets collision authored per\n * cell rather than per tile reach the same geometry, without the layer\n * having to render a tile there.\n */\n readonly cells?: TileCellSource | undefined;\n /**\n * Merge adjacent whole-cell boxes that share a `type` into larger\n * rectangles. Default `true`. With `false`, every whole-cell box becomes its\n * own single-cell rectangle.\n */\n readonly merge?: boolean;\n /**\n * Drop source shapes before any geometry is built (e.g. by a custom\n * property). Return `true` to keep a shape. Default keeps all of them.\n */\n readonly accept?: (object: TileMapObject, tx: number, ty: number) => boolean;\n}\n\nconst DEGREES_TO_RADIANS = Math.PI / 180;\n\n/** Normalise degrees into `[0, 360)`. */\nconst normaliseDegrees = (degrees: number): number => {\n const wrapped = degrees % 360;\n\n return wrapped < 0 ? wrapped + 360 : wrapped;\n};\n\n/**\n * Cosine of an angle in degrees, exact on the quadrant multiples.\n * `Math.cos(Math.PI / 2)` is `6.1e-17`, not `0`; tile transforms produce those\n * quadrant angles constantly, and the whole-cell test below compares\n * coordinates exactly, so the noise has to stay out of the arithmetic.\n */\nconst cosDegrees = (degrees: number): number => {\n const angle = normaliseDegrees(degrees);\n\n if (angle === 0) return 1;\n if (angle === 90 || angle === 270) return 0;\n if (angle === 180) return -1;\n\n return Math.cos(angle * DEGREES_TO_RADIANS);\n};\n\n/** Sine of an angle in degrees, exact on the quadrant multiples. */\nconst sinDegrees = (degrees: number): number => {\n const angle = normaliseDegrees(degrees);\n\n if (angle === 0 || angle === 180) return 0;\n if (angle === 90) return 1;\n if (angle === 270) return -1;\n\n return Math.sin(angle * DEGREES_TO_RADIANS);\n};\n\n/** `true` when the transform mirrors (an odd number of reflections). */\nconst isMirrored = (transform: TileTransform): boolean => {\n const flags = (transform.flipX ? 1 : 0) + (transform.flipY ? 1 : 0) + (transform.diagonal ? 1 : 0);\n\n return flags % 2 === 1;\n};\n\n/**\n * Rotation in degrees contributed by a non-mirroring transform. Only the four\n * even-parity transforms are rotations: identity, `flipX+flipY` (180°),\n * `diagonal+flipX` (90°) and `diagonal+flipY` (270°).\n */\nconst transformRotation = (transform: TileTransform): number => {\n if (transform.diagonal) {\n return transform.flipX ? 90 : 270;\n }\n\n return transform.flipX ? 180 : 0;\n};\n\n/**\n * Map a point from tile-local pixel space into the transformed tile-local\n * space of a placed tile. The diagonal flip (a transpose of the axes, which\n * also swaps the box dimensions) is applied first, then the axis mirrors -\n * the same order the renderer's orientation code implies.\n */\nconst mapLocalPoint = (px: number, py: number, boxWidth: number, boxHeight: number, transform: TileTransform): ObjectPoint => {\n let x = px;\n let y = py;\n let width = boxWidth;\n let height = boxHeight;\n\n if (transform.diagonal) {\n const swapped = x;\n\n x = y;\n y = swapped;\n width = boxHeight;\n height = boxWidth;\n }\n\n if (transform.flipX) x = width - x;\n if (transform.flipY) y = height - y;\n\n return { x, y };\n};\n\n/**\n * Layer-space position of a placed tile's top-left image corner. Mirrors the\n * renderer's placement (see `chunkGeometry`): the tileset's draw offset shifts\n * every tile, and a tile taller than the layer's cell is bottom-aligned within\n * it, so collision geometry lands exactly where the tile is drawn.\n */\nconst tileAnchor = (layer: TileLayer, tileset: TileSet, tx: number, ty: number): ObjectPoint => ({\n x: tx * layer.tileWidth + layer.offsetX + tileset.offsetX,\n y: ty * layer.tileHeight + layer.offsetY + layer.tileHeight - tileset.tileHeight + tileset.offsetY,\n});\n\n/** A shape in layer pixel space, before it is classified as cell or shape. */\ninterface PlacedShape {\n readonly kind: TileCollisionShapeKind;\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n readonly rotation: number;\n readonly points?: readonly ObjectPoint[];\n}\n\n/**\n * Place one tile-local collision object into layer pixel space, applying the\n * tile's flip/rotation transform, the tileset draw offset and the layer offset.\n * Returns `null` for kinds that carry no collision geometry.\n */\nconst placeShape = (object: TileMapObject, layer: TileLayer, tileset: TileSet, transform: TileTransform, tx: number, ty: number): PlacedShape | null => {\n if (object.kind === ObjectKind.Tile || object.kind === ObjectKind.Text) {\n return null;\n }\n\n const anchor = tileAnchor(layer, tileset, tx, ty);\n const boxWidth = tileset.tileWidth;\n const boxHeight = tileset.tileHeight;\n const mirrored = isMirrored(transform);\n const sourceCos = cosDegrees(object.rotation);\n const sourceSin = sinDegrees(object.rotation);\n\n if (object.kind === ObjectKind.Polygon || object.kind === ObjectKind.Polyline) {\n // Rotation is baked into the vertices: the source rotation pivots about the\n // object origin, and the tile transform may mirror the whole tile, so an\n // origin-plus-angle form would need a second angle convention. Absolute\n // vertices are exact under both.\n const origin = mapLocalPoint(object.x, object.y, boxWidth, boxHeight, transform);\n const originX = anchor.x + origin.x;\n const originY = anchor.y + origin.y;\n const points = object.points.map(point => {\n const localX = object.x + (sourceCos * point.x - sourceSin * point.y);\n const localY = object.y + (sourceSin * point.x + sourceCos * point.y);\n const mapped = mapLocalPoint(localX, localY, boxWidth, boxHeight, transform);\n\n return { x: anchor.x + mapped.x - originX, y: anchor.y + mapped.y - originY };\n });\n\n return {\n kind: object.kind,\n x: originX,\n y: originY,\n width: 0,\n height: 0,\n rotation: 0,\n // A mirror reverses the winding order; reversing the vertices restores\n // the source orientation, which convex-hull/polygon consumers rely on.\n points: mirrored ? points.reverse() : points,\n };\n }\n\n if (object.kind === ObjectKind.Point) {\n const mapped = mapLocalPoint(object.x, object.y, boxWidth, boxHeight, transform);\n\n return {\n kind: ObjectKind.Point,\n x: anchor.x + mapped.x,\n y: anchor.y + mapped.y,\n width: 0,\n height: 0,\n rotation: 0,\n };\n }\n\n // Rectangles and ellipses are symmetric about their centre, so placing the\n // centre and re-deriving the origin handles every transform uniformly.\n const halfWidth = object.width / 2;\n const halfHeight = object.height / 2;\n const centreLocalX = object.x + (sourceCos * halfWidth - sourceSin * halfHeight);\n const centreLocalY = object.y + (sourceSin * halfWidth + sourceCos * halfHeight);\n const centre = mapLocalPoint(centreLocalX, centreLocalY, boxWidth, boxHeight, transform);\n const centreX = anchor.x + centre.x;\n const centreY = anchor.y + centre.y;\n\n // Conjugating a rotation by a mirror negates it; a non-mirroring transform is\n // itself a rotation and simply adds. Only a mirror can swap the local axes,\n // and then only when the diagonal flip is part of it.\n const rotation = normaliseDegrees(mirrored ? -object.rotation : object.rotation + transformRotation(transform));\n const swapAxes = mirrored && transform.diagonal;\n const width = swapAxes ? object.height : object.width;\n const height = swapAxes ? object.width : object.height;\n\n if (rotation % 90 === 0) {\n // Quadrant-aligned geometry is axis-aligned; re-express it without a\n // rotation so whole-cell boxes on rotated tiles stay mergeable.\n const axisWidth = rotation % 180 === 0 ? width : height;\n const axisHeight = rotation % 180 === 0 ? height : width;\n\n return {\n kind: object.kind,\n x: centreX - axisWidth / 2,\n y: centreY - axisHeight / 2,\n width: axisWidth,\n height: axisHeight,\n rotation: 0,\n };\n }\n\n const cos = cosDegrees(rotation);\n const sin = sinDegrees(rotation);\n\n return {\n kind: object.kind,\n x: centreX - (cos * (width / 2) - sin * (height / 2)),\n y: centreY - (sin * (width / 2) + cos * (height / 2)),\n width,\n height,\n rotation,\n };\n};\n\n/** The tile-coordinate bounding box of the layer's loaded chunks, or `null`. */\nconst loadedTileRegion = (layer: TileLayer): TileRegion | null => {\n let minTx = Number.POSITIVE_INFINITY;\n let minTy = Number.POSITIVE_INFINITY;\n let maxTx = Number.NEGATIVE_INFINITY;\n let maxTy = Number.NEGATIVE_INFINITY;\n\n for (const chunk of layer.loadedChunks()) {\n const startTx = chunk.cx * layer.chunkWidth;\n const startTy = chunk.cy * layer.chunkHeight;\n\n minTx = Math.min(minTx, startTx);\n minTy = Math.min(minTy, startTy);\n maxTx = Math.max(maxTx, startTx + chunk.width - 1);\n maxTy = Math.max(maxTy, startTy + chunk.height - 1);\n }\n\n if (!Number.isFinite(minTx) || !Number.isFinite(minTy)) {\n return null;\n }\n\n return { x: minTx, y: minTy, width: maxTx - minTx + 1, height: maxTy - minTy + 1 };\n};\n\n/**\n * Region a cell source spans on `layer`: the full tile extent of a bounded\n * layer, `null` when the layer is unbounded and the caller must scope the\n * walk itself. Chunk residency deliberately plays no part - the classification\n * lives outside the layer, so loaded chunks cannot define its domain.\n */\nconst cellSourceRegion = (layer: TileLayer): TileRegion | null => {\n if (layer.width === undefined || layer.height === undefined) {\n return null;\n }\n\n return { x: 0, y: 0, width: layer.width, height: layer.height };\n};\n\n/**\n * The region to walk: the caller's, else the extent a cell source spans, else\n * whatever is currently resident. `null` when there is nothing to walk.\n */\nconst resolveRegion = (layer: TileLayer, options: TileCollisionOptions): TileRegion | null => {\n if (options.region !== undefined) return options.region;\n\n const fromCells = options.cells !== undefined ? cellSourceRegion(layer) : null;\n\n return fromCells ?? loadedTileRegion(layer);\n};\n\n/** Claim every cell of `region` the source classifies, into the occupancy grid. */\nconst claimSourceCells = (cells: Map<string, string>, region: TileRegion, source: TileCellSource): void => {\n const endTx = region.x + region.width;\n const endTy = region.y + region.height;\n\n for (let ty = region.y; ty < endTy; ty++) {\n for (let tx = region.x; tx < endTx; tx++) {\n const type = source(tx, ty);\n\n if (type !== null) cells.set(cellKey(tx, ty), type);\n }\n }\n};\n\n/** Key of a claimed cell in the occupancy grid. */\nconst cellKey = (tx: number, ty: number): string => `${tx},${ty}`;\n\n/**\n * `true` when a placed shape is an axis-aligned rectangle covering exactly the\n * tile cell at `(cellX, cellY)` - the only geometry the merging pass handles.\n */\nconst coversWholeCell = (placed: PlacedShape, layer: TileLayer, cellX: number, cellY: number): boolean =>\n placed.kind === ObjectKind.Rectangle &&\n placed.rotation === 0 &&\n placed.x === cellX &&\n placed.y === cellY &&\n placed.width === layer.tileWidth &&\n placed.height === layer.tileHeight;\n\n/** One single-cell rectangle per claimed cell, for `merge: false`. */\nconst unmergedCells = (cells: ReadonlyMap<string, string>, layer: TileLayer): TileCollisionRect[] => {\n const rects: TileCollisionRect[] = [];\n\n for (const [key, type] of cells) {\n const [tx, ty] = key.split(',').map(Number) as [number, number];\n\n rects.push({\n x: tx * layer.tileWidth + layer.offsetX,\n y: ty * layer.tileHeight + layer.offsetY,\n width: layer.tileWidth,\n height: layer.tileHeight,\n type,\n });\n }\n\n return rects;\n};\n\n/**\n * Greedy rectangle merging over the claimed cells: walk row-major, grow each\n * unconsumed cell as far right as the type key allows, then as far down as a\n * full row of that width allows, and consume the block. Deliberately the plain\n * textbook pass - it is linear in the number of cells and always produces a\n * valid, non-overlapping cover, which matters more here than minimality.\n */\nconst mergeCells = (cells: ReadonlyMap<string, string>, region: TileRegion, layer: TileLayer): TileCollisionRect[] => {\n const consumed = new Set<string>();\n const rects: TileCollisionRect[] = [];\n const endTx = region.x + region.width;\n const endTy = region.y + region.height;\n\n for (let ty = region.y; ty < endTy; ty++) {\n for (let tx = region.x; tx < endTx; tx++) {\n const key = cells.get(cellKey(tx, ty));\n\n if (key === undefined || consumed.has(cellKey(tx, ty))) {\n continue;\n }\n\n let width = 1;\n\n while (tx + width < endTx && cells.get(cellKey(tx + width, ty)) === key && !consumed.has(cellKey(tx + width, ty))) {\n width++;\n }\n\n let height = 1;\n\n growDown: while (ty + height < endTy) {\n for (let column = 0; column < width; column++) {\n const candidate = cellKey(tx + column, ty + height);\n\n if (cells.get(candidate) !== key || consumed.has(candidate)) {\n break growDown;\n }\n }\n\n height++;\n }\n\n for (let row = 0; row < height; row++) {\n for (let column = 0; column < width; column++) {\n consumed.add(cellKey(tx + column, ty + row));\n }\n }\n\n rects.push({\n x: tx * layer.tileWidth + layer.offsetX,\n y: ty * layer.tileHeight + layer.offsetY,\n width: width * layer.tileWidth,\n height: height * layer.tileHeight,\n type: key,\n });\n }\n }\n\n return rects;\n};\n\n/**\n * Extract collision geometry from a tile layer's per-tile\n * {@link import('./types').TileDefinition.collision} shapes.\n *\n * Every placed tile in `region` is resolved back to its tileset definition, and\n * each collision shape is transformed from tile-local pixel space into layer\n * pixel space (+Y down) - applying the tile's flip/rotation transform, the\n * tileset draw offset, and the layer's pixel offset, so the geometry lands\n * exactly where the tile is drawn.\n *\n * Shapes that end up covering exactly one whole tile cell are collected into an\n * occupancy grid and merged into as few {@link TileCollisionRect}s as a greedy\n * pass manages - a 200×200 solid region becomes one rectangle instead of 40 000.\n * Cells only merge with neighbours whose source object carries the same `type`\n * string, so a `water` run never fuses into an adjacent `solid` run. Everything\n * else (partial boxes, ellipses, polygons, polylines, points, rotated geometry)\n * passes through individually as a {@link TileCollisionShape}.\n *\n * A {@link TileCollisionOptions.cells} source feeds the same occupancy grid\n * from outside the tiles, for collision authored per cell rather than per tile.\n * It is consulted first, so a whole-cell tile box landing on a cell the source\n * already claimed passes through as a shape rather than overwriting it. With a\n * cell source and no explicit region, a bounded layer walks its full tile\n * extent - a layer that renders nothing at all still produces geometry.\n *\n * This package has no physics dependency and builds no bodies: the result is\n * plain geometry. Turning it into colliders is a short loop in application code.\n *\n * @param layer - The layer to walk.\n * @param options - Region scoping, merge toggle, a source-shape filter, and an\n * optional per-cell occupancy source.\n * @returns Merged rectangles plus pass-through shapes; both empty when the\n * region holds no tile carrying collision data.\n *\n * @example\n * ```ts\n * const { rects, shapes } = buildTileCollisionGeometry(layer);\n *\n * for (const rect of rects) {\n * world.add(makeStaticBox(rect.x, rect.y, rect.width, rect.height));\n * }\n * ```\n * @advanced\n */\nexport const buildTileCollisionGeometry = (layer: TileLayer, options: TileCollisionOptions = {}): TileCollisionGeometry => {\n const cellSource = options.cells;\n const region = resolveRegion(layer, options);\n\n if (region === null || region.width <= 0 || region.height <= 0) {\n return { rects: [], shapes: [] };\n }\n\n const accept = options.accept;\n const cells = new Map<string, string>();\n const shapes: TileCollisionShape[] = [];\n\n if (cellSource !== undefined) claimSourceCells(cells, region, cellSource);\n\n for (const { tx, ty, tile } of layer.tilesInRect(region.x, region.y, region.width, region.height)) {\n const definition = tile.tileset.getTileDefinition(tile.localTileId);\n\n if (definition?.collision === undefined) {\n continue;\n }\n\n const cellX = tx * layer.tileWidth + layer.offsetX;\n const cellY = ty * layer.tileHeight + layer.offsetY;\n\n for (const object of definition.collision) {\n if (accept !== undefined && !accept(object, tx, ty)) {\n continue;\n }\n\n const placed = placeShape(object, layer, tile.tileset, tile.transform, tx, ty);\n\n if (placed === null) {\n continue;\n }\n\n const key = cellKey(tx, ty);\n\n // A second whole-cell box on the same tile cannot claim the cell twice;\n // it falls through to the shape list so no geometry is silently dropped.\n if (coversWholeCell(placed, layer, cellX, cellY) && !cells.has(key)) {\n cells.set(key, object.type);\n continue;\n }\n\n shapes.push({\n kind: placed.kind,\n x: placed.x,\n y: placed.y,\n width: placed.width,\n height: placed.height,\n rotation: placed.rotation,\n ...(placed.points !== undefined && { points: placed.points }),\n tx,\n ty,\n source: object,\n });\n }\n }\n\n if (cells.size === 0) {\n return { rects: [], shapes };\n }\n\n const rects = options.merge === false ? unmergedCells(cells, layer) : mergeCells(cells, region, layer);\n\n return { rects, shapes };\n};\n"],"mappings":";;;AA2JA,MAAM,qBAAqB,KAAK,KAAK;;AAGrC,MAAM,oBAAoB,YAA4B;CACpD,MAAM,UAAU,UAAU;CAE1B,OAAO,UAAU,IAAI,UAAU,MAAM;AACvC;;;;;;;AAQA,MAAM,cAAc,YAA4B;CAC9C,MAAM,QAAQ,iBAAiB,OAAO;CAEtC,IAAI,UAAU,GAAG,OAAO;CACxB,IAAI,UAAU,MAAM,UAAU,KAAK,OAAO;CAC1C,IAAI,UAAU,KAAK,OAAO;CAE1B,OAAO,KAAK,IAAI,QAAQ,kBAAkB;AAC5C;;AAGA,MAAM,cAAc,YAA4B;CAC9C,MAAM,QAAQ,iBAAiB,OAAO;CAEtC,IAAI,UAAU,KAAK,UAAU,KAAK,OAAO;CACzC,IAAI,UAAU,IAAI,OAAO;CACzB,IAAI,UAAU,KAAK,OAAO;CAE1B,OAAO,KAAK,IAAI,QAAQ,kBAAkB;AAC5C;;AAGA,MAAM,cAAc,cAAsC;CAGxD,SAFe,UAAU,QAAQ,IAAI,MAAM,UAAU,QAAQ,IAAI,MAAM,UAAU,WAAW,IAAI,MAEjF,MAAM;AACvB;;;;;;AAOA,MAAM,qBAAqB,cAAqC;CAC9D,IAAI,UAAU,UACZ,OAAO,UAAU,QAAQ,KAAK;CAGhC,OAAO,UAAU,QAAQ,MAAM;AACjC;;;;;;;AAQA,MAAM,iBAAiB,IAAY,IAAY,UAAkB,WAAmB,cAA0C;CAC5H,IAAI,IAAI;CACR,IAAI,IAAI;CACR,IAAI,QAAQ;CACZ,IAAI,SAAS;CAEb,IAAI,UAAU,UAAU;EACtB,MAAM,UAAU;EAEhB,IAAI;EACJ,IAAI;EACJ,QAAQ;EACR,SAAS;CACX;CAEA,IAAI,UAAU,OAAO,IAAI,QAAQ;CACjC,IAAI,UAAU,OAAO,IAAI,SAAS;CAElC,OAAO;EAAE;EAAG;CAAE;AAChB;;;;;;;AAQA,MAAM,cAAc,OAAkB,SAAkB,IAAY,QAA6B;CAC/F,GAAG,KAAK,MAAM,YAAY,MAAM,UAAU,QAAQ;CAClD,GAAG,KAAK,MAAM,aAAa,MAAM,UAAU,MAAM,aAAa,QAAQ,aAAa,QAAQ;AAC7F;;;;;;AAkBA,MAAM,cAAc,QAAuB,OAAkB,SAAkB,WAA0B,IAAY,OAAmC;CACtJ,IAAI,OAAO,SAAS,WAAW,QAAQ,OAAO,SAAS,WAAW,MAChE,OAAO;CAGT,MAAM,SAAS,WAAW,OAAO,SAAS,IAAI,EAAE;CAChD,MAAM,WAAW,QAAQ;CACzB,MAAM,YAAY,QAAQ;CAC1B,MAAM,WAAW,WAAW,SAAS;CACrC,MAAM,YAAY,WAAW,OAAO,QAAQ;CAC5C,MAAM,YAAY,WAAW,OAAO,QAAQ;CAE5C,IAAI,OAAO,SAAS,WAAW,WAAW,OAAO,SAAS,WAAW,UAAU;EAK7E,MAAM,SAAS,cAAc,OAAO,GAAG,OAAO,GAAG,UAAU,WAAW,SAAS;EAC/E,MAAM,UAAU,OAAO,IAAI,OAAO;EAClC,MAAM,UAAU,OAAO,IAAI,OAAO;EAClC,MAAM,SAAS,OAAO,OAAO,KAAI,UAAS;GACxC,MAAM,SAAS,OAAO,KAAK,YAAY,MAAM,IAAI,YAAY,MAAM;GACnE,MAAM,SAAS,OAAO,KAAK,YAAY,MAAM,IAAI,YAAY,MAAM;GACnE,MAAM,SAAS,cAAc,QAAQ,QAAQ,UAAU,WAAW,SAAS;GAE3E,OAAO;IAAE,GAAG,OAAO,IAAI,OAAO,IAAI;IAAS,GAAG,OAAO,IAAI,OAAO,IAAI;GAAQ;EAC9E,CAAC;EAED,OAAO;GACL,MAAM,OAAO;GACb,GAAG;GACH,GAAG;GACH,OAAO;GACP,QAAQ;GACR,UAAU;GAGV,QAAQ,WAAW,OAAO,QAAQ,IAAI;EACxC;CACF;CAEA,IAAI,OAAO,SAAS,WAAW,OAAO;EACpC,MAAM,SAAS,cAAc,OAAO,GAAG,OAAO,GAAG,UAAU,WAAW,SAAS;EAE/E,OAAO;GACL,MAAM,WAAW;GACjB,GAAG,OAAO,IAAI,OAAO;GACrB,GAAG,OAAO,IAAI,OAAO;GACrB,OAAO;GACP,QAAQ;GACR,UAAU;EACZ;CACF;CAIA,MAAM,YAAY,OAAO,QAAQ;CACjC,MAAM,aAAa,OAAO,SAAS;CACnC,MAAM,eAAe,OAAO,KAAK,YAAY,YAAY,YAAY;CACrE,MAAM,eAAe,OAAO,KAAK,YAAY,YAAY,YAAY;CACrE,MAAM,SAAS,cAAc,cAAc,cAAc,UAAU,WAAW,SAAS;CACvF,MAAM,UAAU,OAAO,IAAI,OAAO;CAClC,MAAM,UAAU,OAAO,IAAI,OAAO;CAKlC,MAAM,WAAW,iBAAiB,WAAW,CAAC,OAAO,WAAW,OAAO,WAAW,kBAAkB,SAAS,CAAC;CAC9G,MAAM,WAAW,YAAY,UAAU;CACvC,MAAM,QAAQ,WAAW,OAAO,SAAS,OAAO;CAChD,MAAM,SAAS,WAAW,OAAO,QAAQ,OAAO;CAEhD,IAAI,WAAW,OAAO,GAAG;EAGvB,MAAM,YAAY,WAAW,QAAQ,IAAI,QAAQ;EACjD,MAAM,aAAa,WAAW,QAAQ,IAAI,SAAS;EAEnD,OAAO;GACL,MAAM,OAAO;GACb,GAAG,UAAU,YAAY;GACzB,GAAG,UAAU,aAAa;GAC1B,OAAO;GACP,QAAQ;GACR,UAAU;EACZ;CACF;CAEA,MAAM,MAAM,WAAW,QAAQ;CAC/B,MAAM,MAAM,WAAW,QAAQ;CAE/B,OAAO;EACL,MAAM,OAAO;EACb,GAAG,WAAW,OAAO,QAAQ,KAAK,OAAO,SAAS;EAClD,GAAG,WAAW,OAAO,QAAQ,KAAK,OAAO,SAAS;EAClD;EACA;EACA;CACF;AACF;;AAGA,MAAM,oBAAoB,UAAwC;CAChE,IAAI,QAAQ,OAAO;CACnB,IAAI,QAAQ,OAAO;CACnB,IAAI,QAAQ,OAAO;CACnB,IAAI,QAAQ,OAAO;CAEnB,KAAK,MAAM,SAAS,MAAM,aAAa,GAAG;EACxC,MAAM,UAAU,MAAM,KAAK,MAAM;EACjC,MAAM,UAAU,MAAM,KAAK,MAAM;EAEjC,QAAQ,KAAK,IAAI,OAAO,OAAO;EAC/B,QAAQ,KAAK,IAAI,OAAO,OAAO;EAC/B,QAAQ,KAAK,IAAI,OAAO,UAAU,MAAM,QAAQ,CAAC;EACjD,QAAQ,KAAK,IAAI,OAAO,UAAU,MAAM,SAAS,CAAC;CACpD;CAEA,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,SAAS,KAAK,GACnD,OAAO;CAGT,OAAO;EAAE,GAAG;EAAO,GAAG;EAAO,OAAO,QAAQ,QAAQ;EAAG,QAAQ,QAAQ,QAAQ;CAAE;AACnF;;;;;;;AAQA,MAAM,oBAAoB,UAAwC;CAChE,IAAI,MAAM,UAAU,UAAa,MAAM,WAAW,QAChD,OAAO;CAGT,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,OAAO,MAAM;EAAO,QAAQ,MAAM;CAAO;AAChE;;;;;AAMA,MAAM,iBAAiB,OAAkB,YAAqD;CAC5F,IAAI,QAAQ,WAAW,QAAW,OAAO,QAAQ;CAIjD,QAFkB,QAAQ,UAAU,SAAY,iBAAiB,KAAK,IAAI,SAEtD,iBAAiB,KAAK;AAC5C;;AAGA,MAAM,oBAAoB,OAA4B,QAAoB,WAAiC;CACzG,MAAM,QAAQ,OAAO,IAAI,OAAO;CAChC,MAAM,QAAQ,OAAO,IAAI,OAAO;CAEhC,KAAK,IAAI,KAAK,OAAO,GAAG,KAAK,OAAO,MAClC,KAAK,IAAI,KAAK,OAAO,GAAG,KAAK,OAAO,MAAM;EACxC,MAAM,OAAO,OAAO,IAAI,EAAE;EAE1B,IAAI,SAAS,MAAM,MAAM,IAAI,QAAQ,IAAI,EAAE,GAAG,IAAI;CACpD;AAEJ;;AAGA,MAAM,WAAW,IAAY,OAAuB,GAAG,GAAG,GAAG;;;;;AAM7D,MAAM,mBAAmB,QAAqB,OAAkB,OAAe,UAC7E,OAAO,SAAS,WAAW,aAC3B,OAAO,aAAa,KACpB,OAAO,MAAM,SACb,OAAO,MAAM,SACb,OAAO,UAAU,MAAM,aACvB,OAAO,WAAW,MAAM;;AAG1B,MAAM,iBAAiB,OAAoC,UAA0C;CACnG,MAAM,QAA6B,CAAC;CAEpC,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO;EAC/B,MAAM,CAAC,IAAI,MAAM,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;EAE1C,MAAM,KAAK;GACT,GAAG,KAAK,MAAM,YAAY,MAAM;GAChC,GAAG,KAAK,MAAM,aAAa,MAAM;GACjC,OAAO,MAAM;GACb,QAAQ,MAAM;GACd;EACF,CAAC;CACH;CAEA,OAAO;AACT;;;;;;;;AASA,MAAM,cAAc,OAAoC,QAAoB,UAA0C;CACpH,MAAM,2BAAW,IAAI,IAAY;CACjC,MAAM,QAA6B,CAAC;CACpC,MAAM,QAAQ,OAAO,IAAI,OAAO;CAChC,MAAM,QAAQ,OAAO,IAAI,OAAO;CAEhC,KAAK,IAAI,KAAK,OAAO,GAAG,KAAK,OAAO,MAClC,KAAK,IAAI,KAAK,OAAO,GAAG,KAAK,OAAO,MAAM;EACxC,MAAM,MAAM,MAAM,IAAI,QAAQ,IAAI,EAAE,CAAC;EAErC,IAAI,QAAQ,UAAa,SAAS,IAAI,QAAQ,IAAI,EAAE,CAAC,GACnD;EAGF,IAAI,QAAQ;EAEZ,OAAO,KAAK,QAAQ,SAAS,MAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC,MAAM,OAAO,CAAC,SAAS,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC,GAC9G;EAGF,IAAI,SAAS;EAEb,UAAU,OAAO,KAAK,SAAS,OAAO;GACpC,KAAK,IAAI,SAAS,GAAG,SAAS,OAAO,UAAU;IAC7C,MAAM,YAAY,QAAQ,KAAK,QAAQ,KAAK,MAAM;IAElD,IAAI,MAAM,IAAI,SAAS,MAAM,OAAO,SAAS,IAAI,SAAS,GACxD,MAAM;GAEV;GAEA;EACF;EAEA,KAAK,IAAI,MAAM,GAAG,MAAM,QAAQ,OAC9B,KAAK,IAAI,SAAS,GAAG,SAAS,OAAO,UACnC,SAAS,IAAI,QAAQ,KAAK,QAAQ,KAAK,GAAG,CAAC;EAI/C,MAAM,KAAK;GACT,GAAG,KAAK,MAAM,YAAY,MAAM;GAChC,GAAG,KAAK,MAAM,aAAa,MAAM;GACjC,OAAO,QAAQ,MAAM;GACrB,QAAQ,SAAS,MAAM;GACvB,MAAM;EACR,CAAC;CACH;CAGF,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAa,8BAA8B,OAAkB,UAAgC,CAAC,MAA6B;CACzH,MAAM,aAAa,QAAQ;CAC3B,MAAM,SAAS,cAAc,OAAO,OAAO;CAE3C,IAAI,WAAW,QAAQ,OAAO,SAAS,KAAK,OAAO,UAAU,GAC3D,OAAO;EAAE,OAAO,CAAC;EAAG,QAAQ,CAAC;CAAE;CAGjC,MAAM,SAAS,QAAQ;CACvB,MAAM,wBAAQ,IAAI,IAAoB;CACtC,MAAM,SAA+B,CAAC;CAEtC,IAAI,eAAe,QAAW,iBAAiB,OAAO,QAAQ,UAAU;CAExE,KAAK,MAAM,EAAE,IAAI,IAAI,UAAU,MAAM,YAAY,OAAO,GAAG,OAAO,GAAG,OAAO,OAAO,OAAO,MAAM,GAAG;EACjG,MAAM,aAAa,KAAK,QAAQ,kBAAkB,KAAK,WAAW;EAElE,IAAI,YAAY,cAAc,QAC5B;EAGF,MAAM,QAAQ,KAAK,MAAM,YAAY,MAAM;EAC3C,MAAM,QAAQ,KAAK,MAAM,aAAa,MAAM;EAE5C,KAAK,MAAM,UAAU,WAAW,WAAW;GACzC,IAAI,WAAW,UAAa,CAAC,OAAO,QAAQ,IAAI,EAAE,GAChD;GAGF,MAAM,SAAS,WAAW,QAAQ,OAAO,KAAK,SAAS,KAAK,WAAW,IAAI,EAAE;GAE7E,IAAI,WAAW,MACb;GAGF,MAAM,MAAM,QAAQ,IAAI,EAAE;GAI1B,IAAI,gBAAgB,QAAQ,OAAO,OAAO,KAAK,KAAK,CAAC,MAAM,IAAI,GAAG,GAAG;IACnE,MAAM,IAAI,KAAK,OAAO,IAAI;IAC1B;GACF;GAEA,OAAO,KAAK;IACV,MAAM,OAAO;IACb,GAAG,OAAO;IACV,GAAG,OAAO;IACV,OAAO,OAAO;IACd,QAAQ,OAAO;IACf,UAAU,OAAO;IACjB,GAAI,OAAO,WAAW,UAAa,EAAE,QAAQ,OAAO,OAAO;IAC3D;IACA;IACA,QAAQ;GACV,CAAC;EACH;CACF;CAEA,IAAI,MAAM,SAAS,GACjB,OAAO;EAAE,OAAO,CAAC;EAAG;CAAO;CAK7B,OAAO;EAAE,OAFK,QAAQ,UAAU,QAAQ,cAAc,OAAO,KAAK,IAAI,WAAW,OAAO,QAAQ,KAAK;EAErF;CAAO;AACzB"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Tile word packing, shared by both backends and by the shader sources they
3
+ * fill: transform-buffer row in the low 29 bits, diagonal flip in bit 29.
4
+ * flipX/flipY are baked into the UV bounds at write time, so the shader only
5
+ * needs the diagonal axis swap.
6
+ *
7
+ * @internal
8
+ */
9
+ export declare const TILE_ROW_MASK = 536870911;
10
+ /** @internal */
11
+ export declare const TILE_DIAGONAL_BIT = 536870912;
12
+ //# sourceMappingURL=tileWord.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tileWord.d.ts","sourceRoot":"","sources":["../../src/tileWord.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,YAAa,CAAC;AAExC,gBAAgB;AAChB,eAAO,MAAM,iBAAiB,YAAa,CAAC"}
@@ -0,0 +1,16 @@
1
+ //#region src/tileWord.ts
2
+ /**
3
+ * Tile word packing, shared by both backends and by the shader sources they
4
+ * fill: transform-buffer row in the low 29 bits, diagonal flip in bit 29.
5
+ * flipX/flipY are baked into the UV bounds at write time, so the shader only
6
+ * needs the diagonal axis swap.
7
+ *
8
+ * @internal
9
+ */
10
+ const TILE_ROW_MASK = 536870911;
11
+ /** @internal */
12
+ const TILE_DIAGONAL_BIT = 536870912;
13
+
14
+ //#endregion
15
+ export { TILE_DIAGONAL_BIT, TILE_ROW_MASK };
16
+ //# sourceMappingURL=tileWord.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tileWord.js","names":[],"sources":["../../src/tileWord.ts"],"sourcesContent":["/**\n * Tile word packing, shared by both backends and by the shader sources they\n * fill: transform-buffer row in the low 29 bits, diagonal flip in bit 29.\n * flipX/flipY are baked into the UV bounds at write time, so the shader only\n * needs the diagonal axis swap.\n *\n * @internal\n */\nexport const TILE_ROW_MASK = 0x1fffffff;\n\n/** @internal */\nexport const TILE_DIAGONAL_BIT = 0x20000000;\n"],"mappings":";;;;;;;;;AAQA,MAAa,gBAAgB;;AAG7B,MAAa,oBAAoB"}