Files
tts-workshop/docs/full-setup-view.md
T
hypercross e88dd03fac feat(web): add full setup view rendering the whole save
Add a /mod/:id/setup page that lays out every renderable object in a single shared scene. Export object-facing mesh wrappers from the viewers and share geometry and materials across objects via module-level caches.
2026-08-08 18:28:03 +08:00

5.2 KiB

Full Setup View — Plan

Scope: A new page that renders every loadable object in a save into a single shared 3D scene, sharing materials and geometry where possible. This complements the existing per-object inspection on the mod page.

Motivation

The mod page (apps/web/src/pages/ModPage.tsx) inspects one object at a time inside a shared Scene. A "full setup" view renders the whole save at once — every tile, token, card, and custom model laid out on a virtual table — so a user can see the entire scene at a glance.

Current architecture (relevant pieces)

  • Each viewer owns its own <Scene> wrapper (components/viewers/Scene.tsx), which provides the camera, lights, orbit controls, contact shadows, and post-processing. The mesh content lives inside each viewer:
    • TileViewer<TileMesh> (extruded tile, textured top face)
    • TokenViewer<TokenMesh> (alpha-traced extruded token)
    • CardViewer<CardMesh> (rounded-rect card, sprite UVs)
    • CustomModelViewer<Model> (GLTF/OBJ/FBX via FlexibleModelLoader)
  • @tts/extract provides flattenObjects(mod) / traverseMod to enumerate every object in a save.
  • @tts/mesh provides the shape + extrusion helpers used by the viewers.
  • TTSObject carries no position data, so exact table placement is not recoverable — the full setup lays objects out itself.

Plan

1. Refactor viewers to expose their mesh content (no Scene)

Each viewer currently wraps its mesh in <Scene>. To compose everything into one scene, extract and export the inner mesh components, keeping the existing viewers as thin wrappers:

  • TileViewer → export TileObjectMesh (accepts a TTSObject)
  • TokenViewer → export TokenObjectMesh
  • CardViewer → export CardObjectMesh
  • CustomModelViewer → export CustomModelMesh (the <Model> + fallback box)

Each viewer now renders <Scene><XxxObjectMesh object={…} /></Scene>, so the object-facing wrapper is the single source of truth for both the per-object view and the full-setup view.

2. New page FullSetupPage.tsx at /mod/:id/setup

  • Reuse useModStore (same load path as ModPage).
  • flattenObjects(mod) → filter to renderable classes (those registered in components/viewers/register.ts: Tile, Custom_Tile, Custom_Token, Card, CardCustom, Deck, DeckCustom, Custom_Deck, Custom_Model*).
  • Render one <Scene> containing all renderable objects as <group position={…}> entries, dispatching to the right *Mesh by Name.
  • Show a summary header (total objects, rendered count, skipped count) and loading/error states matching ModPage.

3. Layout

  • Arrange objects in a grid on the "table" (fixed spacing, wrapping by row), grouping by type so cards sit together, tiles together, etc.
  • Scene's Bounds fit already auto-fits the camera to the full layout, so no camera work is needed.
  • Non-renderable objects (bags, dice, boards without assets) are skipped and counted, not dropped silently.

4. Share materials & geometry

  • Geometry cache (module-level Map<string, BufferGeometry>): key by a canonical string — card:{w}:{h}:{t}, tile:{type}:{aspect}:{t}, token:{url}:{t}, model:{meshUrl}. All cards of the same size, or tiles of the same type/size, reuse one geometry instead of rebuilding per object. Token geometry is keyed by the source image URL (the trace is cached per URL, so the silhouette is deterministic).
  • Material cache (module-level Map<string, Material>): key by textureUrl + color + roughness. drei already caches textures by URL globally, so sharing the material on top avoids per-object material allocation for tiles/tokens with the same image.
  • Cards are the exception: each card clones its texture for sprite UVs, so its face material cannot be shared — but its geometry still can (same card size).
  • Dispose shared resources on page unmount, or accept a module-level cache for the session (see Open decisions).

5. Routing & navigation

  • Add <Route path="/mod/:id/setup" element={<FullSetupPage />} /> in apps/web/src/App.tsx.
  • Add a "Full setup" link/button on ModPage next to the download button.

6. Edge cases

  • Objects with no asset (no ImageURL/MeshURL) render as neutral-colored placeholders (matching the current viewers' fallback behavior).
  • Token tracing suspends per URL (already cached in TokenViewer); the shared Scene Suspense boundary handles it.
  • Large saves: the grid + shared geometry keeps it performant, but cap or warn on very large object counts if needed.

Files touched

  • apps/web/src/components/viewers/{Tile,Token,Card,CustomModel}Viewer.tsx — export object-facing mesh wrappers
  • apps/web/src/components/viewers/sharedResources.ts — new geometry/material caches
  • apps/web/src/pages/FullSetupPage.tsx — new
  • apps/web/src/App.tsx — route
  • apps/web/src/pages/ModPage.tsx — nav link
  • Possibly a small geometryCache/materialCache helper under components/viewers/

Open decisions (defaults in bold)

  • Layout stylegrouped grid vs a fan/stack for cards.
  • Geometry/material cache lifetimesession-level module cache (simplest, consistent with drei's global texture cache) vs dispose-on-unmount.