import type { LayoutState, RoomConfig } from '../types'; import { colliderCenter, satMTV, type Collider } from './collision'; import { overlapsAny } from './collision'; export { overlapsAny }; export interface Bounds { x: number; y: number; width: number; height: number; } export function clampRectToRoom(rect: Collider, room: RoomConfig, minSize = 10): Collider { const width = Math.min(Math.max(minSize, rect.width), room.width); const height = Math.min(Math.max(minSize, rect.height), room.height); const x = Math.min(Math.max(0, rect.x), Math.max(0, room.width - width)); const y = Math.min(Math.max(0, rect.y), Math.max(0, room.height - height)); return { ...rect, x, y, width, height }; } export function clampStateToRoom(state: LayoutState): LayoutState { const room = state.room; const furniture = state.furniture.map((f) => { const b = clampRectToRoom( { x: f.shape.x, y: f.shape.y, width: f.shape.width, height: f.shape.height }, room, ); return { ...f, shape: { ...f.shape, x: b.x, y: b.y, width: b.width, height: b.height } }; }); let entrance = state.entrance; if (entrance) { const ev = clampRectToRoom( { x: entrance.x, y: entrance.y, width: entrance.width ?? 20, height: entrance.height ?? 20 }, room, 10, ); entrance = { ...entrance, x: ev.x, y: ev.y, width: ev.width, height: ev.height }; } return { ...state, furniture, entrance }; } export function resolveMove(moving: Collider, others: Collider[], room: RoomConfig, minSize = 10): Collider { let r = clampRectToRoom(moving, room, minSize); for (let iter = 0; iter < 12; iter++) { let pushed = false; for (const o of others) { const mtv = satMTV(r, o); if (!mtv) continue; const cM = colliderCenter(r); const cO = colliderCenter(o); const dir = (cM.x - cO.x) * mtv.axis.x + (cM.y - cO.y) * mtv.axis.y; const sign = dir >= 0 ? 1 : -1; r = clampRectToRoom( { ...r, x: r.x + sign * mtv.axis.x * mtv.depth, y: r.y + sign * mtv.axis.y * mtv.depth }, room, minSize, ); pushed = true; } if (!pushed) break; } return r; } export function findFreeSpot(room: RoomConfig, size: { width: number; height: number }, others: Collider[], tries = 40): Collider { for (let i = 0; i < tries; i++) { const candidate = clampRectToRoom( { x: Math.random() * Math.max(0, room.width - size.width), y: Math.random() * Math.max(0, room.height - size.height), width: size.width, height: size.height, }, room, ); if (!overlapsAny(candidate, others)) return candidate; const resolved = resolveMove(candidate, others, room); if (!overlapsAny(resolved, others)) return resolved; } return clampRectToRoom({ x: 0, y: 0, width: size.width, height: size.height }, room); } export function entranceCollider(state: LayoutState): Collider | null { if (!state.entrance) return null; return { x: state.entrance.x, y: state.entrance.y, width: state.entrance.width ?? 20, height: state.entrance.height ?? 20, }; } export function furnitureCollider(f: { shape: { x: number; y: number; width: number; height: number; rotation?: number } }): Collider { return { x: f.shape.x, y: f.shape.y, width: f.shape.width, height: f.shape.height, rotation: f.shape.rotation }; }