Model TTSObjectTransform in shared types and convert each object's position, rotation, and scale into three.js placement, with per-class base-size corrections and a lay-flat rotation for extruded meshes.
125 lines
5.8 KiB
Markdown
125 lines
5.8 KiB
Markdown
# 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.
|
|
- Every object in a save carries a `Transform` (position/rotation/scale in TTS
|
|
world units), which the proxy passes through in the raw BSON. The shared
|
|
type now models it as `TTSObjectTransform`.
|
|
|
|
## 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
|
|
|
|
- Place each object at its real position from the save's `Transform` instead of
|
|
a generated grid.
|
|
- `components/viewers/transform.ts` converts a TTS transform to three.js:
|
|
TTS is left-handed (Y up, +Z toward the player), three.js is right-handed,
|
|
so Z is reflected in position and rotation. Each class also gets a base-size
|
|
correction (our viewer meshes are authored for inspection, in arbitrary
|
|
units) and a "lay flat" rotation for the extruded tile/token/card meshes.
|
|
- `Scene`'s `Bounds fit` 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/components/viewers/transform.ts` — new TTS→three.js placement
|
|
conversion
|
|
- `packages/shared/src/types.ts` — add `TTSObjectTransform`
|
|
- `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 style** — **real `Transform` placement** (was a grouped grid before
|
|
the transform data was wired in).
|
|
- **Geometry/material cache lifetime** — **session-level module cache**
|
|
(simplest, consistent with drei's global texture cache) vs dispose-on-unmount. |