import { type Aabb, type Footprint, type Vec2 } from "./geometry.js"; import { type PlacementRules } from "./placement.js"; export type SnapMode = "grid" | "free" | "surface"; export type PlacementVec3 = readonly [number, number, number]; export interface PlacementHit { point: PlacementVec3; normal: PlacementVec3; } /** A fixed buildable slot the placement controller snaps to instead of grid/free positioning. */ export interface PlacementSlot { id: string; center: PlacementVec3; /** Capture radius around `center`. Defaults to `config.slotRadius`, then `1.5`. */ radius?: number; /** `false` hides the slot from hover resolution without releasing it. Default `true`. */ enabled?: boolean; } export interface PlacementControllerConfig { footprint: Footprint; rules?: PlacementRules; snapMode?: SnapMode; grid?: number; quarterTurns?: number; slots?: readonly PlacementSlot[]; /** Fallback capture radius for slots that don't set their own. Default `1.5`. */ slotRadius?: number; } export interface PlacementPreview { center: Vec2; y: number; quarterTurns: number; footprint: Footprint; aabb: Aabb; valid: boolean; reason?: "out-of-bounds" | "overlap" | "no-slot" | "territory.blocked"; snapMode: SnapMode; normal: PlacementVec3; /** Id of the resolved slot, when the controller is in slot mode and a slot matched. */ slotId?: string; } export interface PlacementCommit { center: Vec2; y: number; rotationY: number; quarterTurns: number; footprint: Footprint; aabb: Aabb; /** Id of the slot this commit occupied, when the controller is in slot mode. */ slotId?: string; } export interface PlacementController { hover(hit: PlacementHit): PlacementPreview; current(): PlacementPreview | null; rotate(steps?: number): PlacementPreview | null; setSnapMode(mode: SnapMode): PlacementPreview | null; cycleSnapMode(): SnapMode; setFootprint(footprint: Footprint): void; setRules(rules: PlacementRules): void; setGrid(grid: number): void; setSlots(slots: readonly PlacementSlot[]): void; /** Id of the currently hovered slot, or `null` when idle, out of slot mode, or unmatched. */ hoveredSlotId(): string | null; /** Frees a previously committed slot so it can match again. */ releaseSlot(id: string): void; commit(): PlacementCommit | null; reset(): void; } /** Maps 0–3 quarter turns onto radians for ghost/commit rotation. */ export declare function quarterTurnsToRotationY(quarterTurns: number): number; /** * Headless placement ghost: hover → valid/invalid preview, rotate, grid/free/surface snap, commit. * Pair with `@jgengine/shell/structures` `PlacementGhost` and {@link placeAssetFromCommit}. * @capability placement-controller interactive build-mode ghost preview and commit */ export declare function createPlacementController(config: PlacementControllerConfig): PlacementController;