import type { AccessibilityResult, FurnitureConfig, LayoutEvaluation, LayoutState, Point, Rect } from '../types'; import { buildGrid } from '../pathfinding/grid'; import { aStar } from '../pathfinding/aStar'; export function evaluateLayout(state: LayoutState, gridSize: number = 10): LayoutEvaluation { const obstacles: Rect[] = state.furniture .map((f) => ({ x: f.shape.x, y: f.shape.y, width: f.shape.width, height: f.shape.height, })) .filter((r) => r.width > 0 && r.height > 0); const grid = buildGrid(state.room, obstacles, gridSize); const accessibility: AccessibilityResult[] = []; let penalty = 0; let totalConditionScore = 0; const conditionScores: Record = {}; const entrancePoint = state.entrance ? { x: state.entrance.x, y: state.entrance.y } : { x: 0, y: 0 }; for (const f of state.furniture) { const cx = f.shape.x + f.shape.width / 2; const cy = f.shape.y + f.shape.height / 2; const target: Point = { x: cx, y: cy }; const path = aStar(entrancePoint, target, grid, gridSize); if (path === null) { accessibility.push({ furnitureId: f.id, distanceFromEntrance: null, reachable: false, pathLength: null }); penalty += 100; } else { let len = 0; for (let i = 1; i < path.length; i++) { const p0 = path[i - 1]; const p1 = path[i]; len += Math.sqrt((p0.x - p1.x) ** 2 + (p0.y - p1.y) ** 2); } accessibility.push({ furnitureId: f.id, distanceFromEntrance: len, reachable: true, pathLength: len }); } let score = 0; for (const c of f.conditions) { const s = scoreCondition(c.kind, f, state, accessibility.find((a) => a.furnitureId === f.id) || null, path); score += (s * (c.weight || 1)) / (f.conditions.length || 1); } conditionScores[f.id] = score; totalConditionScore += score; } const totalScore = totalConditionScore - penalty; return { totalScore, conditionScores, accessibility, penalty }; } function scoreCondition( kind: string, f: FurnitureConfig, state: LayoutState, acc: AccessibilityResult | null, path: Point[] | null, ): number { switch (kind) { case 'walk_to_and_sit': { if (acc?.reachable) return Math.min(1, 1 / ((acc.pathLength || 1) / 50 + 1e-6)); return 0; } case 'around_open_area': { const center = { x: state.room.width / 2, y: state.room.height / 2 }; const cx = f.shape.x + f.shape.width / 2; const cy = f.shape.y + f.shape.height / 2; const distCenter = Math.sqrt((cx - center.x) ** 2 + (cy - center.y) ** 2); return Math.max(0, 1 - distCenter / Math.max(state.room.width, state.room.height)); } case 'near_entrance': { if (!state.entrance) return 0; const cx = f.shape.x + f.shape.width / 2; const cy = f.shape.y + f.shape.height / 2; const ex = state.entrance.x; const ey = state.entrance.y; const d = Math.sqrt((cx - ex) ** 2 + (cy - ey) ** 2); return Math.max(0, 1 - d / 200); } case 'not_blocking_path': { return acc?.reachable ? 0.8 : 0.2; } case 'accessible': { return acc?.reachable ? 1.0 : 0.0; } case 'open_circulation': { return path && path.length > 2 ? 0.7 : 0.5; } default: return 0.5; } }