Tilemaps are a new placeable asset type that is painted with autotiles in the scene view, with layers, brush, eraser and rectangle, and is drawn at runtime and in the export (#128)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0607221423
commit
33a7b1b63e
30 files changed
+2060
-20
No files matched your search
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* The geometry a painted layer becomes: one mesh per autotile instead of one sprite per cell, so a
|
||||
* map of a thousand tiles costs as many draw calls as it has autotiles on that layer (#128). Kept a
|
||||
* plain function of numbers – no Pixi, no project – so the quarter that lands in a corner can be
|
||||
* checked without a browser.
|
||||
*/
|
||||
import type { FrameRect } from '../core/model/texture';
|
||||
import type { TileDraw } from '../core/model/tilemap';
|
||||
|
||||
/** Where a tile's quarters are cut from, in the pixels of the texture the mesh wears. */
|
||||
export interface TileSheet {
|
||||
/** Side of one quarter tile in image pixels. */
|
||||
quad: number;
|
||||
/** The block inside that texture; the quadrant positions count from its top-left corner. */
|
||||
block: FrameRect;
|
||||
textureWidth: number;
|
||||
textureHeight: number;
|
||||
/**
|
||||
* Whether the texture is sampled smoothly. It is, the uvs are pulled half a texel inwards: a
|
||||
* linear sample right on the border of a quarter otherwise reaches into the one next to it and
|
||||
* draws a seam along every tile.
|
||||
*/
|
||||
smooth: boolean;
|
||||
}
|
||||
|
||||
export interface MeshArrays {
|
||||
positions: Float32Array;
|
||||
uvs: Float32Array;
|
||||
indices: Uint32Array;
|
||||
}
|
||||
|
||||
/** The corners of one quad, in reading order – the order `tileQuadrants` uses for the four quarters. */
|
||||
const CORNER_OFFSETS = [
|
||||
[0, 0],
|
||||
[1, 0],
|
||||
[0, 1],
|
||||
[1, 1],
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* `origin` is where cell (0, 0) starts in the tilemap's own frame, `tile` the side of a drawn cell
|
||||
* in world units. Every cell contributes four quads – its four quarters – so a tile whose corners
|
||||
* come from four corners of the block is one thing to draw, not four.
|
||||
*/
|
||||
export function tileMesh(
|
||||
tiles: readonly TileDraw[],
|
||||
tile: number,
|
||||
origin: { x: number; y: number },
|
||||
sheet: TileSheet,
|
||||
): MeshArrays {
|
||||
const positions = new Float32Array(tiles.length * 4 * 4 * 2);
|
||||
const uvs = new Float32Array(positions.length);
|
||||
const indices = new Uint32Array(tiles.length * 4 * 6);
|
||||
const half = tile / 2;
|
||||
const insetX = sheet.smooth ? 0.5 / sheet.textureWidth : 0;
|
||||
const insetY = sheet.smooth ? 0.5 / sheet.textureHeight : 0;
|
||||
let v = 0;
|
||||
let i = 0;
|
||||
for (const drawn of tiles) {
|
||||
const left = origin.x + drawn.x * tile;
|
||||
const top = origin.y + drawn.y * tile;
|
||||
drawn.quadrants.forEach((position, corner) => {
|
||||
const x = left + (corner % 2) * half;
|
||||
const y = top + Math.floor(corner / 2) * half;
|
||||
const u0 = (sheet.block.x + position.x * sheet.quad) / sheet.textureWidth + insetX;
|
||||
const v0 = (sheet.block.y + position.y * sheet.quad) / sheet.textureHeight + insetY;
|
||||
const u1 = (sheet.block.x + (position.x + 1) * sheet.quad) / sheet.textureWidth - insetX;
|
||||
const v1 = (sheet.block.y + (position.y + 1) * sheet.quad) / sheet.textureHeight - insetY;
|
||||
const base = v / 2;
|
||||
for (const [dx, dy] of CORNER_OFFSETS) {
|
||||
positions[v] = x + dx * half;
|
||||
uvs[v] = dx ? u1 : u0;
|
||||
v++;
|
||||
positions[v] = y + dy * half;
|
||||
uvs[v] = dy ? v1 : v0;
|
||||
v++;
|
||||
}
|
||||
indices[i++] = base;
|
||||
indices[i++] = base + 1;
|
||||
indices[i++] = base + 2;
|
||||
indices[i++] = base + 2;
|
||||
indices[i++] = base + 1;
|
||||
indices[i++] = base + 3;
|
||||
});
|
||||
}
|
||||
return { positions, uvs, indices };
|
||||
}
|
||||
|
||||
/** The cells of one layer grouped by autotile – one group is one mesh, because it is one texture. */
|
||||
export function tilesByAutotile(tiles: readonly TileDraw[]): Map<string, TileDraw[]> {
|
||||
const groups = new Map<string, TileDraw[]>();
|
||||
for (const drawn of tiles) {
|
||||
const group = groups.get(drawn.autotileId);
|
||||
if (group) group.push(drawn);
|
||||
else groups.set(drawn.autotileId, [drawn]);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
Reference in new issue
Block a user