@flighthq/tilemap 0.3.0-next.1021.f5f1ee0
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/contract.d.ts +2 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +2 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/tilemap.d.ts +68 -0
- package/dist/tilemap.d.ts.map +1 -0
- package/dist/tilemap.js +222 -0
- package/dist/tilemap.js.map +1 -0
- package/package.json +51 -0
- package/src/tilemap.test.ts +497 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { clearTilemap, cloneTilemap, computeTilemapLocalBoundsRectangle, createTilemap, enableTilemapSignals, fillTilemapTiles, getTilemapColumnAtX, getTilemapColumnRowAtPoint, getTilemapRowAtY, getTilemapSignals, getTilemapTile, getTilemapTileAtPoint, getTilemapTileAtPointXY, getTilemapTileRect, resizeTilemap, setTilemapTile, setTilemapTileTint, setTilemapTiles, } from './contract';
|
|
2
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,kCAAkC,EAClC,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,0BAA0B,EAC1B,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,eAAe,GAChB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { clearTilemap, cloneTilemap, computeTilemapLocalBoundsRectangle, createTilemap, enableTilemapSignals, fillTilemapTiles, getTilemapColumnAtX, getTilemapColumnRowAtPoint, getTilemapRowAtY, getTilemapSignals, getTilemapTile, getTilemapTileAtPoint, getTilemapTileAtPointXY, getTilemapTileRect, resizeTilemap, setTilemapTile, setTilemapTileTint, setTilemapTiles, } from './contract';
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,kCAAkC,EAClC,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,0BAA0B,EAC1B,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,eAAe,GAChB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Node, PartialNode, Rectangle, Tilemap, TilemapData, TilemapRuntime, TilemapSignals, Vector2Like } from '@flighthq/types/contract';
|
|
2
|
+
/** Fills all cells with -1 (empty). Equivalent to `fillTilemapTiles(tilemap, -1)`. Fires `onCleared` when signals are enabled. */
|
|
3
|
+
export declare function clearTilemap(tilemap: Tilemap): void;
|
|
4
|
+
/**
|
|
5
|
+
* Deep-copies `source` into a new `Tilemap` with an independent `Int16Array` tiles buffer and a
|
|
6
|
+
* fresh runtime. The new tilemap has the same atlas, cell layout, grid, and `materialData`
|
|
7
|
+
* (shallow copy of the materialData array), but its `tiles` are a cloned typed array.
|
|
8
|
+
*/
|
|
9
|
+
export declare function cloneTilemap(source: Readonly<Tilemap>): Tilemap;
|
|
10
|
+
export declare function computeTilemapLocalBoundsRectangle(out: Rectangle, source: Readonly<Node>): void;
|
|
11
|
+
export declare function createTilemap(obj?: Readonly<PartialNode<Tilemap>>): Tilemap;
|
|
12
|
+
export declare function createTilemapData(data?: Readonly<Partial<TilemapData>>): TilemapData;
|
|
13
|
+
export declare function createTilemapRuntime(): TilemapRuntime;
|
|
14
|
+
export declare function createTilemapSignals(): TilemapSignals;
|
|
15
|
+
/**
|
|
16
|
+
* Opt-in signals for a `Tilemap` node. Returns the {@link TilemapSignals} group attached to
|
|
17
|
+
* `target`, creating it on the first call. Zero cost until enabled — honors the `enable*` convention.
|
|
18
|
+
* Use `getTilemapSignals` to read without creating.
|
|
19
|
+
*/
|
|
20
|
+
export declare function enableTilemapSignals(target: Tilemap): TilemapSignals;
|
|
21
|
+
export declare function fillTilemapTiles(tilemap: Tilemap, id: number): void;
|
|
22
|
+
/**
|
|
23
|
+
* Returns the column index for a local-space x coordinate, or -1 when the atlas is null or
|
|
24
|
+
* `x` is outside the tilemap bounds. The result is a floored column index.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getTilemapColumnAtX(source: Readonly<Tilemap>, x: number): number;
|
|
27
|
+
/**
|
|
28
|
+
* Writes the column and row for a local-space point `(x, y)` into `out.x`/`out.y`.
|
|
29
|
+
* Returns false when the atlas is null or the point is outside the tilemap bounds.
|
|
30
|
+
* On a false return, `out` is not modified.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getTilemapColumnRowAtPoint(out: Vector2Like, source: Readonly<Tilemap>, x: number, y: number): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Returns the row index for a local-space y coordinate, or -1 when the atlas is null or
|
|
35
|
+
* `y` is outside the tilemap bounds. The result is a floored row index.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getTilemapRowAtY(source: Readonly<Tilemap>, y: number): number;
|
|
38
|
+
export declare function getTilemapRuntime(source: Readonly<Tilemap>): Readonly<TilemapRuntime>;
|
|
39
|
+
/** Returns the {@link TilemapSignals} attached to `source`, or `null` if not yet enabled. */
|
|
40
|
+
export declare function getTilemapSignals(source: Readonly<Tilemap>): TilemapSignals | null;
|
|
41
|
+
export declare function getTilemapTile(tilemap: Readonly<Tilemap>, column: number, row: number): number;
|
|
42
|
+
/**
|
|
43
|
+
* Returns the cell value at local-space point `(x, y)`, or -1 when the atlas is null, the
|
|
44
|
+
* point is outside the tilemap, or the cell is empty. Delegates to `getTilemapColumnAtX`,
|
|
45
|
+
* `getTilemapRowAtY`, and `getTilemapTile`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getTilemapTileAtPoint(source: Readonly<Tilemap>, point: Readonly<Vector2Like>): number;
|
|
48
|
+
/**
|
|
49
|
+
* XY variant of `getTilemapTileAtPoint`. Returns the cell value or -1.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getTilemapTileAtPointXY(source: Readonly<Tilemap>, x: number, y: number): number;
|
|
52
|
+
/**
|
|
53
|
+
* Writes the local-space rectangle for the cell at `(column, row)` into `out`.
|
|
54
|
+
* Returns false and does not modify `out` when the atlas is null or the column/row is out of bounds.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getTilemapTileRect(out: Rectangle, source: Readonly<Tilemap>, column: number, row: number): boolean;
|
|
57
|
+
export declare function resizeTilemap(tilemap: Tilemap, columns: number, rows: number): void;
|
|
58
|
+
/** Sets the tile at `(column, row)`. Fires `onTileChanged` when signals are enabled. */
|
|
59
|
+
export declare function setTilemapTile(tilemap: Tilemap, column: number, row: number, id: number): void;
|
|
60
|
+
/**
|
|
61
|
+
* Blits a sub-grid of tile ids from `ids` into `tilemap` starting at (`offsetColumn`, `offsetRow`),
|
|
62
|
+
* reading `width × height` tiles from `ids` in row-major order. Clips the write to the tilemap bounds —
|
|
63
|
+
* out-of-range cells in the target are silently skipped. The `ids` array is read in row-major order:
|
|
64
|
+
* row `r` starts at `r * width`. Fires `onTilesChanged` when signals are enabled.
|
|
65
|
+
*/
|
|
66
|
+
export declare function setTilemapTiles(tilemap: Tilemap, ids: ArrayLike<number>, offsetColumn: number, offsetRow: number, width: number, height: number): void;
|
|
67
|
+
export declare function setTilemapTileTint(tilemap: Tilemap, column: number, row: number, rgba: number): void;
|
|
68
|
+
//# sourceMappingURL=tilemap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tilemap.d.ts","sourceRoot":"","sources":["../src/tilemap.ts"],"names":[],"mappings":"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"}
|
package/dist/tilemap.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { createNode2D, createNode2DRuntime, getNode2DRuntime } from '@flighthq/scene2d/contract';
|
|
2
|
+
import { createSignal } from '@flighthq/signals/contract';
|
|
3
|
+
import { TilemapKind } from '@flighthq/types/contract';
|
|
4
|
+
/** Fills all cells with -1 (empty). Equivalent to `fillTilemapTiles(tilemap, -1)`. Fires `onCleared` when signals are enabled. */
|
|
5
|
+
export function clearTilemap(tilemap) {
|
|
6
|
+
tilemap.data.tiles.fill(-1);
|
|
7
|
+
const signals = getTilemapSignals(tilemap);
|
|
8
|
+
if (signals !== null)
|
|
9
|
+
signals.onCleared.emit();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Deep-copies `source` into a new `Tilemap` with an independent `Int16Array` tiles buffer and a
|
|
13
|
+
* fresh runtime. The new tilemap has the same atlas, cell layout, grid, and `materialData`
|
|
14
|
+
* (shallow copy of the materialData array), but its `tiles` are a cloned typed array.
|
|
15
|
+
*/
|
|
16
|
+
export function cloneTilemap(source) {
|
|
17
|
+
const src = source.data;
|
|
18
|
+
return createTilemap({
|
|
19
|
+
data: {
|
|
20
|
+
atlas: src.atlas,
|
|
21
|
+
columns: src.columns,
|
|
22
|
+
materialData: src.materialData !== null ? src.materialData.slice() : null,
|
|
23
|
+
rows: src.rows,
|
|
24
|
+
tileHeight: src.tileHeight,
|
|
25
|
+
tileWidth: src.tileWidth,
|
|
26
|
+
tiles: src.tiles.slice(),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export function computeTilemapLocalBoundsRectangle(out, source) {
|
|
31
|
+
const tilemap = source;
|
|
32
|
+
const { atlas, columns, rows, tileHeight, tileWidth } = tilemap.data;
|
|
33
|
+
out.x = 0;
|
|
34
|
+
out.y = 0;
|
|
35
|
+
out.width = atlas !== null ? columns * tileWidth : 0;
|
|
36
|
+
out.height = atlas !== null ? rows * tileHeight : 0;
|
|
37
|
+
}
|
|
38
|
+
export function createTilemap(obj) {
|
|
39
|
+
return createNode2D(TilemapKind, obj, createTilemapData, createTilemapRuntime);
|
|
40
|
+
}
|
|
41
|
+
export function createTilemapData(data) {
|
|
42
|
+
const columns = data?.columns ?? 0;
|
|
43
|
+
const rows = data?.rows ?? 0;
|
|
44
|
+
return {
|
|
45
|
+
atlas: data?.atlas ?? null,
|
|
46
|
+
columns,
|
|
47
|
+
materialData: data?.materialData ?? null,
|
|
48
|
+
rows,
|
|
49
|
+
tileHeight: data?.tileHeight ?? 0,
|
|
50
|
+
tileWidth: data?.tileWidth ?? 0,
|
|
51
|
+
tiles: data?.tiles ?? new Int16Array(columns * rows).fill(-1),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function createTilemapRuntime() {
|
|
55
|
+
return createNode2DRuntime(defaultMethods);
|
|
56
|
+
}
|
|
57
|
+
export function createTilemapSignals() {
|
|
58
|
+
return {
|
|
59
|
+
onCleared: createSignal(),
|
|
60
|
+
onTileChanged: createSignal(),
|
|
61
|
+
onTilesChanged: createSignal(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Opt-in signals for a `Tilemap` node. Returns the {@link TilemapSignals} group attached to
|
|
66
|
+
* `target`, creating it on the first call. Zero cost until enabled — honors the `enable*` convention.
|
|
67
|
+
* Use `getTilemapSignals` to read without creating.
|
|
68
|
+
*/
|
|
69
|
+
export function enableTilemapSignals(target) {
|
|
70
|
+
const s = target;
|
|
71
|
+
return (s[tilemapSignalsSlot] ??= createTilemapSignals());
|
|
72
|
+
}
|
|
73
|
+
export function fillTilemapTiles(tilemap, id) {
|
|
74
|
+
tilemap.data.tiles.fill(id);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Returns the column index for a local-space x coordinate, or -1 when the atlas is null or
|
|
78
|
+
* `x` is outside the tilemap bounds. The result is a floored column index.
|
|
79
|
+
*/
|
|
80
|
+
export function getTilemapColumnAtX(source, x) {
|
|
81
|
+
const { atlas, columns, tileWidth } = source.data;
|
|
82
|
+
if (atlas === null || tileWidth <= 0)
|
|
83
|
+
return -1;
|
|
84
|
+
const col = Math.floor(x / tileWidth);
|
|
85
|
+
if (col < 0 || col >= columns)
|
|
86
|
+
return -1;
|
|
87
|
+
return col;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Writes the column and row for a local-space point `(x, y)` into `out.x`/`out.y`.
|
|
91
|
+
* Returns false when the atlas is null or the point is outside the tilemap bounds.
|
|
92
|
+
* On a false return, `out` is not modified.
|
|
93
|
+
*/
|
|
94
|
+
export function getTilemapColumnRowAtPoint(out, source, x, y) {
|
|
95
|
+
const col = getTilemapColumnAtX(source, x);
|
|
96
|
+
const row = getTilemapRowAtY(source, y);
|
|
97
|
+
if (col < 0 || row < 0)
|
|
98
|
+
return false;
|
|
99
|
+
out.x = col;
|
|
100
|
+
out.y = row;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Returns the row index for a local-space y coordinate, or -1 when the atlas is null or
|
|
105
|
+
* `y` is outside the tilemap bounds. The result is a floored row index.
|
|
106
|
+
*/
|
|
107
|
+
export function getTilemapRowAtY(source, y) {
|
|
108
|
+
const { atlas, rows, tileHeight } = source.data;
|
|
109
|
+
if (atlas === null || tileHeight <= 0)
|
|
110
|
+
return -1;
|
|
111
|
+
const row = Math.floor(y / tileHeight);
|
|
112
|
+
if (row < 0 || row >= rows)
|
|
113
|
+
return -1;
|
|
114
|
+
return row;
|
|
115
|
+
}
|
|
116
|
+
export function getTilemapRuntime(source) {
|
|
117
|
+
return getNode2DRuntime(source);
|
|
118
|
+
}
|
|
119
|
+
/** Returns the {@link TilemapSignals} attached to `source`, or `null` if not yet enabled. */
|
|
120
|
+
export function getTilemapSignals(source) {
|
|
121
|
+
return source[tilemapSignalsSlot] ?? null;
|
|
122
|
+
}
|
|
123
|
+
export function getTilemapTile(tilemap, column, row) {
|
|
124
|
+
const { columns, rows, tiles } = tilemap.data;
|
|
125
|
+
if (column < 0 || column >= columns || row < 0 || row >= rows)
|
|
126
|
+
return -1;
|
|
127
|
+
return tiles[row * columns + column];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Returns the cell value at local-space point `(x, y)`, or -1 when the atlas is null, the
|
|
131
|
+
* point is outside the tilemap, or the cell is empty. Delegates to `getTilemapColumnAtX`,
|
|
132
|
+
* `getTilemapRowAtY`, and `getTilemapTile`.
|
|
133
|
+
*/
|
|
134
|
+
export function getTilemapTileAtPoint(source, point) {
|
|
135
|
+
return getTilemapTileAtPointXY(source, point.x, point.y);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* XY variant of `getTilemapTileAtPoint`. Returns the cell value or -1.
|
|
139
|
+
*/
|
|
140
|
+
export function getTilemapTileAtPointXY(source, x, y) {
|
|
141
|
+
const col = getTilemapColumnAtX(source, x);
|
|
142
|
+
const row = getTilemapRowAtY(source, y);
|
|
143
|
+
if (col < 0 || row < 0)
|
|
144
|
+
return -1;
|
|
145
|
+
return getTilemapTile(source, col, row);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Writes the local-space rectangle for the cell at `(column, row)` into `out`.
|
|
149
|
+
* Returns false and does not modify `out` when the atlas is null or the column/row is out of bounds.
|
|
150
|
+
*/
|
|
151
|
+
export function getTilemapTileRect(out, source, column, row) {
|
|
152
|
+
const { atlas, columns, rows, tileHeight, tileWidth } = source.data;
|
|
153
|
+
if (atlas === null || column < 0 || column >= columns || row < 0 || row >= rows)
|
|
154
|
+
return false;
|
|
155
|
+
out.x = column * tileWidth;
|
|
156
|
+
out.y = row * tileHeight;
|
|
157
|
+
out.width = tileWidth;
|
|
158
|
+
out.height = tileHeight;
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
export function resizeTilemap(tilemap, columns, rows) {
|
|
162
|
+
const data = tilemap.data;
|
|
163
|
+
const newTiles = new Int16Array(columns * rows).fill(-1);
|
|
164
|
+
const copyColumns = Math.min(columns, data.columns);
|
|
165
|
+
const copyRows = Math.min(rows, data.rows);
|
|
166
|
+
for (let r = 0; r < copyRows; r++) {
|
|
167
|
+
for (let c = 0; c < copyColumns; c++) {
|
|
168
|
+
newTiles[r * columns + c] = data.tiles[r * data.columns + c];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
data.columns = columns;
|
|
172
|
+
data.rows = rows;
|
|
173
|
+
data.tiles = newTiles;
|
|
174
|
+
}
|
|
175
|
+
/** Sets the tile at `(column, row)`. Fires `onTileChanged` when signals are enabled. */
|
|
176
|
+
export function setTilemapTile(tilemap, column, row, id) {
|
|
177
|
+
const { columns, rows, tiles } = tilemap.data;
|
|
178
|
+
if (column < 0 || column >= columns || row < 0 || row >= rows)
|
|
179
|
+
return;
|
|
180
|
+
tiles[row * columns + column] = id;
|
|
181
|
+
const signals = getTilemapSignals(tilemap);
|
|
182
|
+
if (signals !== null)
|
|
183
|
+
signals.onTileChanged.emit(column, row, id);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Blits a sub-grid of tile ids from `ids` into `tilemap` starting at (`offsetColumn`, `offsetRow`),
|
|
187
|
+
* reading `width × height` tiles from `ids` in row-major order. Clips the write to the tilemap bounds —
|
|
188
|
+
* out-of-range cells in the target are silently skipped. The `ids` array is read in row-major order:
|
|
189
|
+
* row `r` starts at `r * width`. Fires `onTilesChanged` when signals are enabled.
|
|
190
|
+
*/
|
|
191
|
+
export function setTilemapTiles(tilemap, ids, offsetColumn, offsetRow, width, height) {
|
|
192
|
+
const { columns, rows, tiles } = tilemap.data;
|
|
193
|
+
for (let r = 0; r < height; r++) {
|
|
194
|
+
const targetRow = offsetRow + r;
|
|
195
|
+
if (targetRow < 0 || targetRow >= rows)
|
|
196
|
+
continue;
|
|
197
|
+
for (let c = 0; c < width; c++) {
|
|
198
|
+
const targetCol = offsetColumn + c;
|
|
199
|
+
if (targetCol < 0 || targetCol >= columns)
|
|
200
|
+
continue;
|
|
201
|
+
tiles[targetRow * columns + targetCol] = ids[r * width + c];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const signals = getTilemapSignals(tilemap);
|
|
205
|
+
if (signals !== null)
|
|
206
|
+
signals.onTilesChanged.emit(offsetColumn, offsetRow, width, height);
|
|
207
|
+
}
|
|
208
|
+
// Sets one tile's compact multiply-only tint. The material-data array is allocated on first use, so
|
|
209
|
+
// an untinted tilemap remains allocation-free. Invalid coordinates are ignored.
|
|
210
|
+
export function setTilemapTileTint(tilemap, column, row, rgba) {
|
|
211
|
+
const data = tilemap.data;
|
|
212
|
+
if (column < 0 || column >= data.columns || row < 0 || row >= data.rows)
|
|
213
|
+
return;
|
|
214
|
+
if (data.materialData === null)
|
|
215
|
+
data.materialData = new Array(data.columns * data.rows).fill(null);
|
|
216
|
+
data.materialData[row * data.columns + column] = { tint: rgba >>> 0 };
|
|
217
|
+
}
|
|
218
|
+
const defaultMethods = {
|
|
219
|
+
computeLocalBoundsRectangle: computeTilemapLocalBoundsRectangle,
|
|
220
|
+
};
|
|
221
|
+
const tilemapSignalsSlot = Symbol('tilemapSignals');
|
|
222
|
+
//# sourceMappingURL=tilemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tilemap.js","sourceRoot":"","sources":["../src/tilemap.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flighthq/tilemap",
|
|
3
|
+
"version": "0.3.0-next.1021.f5f1ee0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/flighthq/flight.git",
|
|
7
|
+
"directory": "packages/tilemap"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./contract": {
|
|
18
|
+
"types": "./dist/contract.d.ts",
|
|
19
|
+
"default": "./dist/contract.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src/**/*.test.ts",
|
|
25
|
+
"!dist/**/*.test.js",
|
|
26
|
+
"!dist/**/*.test.d.ts",
|
|
27
|
+
"!dist/**/*.test.js.map",
|
|
28
|
+
"!dist/**/*.test.d.ts.map"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -b",
|
|
32
|
+
"clean": "tsc -b --clean",
|
|
33
|
+
"test": "vitest run --config vitest.config.ts",
|
|
34
|
+
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
35
|
+
"prepack": "npm run clean && npm run clean:dist && npm run build",
|
|
36
|
+
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@flighthq/geometry": "0.3.0-next.1021.f5f1ee0",
|
|
40
|
+
"@flighthq/node": "0.3.0-next.1021.f5f1ee0",
|
|
41
|
+
"@flighthq/quadbatch": "0.3.0-next.1021.f5f1ee0",
|
|
42
|
+
"@flighthq/scene2d": "0.3.0-next.1021.f5f1ee0",
|
|
43
|
+
"@flighthq/signals": "0.3.0-next.1021.f5f1ee0",
|
|
44
|
+
"@flighthq/types": "0.3.0-next.1021.f5f1ee0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typescript": "^5.3.0"
|
|
48
|
+
},
|
|
49
|
+
"description": "Atlas-backed tilemap scene node and grid operations",
|
|
50
|
+
"sideEffects": false
|
|
51
|
+
}
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { createRectangle, createVector2 } from '@flighthq/geometry/contract';
|
|
2
|
+
import { connectSignal } from '@flighthq/signals/contract';
|
|
3
|
+
import type { Node, TextureAtlas } from '@flighthq/types/contract';
|
|
4
|
+
import { TilemapKind } from '@flighthq/types/contract';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
clearTilemap,
|
|
8
|
+
cloneTilemap,
|
|
9
|
+
computeTilemapLocalBoundsRectangle,
|
|
10
|
+
createTilemap,
|
|
11
|
+
createTilemapData,
|
|
12
|
+
createTilemapRuntime,
|
|
13
|
+
createTilemapSignals,
|
|
14
|
+
enableTilemapSignals,
|
|
15
|
+
fillTilemapTiles,
|
|
16
|
+
getTilemapColumnAtX,
|
|
17
|
+
getTilemapColumnRowAtPoint,
|
|
18
|
+
getTilemapRowAtY,
|
|
19
|
+
getTilemapRuntime,
|
|
20
|
+
getTilemapSignals,
|
|
21
|
+
getTilemapTile,
|
|
22
|
+
getTilemapTileAtPoint,
|
|
23
|
+
getTilemapTileAtPointXY,
|
|
24
|
+
getTilemapTileRect,
|
|
25
|
+
resizeTilemap,
|
|
26
|
+
setTilemapTile,
|
|
27
|
+
setTilemapTileTint,
|
|
28
|
+
setTilemapTiles,
|
|
29
|
+
} from './tilemap';
|
|
30
|
+
|
|
31
|
+
describe('clearTilemap', () => {
|
|
32
|
+
it('fills all cells with -1', () => {
|
|
33
|
+
const tilemap = createTilemap({ data: { columns: 3, rows: 2 } });
|
|
34
|
+
fillTilemapTiles(tilemap, 5);
|
|
35
|
+
clearTilemap(tilemap);
|
|
36
|
+
expect(Array.from(tilemap.data.tiles)).toEqual([-1, -1, -1, -1, -1, -1]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('cloneTilemap', () => {
|
|
41
|
+
it('copies grid, atlas, and tiles into a new tilemap', () => {
|
|
42
|
+
const atlas = {} as TextureAtlas;
|
|
43
|
+
const source = createTilemap({ data: { atlas, columns: 3, rows: 2, tileHeight: 16, tileWidth: 32 } });
|
|
44
|
+
setTilemapTile(source, 1, 1, 9);
|
|
45
|
+
const clone = cloneTilemap(source);
|
|
46
|
+
expect(clone).not.toBe(source);
|
|
47
|
+
expect(clone.data.columns).toBe(3);
|
|
48
|
+
expect(clone.data.rows).toBe(2);
|
|
49
|
+
expect(clone.data.atlas).toBe(atlas);
|
|
50
|
+
expect(getTilemapTile(clone, 1, 1)).toBe(9);
|
|
51
|
+
expect(clone.kind).toBe(TilemapKind);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('clones the tiles buffer so mutations do not leak back', () => {
|
|
55
|
+
const source = createTilemap({ data: { columns: 2, rows: 2 } });
|
|
56
|
+
const clone = cloneTilemap(source);
|
|
57
|
+
expect(clone.data.tiles).not.toBe(source.data.tiles);
|
|
58
|
+
setTilemapTile(clone, 0, 0, 5);
|
|
59
|
+
expect(getTilemapTile(source, 0, 0)).toBe(-1);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('computeTilemapLocalBoundsRectangle', () => {
|
|
64
|
+
it('sets out dimensions from atlas and grid size', () => {
|
|
65
|
+
const atlas = {} as TextureAtlas;
|
|
66
|
+
const tilemap = createTilemap({ data: { atlas, columns: 5, rows: 4, tileHeight: 16, tileWidth: 32 } });
|
|
67
|
+
const out = createRectangle();
|
|
68
|
+
computeTilemapLocalBoundsRectangle(out, tilemap as unknown as Node);
|
|
69
|
+
expect(out.width).toBe(160);
|
|
70
|
+
expect(out.height).toBe(64);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('sets zero dimensions when atlas is null', () => {
|
|
74
|
+
const tilemap = createTilemap({ data: { columns: 5, rows: 4 } });
|
|
75
|
+
const out = createRectangle(0, 0, 99, 99);
|
|
76
|
+
computeTilemapLocalBoundsRectangle(out, tilemap as unknown as Node);
|
|
77
|
+
expect(out.width).toBe(0);
|
|
78
|
+
expect(out.height).toBe(0);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('createTilemap', () => {
|
|
83
|
+
it('initializes default values', () => {
|
|
84
|
+
const tilemap = createTilemap();
|
|
85
|
+
expect(tilemap.data.columns).toBe(0);
|
|
86
|
+
expect(tilemap.data.rows).toBe(0);
|
|
87
|
+
expect(tilemap.data.atlas).toBeNull();
|
|
88
|
+
expect(tilemap.data.tiles).toBeInstanceOf(Int16Array);
|
|
89
|
+
expect(tilemap.kind).toBe(TilemapKind);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('initializes tiles to -1 (empty)', () => {
|
|
93
|
+
const tilemap = createTilemap({ data: { columns: 3, rows: 2 } });
|
|
94
|
+
expect(tilemap.data.tiles.length).toBe(6);
|
|
95
|
+
expect(Array.from(tilemap.data.tiles)).toEqual([-1, -1, -1, -1, -1, -1]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('allows pre-defined values', () => {
|
|
99
|
+
const base = { data: { columns: 10, rows: 5, atlas: {} as TextureAtlas, tileHeight: 16, tileWidth: 16 } };
|
|
100
|
+
const obj = createTilemap(base);
|
|
101
|
+
expect(obj.data.columns).toBe(10);
|
|
102
|
+
expect(obj.data.rows).toBe(5);
|
|
103
|
+
expect(obj.data.atlas).toBe(base.data.atlas);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('returns a new object for better hidden-class performance', () => {
|
|
107
|
+
const base = {};
|
|
108
|
+
const obj = createTilemap(base);
|
|
109
|
+
expect(obj).not.toStrictEqual(base);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('createTilemapData', () => {
|
|
114
|
+
it('returns default values', () => {
|
|
115
|
+
const data = createTilemapData();
|
|
116
|
+
expect(data.columns).toBe(0);
|
|
117
|
+
expect(data.rows).toBe(0);
|
|
118
|
+
expect(data.atlas).toBeNull();
|
|
119
|
+
expect(data.tiles).toBeInstanceOf(Int16Array);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('fills tiles with -1', () => {
|
|
123
|
+
const data = createTilemapData({ columns: 2, rows: 3 });
|
|
124
|
+
expect(Array.from(data.tiles)).toEqual([-1, -1, -1, -1, -1, -1]);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('createTilemapRuntime', () => {
|
|
129
|
+
it('returns a non-null runtime', () => {
|
|
130
|
+
const runtime = createTilemapRuntime();
|
|
131
|
+
expect(runtime).not.toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('uses computeTilemapLocalBoundsRectangle', () => {
|
|
135
|
+
const runtime = createTilemapRuntime();
|
|
136
|
+
expect(runtime.computeLocalBoundsRectangle).toStrictEqual(computeTilemapLocalBoundsRectangle);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('createTilemapSignals', () => {
|
|
141
|
+
it('creates a signals group with the three tilemap signals', () => {
|
|
142
|
+
const signals = createTilemapSignals();
|
|
143
|
+
let cleared = false;
|
|
144
|
+
let tileArgs: readonly number[] = [];
|
|
145
|
+
connectSignal(signals.onCleared, () => {
|
|
146
|
+
cleared = true;
|
|
147
|
+
});
|
|
148
|
+
connectSignal(signals.onTileChanged, (c, r, id) => {
|
|
149
|
+
tileArgs = [c, r, id];
|
|
150
|
+
});
|
|
151
|
+
signals.onCleared.emit();
|
|
152
|
+
signals.onTileChanged.emit(1, 2, 3);
|
|
153
|
+
expect(cleared).toBe(true);
|
|
154
|
+
expect(tileArgs).toEqual([1, 2, 3]);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('enableTilemapSignals', () => {
|
|
159
|
+
it('creates and attaches signals on first call', () => {
|
|
160
|
+
const tilemap = createTilemap();
|
|
161
|
+
expect(getTilemapSignals(tilemap)).toBeNull();
|
|
162
|
+
const signals = enableTilemapSignals(tilemap);
|
|
163
|
+
expect(signals).not.toBeNull();
|
|
164
|
+
expect(getTilemapSignals(tilemap)).toBe(signals);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('returns the same group on repeated calls', () => {
|
|
168
|
+
const tilemap = createTilemap();
|
|
169
|
+
expect(enableTilemapSignals(tilemap)).toBe(enableTilemapSignals(tilemap));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('makes setTilemapTile fire onTileChanged', () => {
|
|
173
|
+
const tilemap = createTilemap({ data: { columns: 3, rows: 3 } });
|
|
174
|
+
const signals = enableTilemapSignals(tilemap);
|
|
175
|
+
let received: readonly number[] = [];
|
|
176
|
+
connectSignal(signals.onTileChanged, (c, r, id) => {
|
|
177
|
+
received = [c, r, id];
|
|
178
|
+
});
|
|
179
|
+
setTilemapTile(tilemap, 1, 2, 4);
|
|
180
|
+
expect(received).toEqual([1, 2, 4]);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('makes clearTilemap fire onCleared', () => {
|
|
184
|
+
const tilemap = createTilemap({ data: { columns: 2, rows: 2 } });
|
|
185
|
+
const signals = enableTilemapSignals(tilemap);
|
|
186
|
+
let cleared = false;
|
|
187
|
+
connectSignal(signals.onCleared, () => {
|
|
188
|
+
cleared = true;
|
|
189
|
+
});
|
|
190
|
+
clearTilemap(tilemap);
|
|
191
|
+
expect(cleared).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('fillTilemapTiles', () => {
|
|
196
|
+
it('fills all cells with the given id', () => {
|
|
197
|
+
const tilemap = createTilemap({ data: { columns: 2, rows: 2 } });
|
|
198
|
+
fillTilemapTiles(tilemap, 3);
|
|
199
|
+
expect(Array.from(tilemap.data.tiles)).toEqual([3, 3, 3, 3]);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('clears all tiles with -1', () => {
|
|
203
|
+
const tilemap = createTilemap({ data: { columns: 2, rows: 2 } });
|
|
204
|
+
fillTilemapTiles(tilemap, 5);
|
|
205
|
+
fillTilemapTiles(tilemap, -1);
|
|
206
|
+
expect(Array.from(tilemap.data.tiles)).toEqual([-1, -1, -1, -1]);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('getTilemapColumnAtX', () => {
|
|
211
|
+
it('returns -1 when atlas is null', () => {
|
|
212
|
+
const tilemap = createTilemap({ data: { columns: 4, rows: 4 } });
|
|
213
|
+
expect(getTilemapColumnAtX(tilemap, 10)).toBe(-1);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('returns the floored column for an in-bounds x', () => {
|
|
217
|
+
const atlas = {} as TextureAtlas;
|
|
218
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 16, tileWidth: 16 } });
|
|
219
|
+
expect(getTilemapColumnAtX(tilemap, 0)).toBe(0);
|
|
220
|
+
expect(getTilemapColumnAtX(tilemap, 31)).toBe(1);
|
|
221
|
+
expect(getTilemapColumnAtX(tilemap, 48)).toBe(3);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('returns -1 for x outside the grid', () => {
|
|
225
|
+
const atlas = {} as TextureAtlas;
|
|
226
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 16, tileWidth: 16 } });
|
|
227
|
+
expect(getTilemapColumnAtX(tilemap, -1)).toBe(-1);
|
|
228
|
+
expect(getTilemapColumnAtX(tilemap, 64)).toBe(-1);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('getTilemapColumnRowAtPoint', () => {
|
|
233
|
+
it('writes column and row for an in-bounds point', () => {
|
|
234
|
+
const atlas = {} as TextureAtlas;
|
|
235
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 16, tileWidth: 16 } });
|
|
236
|
+
const out = createVector2(99, 99);
|
|
237
|
+
expect(getTilemapColumnRowAtPoint(out, tilemap, 33, 17)).toBe(true);
|
|
238
|
+
expect(out.x).toBe(2);
|
|
239
|
+
expect(out.y).toBe(1);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('returns false and does not modify out for an out-of-bounds point', () => {
|
|
243
|
+
const atlas = {} as TextureAtlas;
|
|
244
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 16, tileWidth: 16 } });
|
|
245
|
+
const out = createVector2(7, 8);
|
|
246
|
+
expect(getTilemapColumnRowAtPoint(out, tilemap, -1, 0)).toBe(false);
|
|
247
|
+
expect(out.x).toBe(7);
|
|
248
|
+
expect(out.y).toBe(8);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('getTilemapRowAtY', () => {
|
|
253
|
+
it('returns -1 when atlas is null', () => {
|
|
254
|
+
const tilemap = createTilemap({ data: { columns: 4, rows: 4 } });
|
|
255
|
+
expect(getTilemapRowAtY(tilemap, 10)).toBe(-1);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('returns the floored row for an in-bounds y', () => {
|
|
259
|
+
const atlas = {} as TextureAtlas;
|
|
260
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 16, tileWidth: 16 } });
|
|
261
|
+
expect(getTilemapRowAtY(tilemap, 0)).toBe(0);
|
|
262
|
+
expect(getTilemapRowAtY(tilemap, 31)).toBe(1);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('returns -1 for y outside the grid', () => {
|
|
266
|
+
const atlas = {} as TextureAtlas;
|
|
267
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 16, tileWidth: 16 } });
|
|
268
|
+
expect(getTilemapRowAtY(tilemap, -1)).toBe(-1);
|
|
269
|
+
expect(getTilemapRowAtY(tilemap, 64)).toBe(-1);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('getTilemapRuntime', () => {
|
|
274
|
+
it('returns the runtime for a Tilemap', () => {
|
|
275
|
+
const tilemap = createTilemap();
|
|
276
|
+
const runtime = getTilemapRuntime(tilemap);
|
|
277
|
+
expect(runtime).not.toBeNull();
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe('getTilemapSignals', () => {
|
|
282
|
+
it('returns null before signals are enabled', () => {
|
|
283
|
+
const tilemap = createTilemap();
|
|
284
|
+
expect(getTilemapSignals(tilemap)).toBeNull();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('returns the group after enabling', () => {
|
|
288
|
+
const tilemap = createTilemap();
|
|
289
|
+
const signals = enableTilemapSignals(tilemap);
|
|
290
|
+
expect(getTilemapSignals(tilemap)).toBe(signals);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('getTilemapTile', () => {
|
|
295
|
+
it('returns -1 for out-of-bounds coordinates', () => {
|
|
296
|
+
const tilemap = createTilemap({ data: { columns: 3, rows: 3 } });
|
|
297
|
+
expect(getTilemapTile(tilemap, -1, 0)).toBe(-1);
|
|
298
|
+
expect(getTilemapTile(tilemap, 3, 0)).toBe(-1);
|
|
299
|
+
expect(getTilemapTile(tilemap, 0, -1)).toBe(-1);
|
|
300
|
+
expect(getTilemapTile(tilemap, 0, 3)).toBe(-1);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('returns -1 for empty cells', () => {
|
|
304
|
+
const tilemap = createTilemap({ data: { columns: 2, rows: 2 } });
|
|
305
|
+
expect(getTilemapTile(tilemap, 0, 0)).toBe(-1);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('reads back what setTilemapTile wrote', () => {
|
|
309
|
+
const tilemap = createTilemap({ data: { columns: 4, rows: 4 } });
|
|
310
|
+
setTilemapTile(tilemap, 2, 1, 7);
|
|
311
|
+
expect(getTilemapTile(tilemap, 2, 1)).toBe(7);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe('getTilemapTileAtPoint', () => {
|
|
316
|
+
it('returns the cell value at a local-space point', () => {
|
|
317
|
+
const atlas = {} as TextureAtlas;
|
|
318
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 16, tileWidth: 16 } });
|
|
319
|
+
setTilemapTile(tilemap, 2, 1, 8);
|
|
320
|
+
expect(getTilemapTileAtPoint(tilemap, createVector2(33, 17))).toBe(8);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('returns -1 for an out-of-bounds point', () => {
|
|
324
|
+
const atlas = {} as TextureAtlas;
|
|
325
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 16, tileWidth: 16 } });
|
|
326
|
+
expect(getTilemapTileAtPoint(tilemap, createVector2(-5, 0))).toBe(-1);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('getTilemapTileAtPointXY', () => {
|
|
331
|
+
it('returns the cell value at (x, y)', () => {
|
|
332
|
+
const atlas = {} as TextureAtlas;
|
|
333
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 16, tileWidth: 16 } });
|
|
334
|
+
setTilemapTile(tilemap, 0, 0, 3);
|
|
335
|
+
expect(getTilemapTileAtPointXY(tilemap, 5, 5)).toBe(3);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('returns -1 when atlas is null', () => {
|
|
339
|
+
const tilemap = createTilemap({ data: { columns: 4, rows: 4 } });
|
|
340
|
+
expect(getTilemapTileAtPointXY(tilemap, 5, 5)).toBe(-1);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe('getTilemapTileRect', () => {
|
|
345
|
+
it('writes the cell rectangle for an in-bounds cell', () => {
|
|
346
|
+
const atlas = {} as TextureAtlas;
|
|
347
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 24, tileWidth: 16 } });
|
|
348
|
+
const out = createRectangle();
|
|
349
|
+
expect(getTilemapTileRect(out, tilemap, 2, 1)).toBe(true);
|
|
350
|
+
expect(out.x).toBe(32);
|
|
351
|
+
expect(out.y).toBe(24);
|
|
352
|
+
expect(out.width).toBe(16);
|
|
353
|
+
expect(out.height).toBe(24);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('returns false and does not modify out for an out-of-bounds cell', () => {
|
|
357
|
+
const atlas = {} as TextureAtlas;
|
|
358
|
+
const tilemap = createTilemap({ data: { atlas, columns: 4, rows: 4, tileHeight: 16, tileWidth: 16 } });
|
|
359
|
+
const out = createRectangle(1, 2, 3, 4);
|
|
360
|
+
expect(getTilemapTileRect(out, tilemap, 5, 0)).toBe(false);
|
|
361
|
+
expect(out.x).toBe(1);
|
|
362
|
+
expect(out.y).toBe(2);
|
|
363
|
+
expect(out.width).toBe(3);
|
|
364
|
+
expect(out.height).toBe(4);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('returns false when atlas is null', () => {
|
|
368
|
+
const tilemap = createTilemap({ data: { columns: 4, rows: 4 } });
|
|
369
|
+
const out = createRectangle();
|
|
370
|
+
expect(getTilemapTileRect(out, tilemap, 0, 0)).toBe(false);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe('resizeTilemap', () => {
|
|
375
|
+
it('updates columns and rows', () => {
|
|
376
|
+
const tilemap = createTilemap({ data: { columns: 3, rows: 3 } });
|
|
377
|
+
resizeTilemap(tilemap, 5, 4);
|
|
378
|
+
expect(tilemap.data.columns).toBe(5);
|
|
379
|
+
expect(tilemap.data.rows).toBe(4);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('copies surviving cells when shrinking', () => {
|
|
383
|
+
const tilemap = createTilemap({ data: { columns: 3, rows: 3 } });
|
|
384
|
+
setTilemapTile(tilemap, 0, 0, 1);
|
|
385
|
+
setTilemapTile(tilemap, 1, 1, 2);
|
|
386
|
+
resizeTilemap(tilemap, 2, 2);
|
|
387
|
+
expect(getTilemapTile(tilemap, 0, 0)).toBe(1);
|
|
388
|
+
expect(getTilemapTile(tilemap, 1, 1)).toBe(2);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('fills new cells with -1 when growing', () => {
|
|
392
|
+
const tilemap = createTilemap({ data: { columns: 2, rows: 2 } });
|
|
393
|
+
setTilemapTile(tilemap, 0, 0, 5);
|
|
394
|
+
resizeTilemap(tilemap, 4, 4);
|
|
395
|
+
expect(getTilemapTile(tilemap, 0, 0)).toBe(5);
|
|
396
|
+
expect(getTilemapTile(tilemap, 3, 3)).toBe(-1);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('correctly re-indexes tiles after resize', () => {
|
|
400
|
+
const tilemap = createTilemap({ data: { columns: 3, rows: 2 } });
|
|
401
|
+
// row 1, column 2 -> raw index = 1*3 + 2 = 5
|
|
402
|
+
setTilemapTile(tilemap, 2, 1, 9);
|
|
403
|
+
resizeTilemap(tilemap, 4, 3);
|
|
404
|
+
// after resize, row 1, column 2 -> raw index = 1*4 + 2 = 6
|
|
405
|
+
expect(getTilemapTile(tilemap, 2, 1)).toBe(9);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
describe('setTilemapTile', () => {
|
|
410
|
+
it('stores tiles in row-major order', () => {
|
|
411
|
+
const tilemap = createTilemap({ data: { columns: 3, rows: 3 } });
|
|
412
|
+
setTilemapTile(tilemap, 1, 0, 42); // index = row(0) * cols(3) + column(1) = 1
|
|
413
|
+
expect(tilemap.data.tiles[1]).toBe(42);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('silently ignores out-of-bounds writes', () => {
|
|
417
|
+
const tilemap = createTilemap({ data: { columns: 2, rows: 2 } });
|
|
418
|
+
setTilemapTile(tilemap, -1, 0, 5);
|
|
419
|
+
setTilemapTile(tilemap, 2, 0, 5);
|
|
420
|
+
setTilemapTile(tilemap, 0, -1, 5);
|
|
421
|
+
setTilemapTile(tilemap, 0, 2, 5);
|
|
422
|
+
expect(Array.from(tilemap.data.tiles)).toEqual([-1, -1, -1, -1]);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('can set the empty sentinel -1', () => {
|
|
426
|
+
const tilemap = createTilemap({ data: { columns: 2, rows: 2 } });
|
|
427
|
+
setTilemapTile(tilemap, 0, 0, 3);
|
|
428
|
+
setTilemapTile(tilemap, 0, 0, -1);
|
|
429
|
+
expect(getTilemapTile(tilemap, 0, 0)).toBe(-1);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
describe('setTilemapTiles', () => {
|
|
434
|
+
it('blits a sub-grid at offset (0, 0)', () => {
|
|
435
|
+
const tilemap = createTilemap({ data: { columns: 4, rows: 3 } });
|
|
436
|
+
const ids = new Int16Array([1, 2, 3, 4, 5, 6]);
|
|
437
|
+
setTilemapTiles(tilemap, ids, 0, 0, 3, 2);
|
|
438
|
+
expect(getTilemapTile(tilemap, 0, 0)).toBe(1);
|
|
439
|
+
expect(getTilemapTile(tilemap, 1, 0)).toBe(2);
|
|
440
|
+
expect(getTilemapTile(tilemap, 2, 0)).toBe(3);
|
|
441
|
+
expect(getTilemapTile(tilemap, 0, 1)).toBe(4);
|
|
442
|
+
expect(getTilemapTile(tilemap, 1, 1)).toBe(5);
|
|
443
|
+
expect(getTilemapTile(tilemap, 2, 1)).toBe(6);
|
|
444
|
+
// untouched cells remain -1
|
|
445
|
+
expect(getTilemapTile(tilemap, 3, 0)).toBe(-1);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('blits at a non-zero offset', () => {
|
|
449
|
+
const tilemap = createTilemap({ data: { columns: 4, rows: 4 } });
|
|
450
|
+
const ids = [10, 11, 12, 13];
|
|
451
|
+
setTilemapTiles(tilemap, ids, 1, 2, 2, 2);
|
|
452
|
+
expect(getTilemapTile(tilemap, 1, 2)).toBe(10);
|
|
453
|
+
expect(getTilemapTile(tilemap, 2, 2)).toBe(11);
|
|
454
|
+
expect(getTilemapTile(tilemap, 1, 3)).toBe(12);
|
|
455
|
+
expect(getTilemapTile(tilemap, 2, 3)).toBe(13);
|
|
456
|
+
// untouched
|
|
457
|
+
expect(getTilemapTile(tilemap, 0, 0)).toBe(-1);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('clips writes that extend outside the tilemap', () => {
|
|
461
|
+
const tilemap = createTilemap({ data: { columns: 2, rows: 2 } });
|
|
462
|
+
const ids = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
463
|
+
// 3x3 blit at offset (1, 1): only (1,1) is in-bounds
|
|
464
|
+
setTilemapTiles(tilemap, ids, 1, 1, 3, 3);
|
|
465
|
+
expect(getTilemapTile(tilemap, 1, 1)).toBe(1);
|
|
466
|
+
// All other cells remain -1
|
|
467
|
+
expect(getTilemapTile(tilemap, 0, 0)).toBe(-1);
|
|
468
|
+
expect(getTilemapTile(tilemap, 1, 0)).toBe(-1);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it('handles negative offsets by clipping', () => {
|
|
472
|
+
const tilemap = createTilemap({ data: { columns: 3, rows: 3 } });
|
|
473
|
+
const ids = [10, 20, 30, 40, 50, 60, 70, 80, 90];
|
|
474
|
+
// 3x3 blit at offset (-1, -1): only the bottom-right 2x2 of the source lands in bounds
|
|
475
|
+
setTilemapTiles(tilemap, ids, -1, -1, 3, 3);
|
|
476
|
+
// ids[1*3+1]=50 -> (0,0), ids[1*3+2]=60 -> (1,0), ids[2*3+1]=80 -> (0,1), ids[2*3+2]=90 -> (1,1)
|
|
477
|
+
expect(getTilemapTile(tilemap, 0, 0)).toBe(50);
|
|
478
|
+
expect(getTilemapTile(tilemap, 1, 0)).toBe(60);
|
|
479
|
+
expect(getTilemapTile(tilemap, 0, 1)).toBe(80);
|
|
480
|
+
expect(getTilemapTile(tilemap, 1, 1)).toBe(90);
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
describe('setTilemapTileTint', () => {
|
|
485
|
+
it('lazily allocates row-major per-tile data and stores packed RGBA', () => {
|
|
486
|
+
const tilemap = createTilemap({ data: { columns: 3, rows: 2 } });
|
|
487
|
+
expect(tilemap.data.materialData).toBeNull();
|
|
488
|
+
setTilemapTileTint(tilemap, 1, 1, 0x12345678);
|
|
489
|
+
expect(tilemap.data.materialData).toEqual([null, null, null, null, { tint: 0x12345678 }, null]);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('ignores out-of-range coordinates without allocating', () => {
|
|
493
|
+
const tilemap = createTilemap({ data: { columns: 1, rows: 1 } });
|
|
494
|
+
setTilemapTileTint(tilemap, 1, 0, 0xffffffff);
|
|
495
|
+
expect(tilemap.data.materialData).toBeNull();
|
|
496
|
+
});
|
|
497
|
+
});
|