fix(tabletop): align cards to the stacking curve
Fix the tangent angle at the curve's start and rotate cards to follow the curve direction. Add a stacking curve visualization shown with surface debug.
This commit is contained in:
@@ -12,6 +12,7 @@ import { PartPlacement } from '../placement.js';
|
||||
import type { MountNode } from '../mount.js';
|
||||
import { MM_TO_WORLD } from '../part.js';
|
||||
import { SurfaceBounds } from './SurfaceBounds.js';
|
||||
import { StackingCurve } from './StackingCurve.js';
|
||||
import { SurfaceNode } from './WorldSurfaceView.js';
|
||||
|
||||
export function HudSurfaceView({
|
||||
@@ -33,7 +34,14 @@ export function HudSurfaceView({
|
||||
distanceFactor={1}
|
||||
style={{ pointerEvents: 'none' }}
|
||||
>
|
||||
{showSurface && <SurfaceBounds surface={node.surface} />}
|
||||
{showSurface && (
|
||||
<>
|
||||
<SurfaceBounds surface={node.surface} />
|
||||
{node.surface.layout.map((route, i) =>
|
||||
route.stacking ? <StackingCurve key={i} route={route} /> : null,
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{own.map((p) => (
|
||||
<PartPlacement key={placementKey(p)} pkg={pkg} placement={p} />
|
||||
))}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* `StackingCurve` — a debug visualization of a route's stacking `curve`.
|
||||
*
|
||||
* Draws the SVG path (mm, converted to world units) as a line on the surface,
|
||||
* plus a small marker at the curve's start. The curve is defined relative to
|
||||
* the route's anchor, so the points are offset by the anchor's `x`/`y`. Used
|
||||
* by the surface views when `showSurface` is enabled, so the curve's shape and
|
||||
* direction are visible.
|
||||
*/
|
||||
import { Line } from '@react-three/drei';
|
||||
import type { Route } from '@tts/bgm';
|
||||
import { parsePath } from '../stacking.js';
|
||||
import { MM_TO_WORLD } from '../part.js';
|
||||
|
||||
export function StackingCurve({ route }: { route: Route }) {
|
||||
if (!route.stacking?.curve) return null;
|
||||
const path = parsePath(route.stacking.curve);
|
||||
if (path.points.length === 0) return null;
|
||||
|
||||
// Curve coords are in mm relative to the route anchor (x, y); map to world
|
||||
// (x, z) and offset by the anchor, matching how parts are placed.
|
||||
const points: [number, number, number][] = path.points.map((p) => [
|
||||
(route.x + p.x) * MM_TO_WORLD,
|
||||
0,
|
||||
(route.y + p.y) * MM_TO_WORLD,
|
||||
]);
|
||||
|
||||
const start = points[0]!;
|
||||
|
||||
return (
|
||||
<group>
|
||||
<Line points={points} color="#f59e0b" lineWidth={2} />
|
||||
{/* Marker at the curve's start, showing its direction. */}
|
||||
<mesh position={[start[0], 0.01, start[2]]}>
|
||||
<sphereGeometry args={[0.01, 8, 8]} />
|
||||
<meshBasicMaterial color="#f59e0b" />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import type { MountNode } from '../mount.js';
|
||||
import { MM_TO_WORLD, DEG_TO_RAD } from '../part.js';
|
||||
import { SurfaceBounds } from './SurfaceBounds.js';
|
||||
import { SurfacePlane } from './SurfacePlane.js';
|
||||
import { StackingCurve } from './StackingCurve.js';
|
||||
|
||||
export function WorldSurfaceView({
|
||||
pkg,
|
||||
@@ -48,6 +49,9 @@ export function SurfaceNode({
|
||||
<>
|
||||
<SurfacePlane surface={node.surface} />
|
||||
<SurfaceBounds surface={node.surface} />
|
||||
{node.surface.layout.map((route, i) =>
|
||||
route.stacking ? <StackingCurve key={i} route={route} /> : null,
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{own.map((p) => (
|
||||
|
||||
Reference in New Issue
Block a user