import { useMemo } from 'react'; import type { GalleryWindowSpec } from '../../data/movement-interior-styles'; /** * Shared geometry budget for every period interior. * * Movement halls hang frames 0.23 m proud of the inner wall face * (`MOUNT_OFFSET` + `WALL_STANDOFF`), centred at eye height with tops around * 2.4 m, and each wall keeps a 0.7 m clear margin at both ends * (`WALL_PADDING` in `movementHallLayout.ts`). Period architecture therefore * has to stay inside these limits, exactly as the Gothic nave and Byzantine * basilica do: * * - relief below the frame line: at most 0.11 m proud of the wall face * - friezes and cornices: 2.6 m and above, at most 0.17 m proud * - freestanding masses: only inside the 0.34 m corner pocket at a corner * - doorways: nothing below 2.6 m within 1.45 m of a wall centre * - the end wall carries the hall title from 3.4 m to 3.9 m — keep it clear * - side-wall windows are placed at runtime between 2.4 m and 3.9 m, so * anything wider than a mullion up there goes through `useWallClearance` * - added lights: at most two per hall, so hall materials stay inside the * WebGL light limits the shared track lighting already budgets for */ /** Half of WALL_THICKNESS — distance from a wall's centre plane to its inner face. */ export const WALL_INNER = 0.09; /** Highest a detail can sit before it breaks the ceiling plane at 4.2 m. */ export const CEILING_Y = 4.13; /** Sink bands this far into the wall so they never z-fight with the surface. */ export const EMBED = 0.02; export type WallId = 'back' | 'front' | 'left' | 'right'; export interface RoomDims { width: number; depth: number; halfW: number; halfD: number; } /** Every period module takes the same three inputs. */ export interface PeriodProps { room: RoomDims; /** The movement's trim colour, already blended with its accent. */ trim: string; /** Windows the hall placed at runtime, so upper-wall relief can avoid them. */ windows?: GalleryWindowSpec[]; } /** Lighten (amount > 0) or darken (amount < 0) a hex colour. */ export function shade(hex: string, amount: number): string { const n = parseInt(hex.replace('#', ''), 16); const ch = [(n >> 16) & 255, (n >> 8) & 255, n & 255].map((c) => Math.max(0, Math.min(255, Math.round(amount >= 0 ? c + (255 - c) * amount : c * (1 + amount)))) ); return `#${((ch[0] << 16) | (ch[1] << 8) | ch[2]).toString(16).padStart(6, '0')}`; } /** Bay rhythm along the depth axis — the spacing that drives piers and beams. */ export function useBayZ(depth: number, halfD: number, spacing = 4.2, max = 9): number[] { return useMemo(() => { const count = Math.max(2, Math.min(max, Math.floor(depth / spacing))); const step = depth / (count + 1); return Array.from({ length: count }, (_, i) => -halfD + step * (i + 1)); }, [depth, halfD, spacing, max]); } /** * Evenly spaced positions centred on a wall, for the repeating small members — * dentils, linenfold ribs, modillions, stencil motifs, quatrefoils. */ export function useRepeat(span: number, step: number, max: number): number[] { return useMemo(() => { const count = Math.min(max, Math.max(3, Math.floor((span - 0.6) / step))); const run = (count - 1) * step; return Array.from({ length: count }, (_, i) => -run / 2 + i * step); }, [span, step, max]); } /** * Side-wall windows are placed at runtime in whatever gaps the hang leaves, so * any panel, arch or roundel a period module paints across the upper wall has * to step around them. Returns a predicate: true when the span is clear. */ export function useWallClearance( windows: GalleryWindowSpec[] | undefined, wall: 'left' | 'right' | 'back' ) { return useMemo(() => { const spans = (windows ?? []) .filter((w) => w.wall === wall) .map((w) => [w.x - w.width / 2 - 0.18, w.x + w.width / 2 + 0.18] as [number, number]); return (pos: number, halfSpan = 0.2) => !spans.some(([a, b]) => pos + halfSpan > a && pos - halfSpan < b); }, [windows, wall]); } /** The four corner columns of the room, each inside its hang-margin pocket. */ export function useCorners(halfW: number, halfD: number, inset = 0.08): [number, number][] { return useMemo( () => [ [-halfW + inset, -halfD + inset], [halfW - inset, -halfD + inset], [-halfW + inset, halfD - inset], [halfW - inset, halfD - inset], ] as [number, number][], [halfW, halfD, inset] ); }