feat(tile): render tiles with the mesh generator

Replace the per-shape tile geometries with @tts/mesh extrusion, honoring
the tile type and stretch aspect ratio. Add extrudeShapeParts so the
caps carry the texture while the walls are solid white, matching TTS
tinting. Fix bottom-face UVs to mirror horizontally and make wall UVs
inherit the front mapping independent of z.
This commit is contained in:
2026-08-08 14:21:53 +08:00
parent 14f83f476c
commit d554d6bde1
11 changed files with 246 additions and 45 deletions
+32 -2
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { extrudeShape, mergeFaces } from './extrude.js';
import { rectShape, hexShape, circleShape, roundedRectShape } from './shapes.js';
import { extrudeShape, extrudeShapeParts, mergeFaces } from './extrude.js';
import { rectShape, hexShape, circleShape, roundedRectShape, scaleShape } from './shapes.js';
import { capFaces } from './tessellate.js';
import { wallFaces } from './walls.js';
import type { FaceGeometry } from './types.js';
@@ -44,6 +44,36 @@ describe('extrudeShape', () => {
expect(geo.uvs[1]).toBeCloseTo(0, 5);
expect(geo.uvs[4]).toBeCloseTo(2, 5);
});
it('produces non-square geometry when scaled by an aspect ratio', () => {
const geo = extrudeShape(scaleShape(rectShape(2, 2), 2, 1), { height: 0.5 });
let minX = Infinity;
let maxX = -Infinity;
let minY = Infinity;
let maxY = -Infinity;
for (let i = 0; i < geo.positions.length; i += 3) {
minX = Math.min(minX, geo.positions[i]!);
maxX = Math.max(maxX, geo.positions[i]!);
minY = Math.min(minY, geo.positions[i + 1]!);
maxY = Math.max(maxY, geo.positions[i + 1]!);
}
expect(maxX - minX).toBeCloseTo(4, 5); // 2 * aspect 2
expect(maxY - minY).toBeCloseTo(2, 5);
});
});
describe('extrudeShapeParts', () => {
it('returns caps and walls as separate geometries', () => {
const { caps, walls } = extrudeShapeParts(rectShape(2, 2), { height: 1 });
// Caps: 2 faces * 4 outline points = 8 vertices.
expect(caps.positions.length / 3).toBe(8);
// Walls: 4 outline points * 2 vertices = 8 vertices.
expect(walls.positions.length / 3).toBe(8);
// Combined, they match `extrudeShape`.
const combined = extrudeShape(rectShape(2, 2), { height: 1 });
expect(caps.positions.length + walls.positions.length).toBe(combined.positions.length);
expect(caps.indices.length + walls.indices.length).toBe(combined.indices.length);
});
});
describe('mergeFaces', () => {