Files
tts-workshop/packages/tabletop/src/placement.tsx
T
hypercross 8d0e393100 feat(tabletop): let candidates inherit and override route stacking
Candidates now carry their own stacking strategy, inheriting the route's
when absent, so the default tilt applies consistently. Drop the now
redundant explicit tilt from the poker deck.
2026-08-10 00:27:01 +08:00

42 lines
1.9 KiB
TypeScript

/**
* `PartPlacement` — position a part on a surface location.
*
* A stable per-part component that places a part at its route's anchor (plus
* the candidate's anchor when there is one) and applies the route's stacking
* strategy via `useStacking`. Renders the part's mesh with `PartView`.
*/
import type { Package } from '@tts/bgm';
import { useStacking } from './stacking.js';
import type { Placement } from './state.js';
import { PartView } from './partView.js';
import { MM_TO_WORLD, DEG_TO_RAD } from './part.js';
export function PartPlacement({ pkg, placement }: { pkg: Package; placement: Placement }) {
const { route, candidate, piece, index, stackSize } = placement;
// `piece` is `package:type#id`; the parts map is keyed by `type#id`.
const part = pkg.parts.get(piece.split(':').slice(1).join(':'));
if (!part) return null;
// A candidate inherits the route's stacking unless it overrides it.
const stacking = candidate?.stacking ?? route.stacking;
const { x, y, rotation, z, tilt } = useStacking(stacking, index, stackSize);
// Route anchors and stacking offsets are in mm; convert to world units so
// parts land on the (world-scaled) surface. `z` raises the part along the
// surface normal (world +Y); `tilt` rotates it about its local Y (long) axis.
// Angles are authored in degrees; three.js expects radians.
const anchorX = ((candidate?.x ?? route.x ?? 0) + x) * MM_TO_WORLD;
const anchorY = ((candidate?.y ?? route.y ?? 0) + y) * MM_TO_WORLD;
const anchorZ = z * MM_TO_WORLD;
const anchorRotation = ((candidate?.rotation ?? route.rotation ?? 0) + rotation) * DEG_TO_RAD;
return (
<group position={[anchorX, anchorZ, anchorY]} rotation={[0, anchorRotation, 0]}>
{/* The part mesh extrudes along +Z; lay it flat so its face points up. */}
<group rotation={[-Math.PI / 2, tilt * DEG_TO_RAD, 0]}>
<PartView part={part} baseUrl={part.baseUrl} />
</group>
</group>
);
}