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:
2026-08-08 17:41:59 +08:00
parent f7139495fe
commit 835250abdd
12 changed files with 633 additions and 80 deletions
@@ -0,0 +1,19 @@
import type * as THREE from 'three';
/**
* Return a texture flipped left/right so it reads correctly when viewed from
* behind (the back face). The back cap maps with the same planar UVs as the
* front, so without a flip the back appears mirrored.
*
* The source texture is cloned so the transform doesn't leak into other meshes
* that share the same texture (drei caches textures globally by URL).
*/
export function flipTexture(texture: THREE.Texture): THREE.Texture {
const tex = texture.clone();
// Negate repeat.x and shift offset.x by one full repeat so the visible
// region stays in the same place while mirrored. After negation
// `repeat.x` is `-rx`, so subtracting it adds `rx` to the offset.
tex.repeat.x = -tex.repeat.x;
tex.offset.x -= tex.repeat.x;
return tex;
}