Add venue planner: OOP furniture, shapes, conditions, room/birds-eye UI, layout+optimize modes, pathfinding, random search + NN stub
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user