Files
furniture/src/layout/evaluator.ts
T

95 lines
3.2 KiB
TypeScript

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<string, number> = {};
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;
}
}