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.
This commit is contained in:
@@ -11,6 +11,7 @@ import Scene from './Scene';
|
||||
import { assetUrl } from './assetUrl';
|
||||
import { cardAspect, resolveCardConfig, spriteUv } from './cardResolution';
|
||||
import { flipTexture } from './flipTexture';
|
||||
import { getSharedGeometry } from './sharedResources';
|
||||
|
||||
/** Longer card dimension, in world units. */
|
||||
const CARD_LENGTH = 2;
|
||||
@@ -49,25 +50,36 @@ const FALLBACK_URL =
|
||||
* the card.
|
||||
*/
|
||||
export default function CardViewer({ object }: { object: TTSObject }) {
|
||||
const { cardId, faceUrl, backUrl, numWidth, numHeight, uniqueBack } =
|
||||
resolveCardConfig(object);
|
||||
|
||||
return (
|
||||
<Scene>
|
||||
<CardMesh
|
||||
faceUrl={faceUrl}
|
||||
backUrl={backUrl}
|
||||
numWidth={numWidth}
|
||||
numHeight={numHeight}
|
||||
uniqueBack={uniqueBack}
|
||||
cardId={cardId}
|
||||
/>
|
||||
<CardObjectMesh object={object} />
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The card mesh for an object, exported so the full-setup view can compose it
|
||||
* into a shared scene.
|
||||
*/
|
||||
export function CardObjectMesh({ object }: { object: TTSObject }) {
|
||||
const { cardId, faceUrl, backUrl, numWidth, numHeight, uniqueBack } =
|
||||
resolveCardConfig(object);
|
||||
|
||||
return (
|
||||
<CardMesh
|
||||
faceUrl={faceUrl}
|
||||
backUrl={backUrl}
|
||||
numWidth={numWidth}
|
||||
numHeight={numHeight}
|
||||
uniqueBack={uniqueBack}
|
||||
cardId={cardId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Rendered inside the Canvas so `useTexture` can access the R3F store.
|
||||
function CardMesh({
|
||||
// Exported so the full-setup view can compose it into a shared scene.
|
||||
export function CardMesh({
|
||||
faceUrl,
|
||||
backUrl,
|
||||
numWidth,
|
||||
@@ -116,7 +128,9 @@ function CardMesh({
|
||||
|
||||
// Build the rounded-rect geometry from the card sprite's aspect ratio. The
|
||||
// front and back faces each get their own material; the walls are a solid
|
||||
// white, matching TTS card tinting.
|
||||
// white, matching TTS card tinting. Geometry is shared across cards of the
|
||||
// same size so the full-setup view reuses it; the face/back materials stay
|
||||
// per-card because each card clones its texture for sprite UVs.
|
||||
const { frontGeo, backGeo, wallsGeo } = useMemo(() => {
|
||||
const img = (faceUrl ? face.image : backUrl ? back.image : undefined) as
|
||||
| HTMLImageElement
|
||||
@@ -127,13 +141,12 @@ function CardMesh({
|
||||
// Radius scales with the shorter edge so corners look proportional and
|
||||
// stay circular (no scaling distortion).
|
||||
const shape = roundedRectShape(width, height, CORNER_RADIUS * Math.min(width, height));
|
||||
const { front: frontGeo, back: backGeo, walls: wallsGeo } = extrudeShapeParts(shape, {
|
||||
height: CARD_THICKNESS,
|
||||
});
|
||||
const parts = extrudeShapeParts(shape, { height: CARD_THICKNESS });
|
||||
const key = `card:${width}:${height}:${CARD_THICKNESS}`;
|
||||
return {
|
||||
frontGeo: toGeometry(frontGeo),
|
||||
backGeo: toGeometry(backGeo),
|
||||
wallsGeo: toGeometry(wallsGeo),
|
||||
frontGeo: getSharedGeometry(key + ':front', () => toGeometry(parts.front)),
|
||||
backGeo: getSharedGeometry(key + ':back', () => toGeometry(parts.back)),
|
||||
wallsGeo: getSharedGeometry(key + ':walls', () => toGeometry(parts.walls)),
|
||||
};
|
||||
}, [faceUrl, face, backUrl, back, numWidth, numHeight]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user