Files
tts-workshop/docs/full-setup-view.md
T
hypercross 82a81c9c1a feat(web): tint objects by their ColorDiffuse
Model ColorDiffuse in shared types and multiply each viewer's material color by the per-object tint, including textured faces and loaded custom models.
2026-08-08 22:38:12 +08:00

127 lines
6.0 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
- `apps/web/src/components/viewers/sharedResources.ts``objectTint`/
`tintedColor` helpers for the per-object `ColorDiffuse` tint
- `packages/shared/src/types.ts` — add `TTSObjectTransform` and `ColorDiffuse`
- `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.