feat(mesh): render cards, tiles, and tokens with separated front/back/walls
Split extrudeShapeParts into front, back, and walls so each face can carry its own material. Flip back textures left/right on the material so they are not mirrored, and slice card faces from the deck sprite sheet via CardID. Resolve deck config from the parent deck, which is authoritative over a card's own CustomDeck. Add unit tests for card resolution, sprite UVs, and the flip helper.
This commit is contained in:
@@ -62,27 +62,42 @@ describe('extrudeShape', () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
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);
|
||||
it('returns front, back, and walls as separate geometries', () => {
|
||||
const { front, back, walls } = extrudeShapeParts(rectShape(2, 2), { height: 1 });
|
||||
// Each face: 4 outline points.
|
||||
expect(front.positions.length / 3).toBe(4);
|
||||
expect(back.positions.length / 3).toBe(4);
|
||||
// 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);
|
||||
expect(front.positions.length + back.positions.length + walls.positions.length).toBe(
|
||||
combined.positions.length,
|
||||
);
|
||||
expect(front.indices.length + back.indices.length + walls.indices.length).toBe(
|
||||
combined.indices.length,
|
||||
);
|
||||
});
|
||||
|
||||
it('applies uvBounds to caps and walls', () => {
|
||||
it('front faces +Z and back faces -Z', () => {
|
||||
const { front, back } = extrudeShapeParts(rectShape(2, 2), { height: 1 });
|
||||
expect(Array.from(front.normals.slice(0, 3))).toEqual([0, 0, 1]);
|
||||
expect(Array.from(back.normals.slice(0, 3))).toEqual([0, 0, -1]);
|
||||
});
|
||||
|
||||
it('applies uvBounds to front, back, and walls', () => {
|
||||
const uvBounds = { minX: 0, minY: 0, maxX: 4, maxY: 4 };
|
||||
const { caps, walls } = extrudeShapeParts(rectShape(1, 1), {
|
||||
const { front, back, walls } = extrudeShapeParts(rectShape(1, 1), {
|
||||
height: 1,
|
||||
uvBounds,
|
||||
});
|
||||
// Caps: bottom-left vertex at (-0.5,-0.5) -> u=-0.125.
|
||||
expect(caps.uvs[0]).toBeCloseTo(-0.125, 5);
|
||||
// Front: bottom-left vertex at (-0.5,-0.5) -> u=-0.125.
|
||||
expect(front.uvs[0]).toBeCloseTo(-0.125, 5);
|
||||
// Back uses the same planar xy mapping (no mirror).
|
||||
expect(back.uvs[0]).toBeCloseTo(-0.125, 5);
|
||||
// Walls: first outline point (-0.5,-0.5) -> u=-0.125, v=-0.125.
|
||||
expect(walls.uvs[0]).toBeCloseTo(-0.125, 5);
|
||||
expect(walls.uvs[1]).toBeCloseTo(-0.125, 5);
|
||||
|
||||
Reference in New Issue
Block a user