feat(tabletop): author rotation and tilt angles in degrees
Convert bgm rotation and tilt values to radians only at the three.js boundary, so the format's angles are authored in degrees.
This commit is contained in:
+1
-1
@@ -371,7 +371,7 @@ layout:
|
||||
- `align` — `start`, `end`, or `center` of the curve.
|
||||
- `steps` — the maximum number of parts per curve length unit. Defaults to
|
||||
`1`. See the positioning process below.
|
||||
- `tilt` — rotation in radians per shown part about the card's local Y (long)
|
||||
- `tilt` — rotation in degrees per shown part about the card's local Y (long)
|
||||
axis. Each part tilts `tilt` more than the previous, fanning the stack so
|
||||
its edges stay visible. It applies even without a `curve`, so a bare `tilt`
|
||||
fans a straight pile.
|
||||
|
||||
@@ -108,7 +108,7 @@ export interface Stacking {
|
||||
/** Maximum parts per curve length unit; defaults to `1`. */
|
||||
steps?: number;
|
||||
/**
|
||||
* Rotation in radians per shown part about the card's local Y (long) axis.
|
||||
* Rotation in degrees per shown part about the card's local Y (long) axis.
|
||||
* Each part tilts `tilt` more than the previous, fanning the stack so its
|
||||
* edges stay visible. Works with or without a `curve`.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,9 @@ import {
|
||||
/** Scale from mm (the format's `size` unit) to world units. */
|
||||
export const MM_TO_WORLD = 1 / 30;
|
||||
|
||||
/** Degrees → radians. Angles in the bgm format are authored in degrees. */
|
||||
export const DEG_TO_RAD = Math.PI / 180;
|
||||
|
||||
/** Default part size `[w, h, d]` in mm when a part has no `size`. */
|
||||
const DEFAULT_SIZE: [number, number, number] = [60, 60, 3];
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ 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 } from './part.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;
|
||||
@@ -22,15 +22,16 @@ export function PartPlacement({ pkg, placement }: { pkg: Package; placement: Pla
|
||||
// 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` fans 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;
|
||||
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, 0, tilt]}>
|
||||
<group rotation={[-Math.PI / 2, 0, tilt * DEG_TO_RAD]}>
|
||||
<PartView part={part} baseUrl={part.baseUrl} />
|
||||
</group>
|
||||
</group>
|
||||
|
||||
@@ -14,11 +14,11 @@ export interface StackOffset {
|
||||
x: number;
|
||||
/** y offset from the anchor. */
|
||||
y: number;
|
||||
/** Rotation in radians. */
|
||||
/** Rotation in degrees. */
|
||||
rotation: number;
|
||||
/** Vertical (surface-normal) offset from the anchor, in mm. */
|
||||
z: number;
|
||||
/** Rotation in radians about the card's local Y (long) axis. */
|
||||
/** Rotation in degrees about the card's local Y (long) axis. */
|
||||
tilt: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { Package } from '@tts/bgm';
|
||||
import { useRenderState, placementKey } from '../state.js';
|
||||
import { PartPlacement } from '../placement.js';
|
||||
import type { MountNode } from '../mount.js';
|
||||
import { MM_TO_WORLD } from '../part.js';
|
||||
import { MM_TO_WORLD, DEG_TO_RAD } from '../part.js';
|
||||
import { SurfaceBounds } from './SurfaceBounds.js';
|
||||
import { SurfacePlane } from './SurfacePlane.js';
|
||||
|
||||
@@ -42,7 +42,7 @@ export function SurfaceNode({
|
||||
return (
|
||||
<group
|
||||
position={[node.x * MM_TO_WORLD, 0, node.y * MM_TO_WORLD]}
|
||||
rotation={[0, node.rotation, 0]}
|
||||
rotation={[0, node.rotation * DEG_TO_RAD, 0]}
|
||||
>
|
||||
{showSurface && (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user