docs: update mesh, viewers, and selection decisions
Document the separated front/back/walls extrusion, material-level back flips, CardID sprite selection from the parent deck, index-path tree selection, and the Bounds-inside-Suspense camera fit.
This commit is contained in:
@@ -52,8 +52,8 @@ apps/web ──► apps/proxy ──► packages/tts ──► packages/shared
|
||||
(image → vector shape) over HTTP.
|
||||
- **`apps/web` → `packages/extract`** — uses `buildTree` / `collectRefs`
|
||||
to analyze a loaded `TTSMod` in the browser (tree sidebar + asset refs).
|
||||
- **`apps/web` → `packages/mesh`** — extrudes 2D shapes into 3D geometry for
|
||||
the tile and token viewers.
|
||||
- **`apps/web` → `packages/mesh`** — extrudes 2D shapes into 3D geometry
|
||||
(`{ front, back, walls }`) for the tile, token, and card viewers.
|
||||
- **`apps/proxy` → `packages/tts`** — calls `fetchMod` / `getFileName` to serve
|
||||
item requests.
|
||||
- **`apps/proxy` → `packages/shared`** — uses shared types and zod schemas for
|
||||
@@ -93,7 +93,7 @@ apps/web ──► apps/proxy ──► packages/tts ──► packages/shared
|
||||
| `zod` | Runtime validation | `apps/proxy`, `packages/shared` |
|
||||
| `three` | 3D rendering | `apps/web` |
|
||||
| `@react-three/fiber` | React renderer for three.js | `apps/web` |
|
||||
| `@react-three/drei` | three.js helpers (controls, textures) | `apps/web` |
|
||||
| `@react-three/drei` | three.js helpers (controls, textures, bounds) | `apps/web` |
|
||||
| `@react-three/postprocessing` | Post-processing effects | `apps/web` |
|
||||
|
||||
### Runtime constraints
|
||||
|
||||
+77
-1
@@ -183,4 +183,80 @@ parsing yields a shape the mesh package can consume directly, and alpha-based
|
||||
tracing (the default) is the common case for token/tile art. For shape
|
||||
inset/outset, `clipper-lib` (Angus Johnson's Clipper ported to JS) was chosen
|
||||
over the `polygon-offset` package because the latter crashes on degenerate
|
||||
cases (collapse, hole closure) via a bug in its pinned Martinez dependency.
|
||||
cases (collapse, hole closure) via a bug in its pinned Martinez dependency.
|
||||
|
||||
## D14 — Extrusion exposes separated front/back/walls
|
||||
|
||||
**Decision:** `extrudeShapeParts` returns `{ front, back, walls }` as separate
|
||||
geometries, and `tessellate.ts` exposes `frontFaces` / `backFaces` (with
|
||||
`capFaces` kept as a merged convenience).
|
||||
|
||||
**Context:** Cards and tiles need distinct materials on the front and back
|
||||
faces, and the back must be flipped so it isn't mirrored when viewed from
|
||||
behind. Splitting the caps into front/back at the mesh level lets each viewer
|
||||
apply its own material without post-hoc geometry-group splitting.
|
||||
|
||||
**Alternatives considered:** Returning a single merged caps geometry and
|
||||
splitting it in the viewer (the previous approach for cards). Rejected —
|
||||
required manual triangle-group bookkeeping in the component.
|
||||
|
||||
## D15 — Back faces are flipped on the material, not the geometry
|
||||
|
||||
**Decision:** The back face is un-mirrored by flipping the texture on the
|
||||
material (`flipTexture.ts` negates `repeat.x` and shifts `offset.x`), rather
|
||||
than by transforming the geometry's UVs.
|
||||
|
||||
**Context:** The back cap maps with the same planar UVs as the front, so
|
||||
without a flip it appears mirrored. Flipping on the material keeps the mesh
|
||||
geometry simple and shared, and works for both a full texture and a sprite
|
||||
cell.
|
||||
|
||||
**Alternatives considered:** Flipping the UVs in `backFaces`. Rejected —
|
||||
would bake the flip into the shared mesh package, forcing it on every consumer
|
||||
rather than letting viewers opt in.
|
||||
|
||||
## D16 — Card sprite selection via `CardID` and the parent deck
|
||||
|
||||
**Decision:** A card's face/back sprite is selected from the deck sheet by
|
||||
`CardID` (deck index in the hundreds place, 0-based card number in the last
|
||||
two digits). The sheet config (grid, face/back URLs, `UniqueBack`) is resolved
|
||||
from the containing deck's `CustomDeck[deckIndex]`, which is authoritative
|
||||
over the card's own `CustomDeck` (often keyed differently or absent).
|
||||
|
||||
**Context:** Deck images are sheets divided into a `NumWidth` x `NumHeight`
|
||||
grid. The card's own `CustomDeck` field is unreliable — in the Wingspan dump a
|
||||
card with `CardID 1605` carries `CustomDeck: {14: ...}` even though its deck
|
||||
index is 16 — so the parent deck is the source of truth.
|
||||
|
||||
**Alternatives considered:** Using the card's own `CustomDeck`. Rejected —
|
||||
produces the wrong sprite for cards whose own field is mis-keyed or missing.
|
||||
|
||||
## D17 — Tree selection by index path, not GUID
|
||||
|
||||
**Decision:** The object tree selects nodes by their unique index path (e.g.
|
||||
`0-3-1`) rather than by `GUID`.
|
||||
|
||||
**Context:** Cards in a deck share the deck's GUID — in the Wingspan dump, 292
|
||||
of 606 objects carry a duplicate GUID. GUID-based selection highlighted every
|
||||
card with that GUID and rendered the first match, so the wrong sprite could
|
||||
show.
|
||||
|
||||
**Alternatives considered:** Using `GUID` (the previous approach). Rejected —
|
||||
ambiguous for decked cards.
|
||||
|
||||
## D18 — Camera fit via drei `Bounds` inside Suspense
|
||||
|
||||
**Decision:** The viewer camera is fitted to the object's bounds using drei's
|
||||
`Bounds` component, placed inside the scene's Suspense boundary so it mounts
|
||||
only after the (suspending) content has loaded.
|
||||
|
||||
**Context:** Viewers load content asynchronously (textures suspend, tokens
|
||||
trace via the proxy, models stream in). `Bounds` fits on mount, so it must
|
||||
mount after the content is present. The token viewer was converted from
|
||||
`useEffect` + state to a suspending resource so it participates in the same
|
||||
boundary.
|
||||
|
||||
**Alternatives considered:** A polling `CameraFit` that waited for non-empty
|
||||
bounds each frame; hand-rolled camera math. Rejected — Suspense already
|
||||
signals content readiness, so `Bounds` inside the boundary fits the loaded
|
||||
geometry directly.
|
||||
+25
-14
@@ -205,16 +205,20 @@ Isomorphic analysis of a parsed `TTSMod`. No Node-specific APIs.
|
||||
`scaleShape` and `signedArea`.
|
||||
- `tessellate.ts`
|
||||
- `triangulate(shape)` — earcut triangulation (same as three.js).
|
||||
- `capFaces(shape, height, uvScale, uvBounds)` — top/bottom faces. UVs map
|
||||
the shape's bounding box (or `uvBounds` framing) to the unit square; the
|
||||
bottom face uses the same planar xy mapping as the top (no mirror).
|
||||
- `frontFaces(shape, height, uvScale, uvBounds)` — top face, normal +Z.
|
||||
- `backFaces(shape, height, uvScale, uvBounds)` — bottom face, normal -Z.
|
||||
UVs map the shape's bounding box (or `uvBounds` framing) to the unit
|
||||
square; the back uses the same planar xy mapping as the front (no mirror).
|
||||
- `capFaces(...)` — front + back merged into one geometry (front vertices
|
||||
first, then back).
|
||||
- `walls.ts`
|
||||
- `wallFaces(shape, height, uvScale, uvBounds)` — side walls with outward
|
||||
normals and planar xy UVs (z-independent).
|
||||
- `extrude.ts`
|
||||
- `extrudeShape(shape, options)` — merged caps + walls as one geometry.
|
||||
- `extrudeShapeParts(shape, options)` — caps and walls as separate
|
||||
geometries (for distinct materials).
|
||||
- `extrudeShape(shape, options)` — merged front + back + walls as one
|
||||
geometry.
|
||||
- `extrudeShapeParts(shape, options)` — `{ front, back, walls }` as separate
|
||||
geometries, so each face can carry its own material.
|
||||
- `ExtrudeOptions` — `height`, `capUvScale`, `wallUvScale`, `uvBounds`.
|
||||
- `types.ts`
|
||||
- `FaceGeometry`, `ExtrudedGeometry`, `UVBounds`.
|
||||
@@ -278,7 +282,8 @@ proxy API and `packages/extract` directly for analysis.
|
||||
- `components/SearchResults.tsx` — result grid + pagination.
|
||||
- `components/ObjectTree.tsx` — recursive tree sidebar; each entry shows a
|
||||
class icon (hover for the class name) + display label, indented by depth.
|
||||
Clicking selects an object.
|
||||
Clicking selects an object by its unique index path (not GUID — cards in a
|
||||
deck share the deck's GUID).
|
||||
- `components/viewers.tsx` — viewer registry (`registerViewer` /
|
||||
`resolveViewer`) plus a `DefaultViewer` that renders an object's fields;
|
||||
custom per-class viewers can be registered later.
|
||||
@@ -286,13 +291,19 @@ proxy API and `packages/extract` directly for analysis.
|
||||
`@react-three/drei`, and `@react-three/postprocessing`. `register.ts`
|
||||
registers lazy-loaded viewers for `Tile`/`Custom_Tile` (flat box),
|
||||
`Custom_Token` (shape traced from the image's alpha channel via `/trace`,
|
||||
extruded with `@tts/mesh`), `Card`/`Deck`/`Custom_Deck` (thin box with
|
||||
face/back textures), and `Custom_Model`/`Custom_Model_Bag`/
|
||||
`Custom_Model_Infinite_Bag` (GLTF/OBJ/FBX from `CustomMesh.MeshURL`).
|
||||
`Scene.tsx` is a shared canvas with lighting, orbit controls, contact
|
||||
shadows, and subtle bloom/vignette. `assetUrl.ts` routes asset URLs through
|
||||
the proxy for CORS-safe loading. The viewers are lazy-loaded so the three.js
|
||||
stack is code-split out of the main bundle.
|
||||
extruded with `@tts/mesh`), `Card`/`CardCustom`/`Deck`/`DeckCustom`/
|
||||
`Custom_Deck` (thin rounded rect with face/back textures), and
|
||||
`Custom_Model`/`Custom_Model_Bag`/`Custom_Model_Infinite_Bag` (GLTF/OBJ/FBX
|
||||
from `CustomMesh.MeshURL`). Viewers build `{ front, back, walls }` geometry
|
||||
via `extrudeShapeParts`; back faces are flipped left/right on the material
|
||||
(`flipTexture.ts`) so they aren't mirrored, and card faces slice the deck
|
||||
sprite sheet via `CardID` (`cardResolution.ts`). `Scene.tsx` is a shared
|
||||
canvas with lighting, orbit controls, contact shadows, and subtle
|
||||
bloom/vignette; it fits the camera to the object's bounds via drei's
|
||||
`Bounds` inside the Suspense boundary, so it frames the loaded content.
|
||||
`assetUrl.ts` routes asset URLs through the proxy for CORS-safe loading.
|
||||
The viewers are lazy-loaded so the three.js stack is code-split out of the
|
||||
main bundle.
|
||||
- `components/objectIcons.tsx` — maps TTS object classes to one or more
|
||||
Iconify icons (`iconsForObject`); unknown classes fall back to a help icon.
|
||||
Icons may come from multiple sets (mdi, material-symbols, file-icons, ...);
|
||||
|
||||
Reference in New Issue
Block a user