Show optimization score in optimize tab; prevent overlaps via collision resolution and overlap penalty
This commit is contained in:
Vendored
+2
-2
@@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>furniture-app</title>
|
<title>furniture-app</title>
|
||||||
<script type="module" crossorigin src="/assets/index-CCxBSCEa.js"></script>
|
<script type="module" crossorigin src="/assets/index-BNpMmn-1.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Gl7EQEpA.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-9k24Ci9h.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
+6
-1
@@ -20,6 +20,7 @@ function App() {
|
|||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const [optimizing, setOptimizing] = useState(false);
|
const [optimizing, setOptimizing] = useState(false);
|
||||||
const [timeLeft, setTimeLeft] = useState(15);
|
const [timeLeft, setTimeLeft] = useState(15);
|
||||||
|
const [lastScore, setLastScore] = useState<number | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKey = (e: KeyboardEvent) => {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
@@ -39,23 +40,26 @@ function App() {
|
|||||||
if (timeLeft <= 0) {
|
if (timeLeft <= 0) {
|
||||||
const res = optimizeWithNN(state, { gridSize: 10 });
|
const res = optimizeWithNN(state, { gridSize: 10 });
|
||||||
setState(res.bestState);
|
setState(res.bestState);
|
||||||
|
setLastScore(res.bestEval.totalScore);
|
||||||
setOptimizing(false);
|
setOptimizing(false);
|
||||||
setTimeLeft(15);
|
setTimeLeft(15);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const t = setTimeout(() => setTimeLeft((v) => v - 1), 1000);
|
const t = setTimeout(() => setTimeLeft((v) => v - 1), 1000);
|
||||||
const { bestState } = runRandomSearch(state, {
|
const { bestState, bestEval } = runRandomSearch(state, {
|
||||||
iterations: 5,
|
iterations: 5,
|
||||||
roomWidth: state.room.width,
|
roomWidth: state.room.width,
|
||||||
roomHeight: state.room.height,
|
roomHeight: state.room.height,
|
||||||
gridSize: state.room.gridSize,
|
gridSize: state.room.gridSize,
|
||||||
});
|
});
|
||||||
setState(bestState);
|
setState(bestState);
|
||||||
|
setLastScore(bestEval.totalScore);
|
||||||
return () => clearTimeout(t);
|
return () => clearTimeout(t);
|
||||||
}, [optimizing, timeLeft]);
|
}, [optimizing, timeLeft]);
|
||||||
|
|
||||||
const handleOptimize = () => {
|
const handleOptimize = () => {
|
||||||
setState((s) => prepareForOptimize(s));
|
setState((s) => prepareForOptimize(s));
|
||||||
|
setLastScore(null);
|
||||||
setOptimizing(true);
|
setOptimizing(true);
|
||||||
setTimeLeft(15);
|
setTimeLeft(15);
|
||||||
};
|
};
|
||||||
@@ -72,6 +76,7 @@ function App() {
|
|||||||
onOptimize={handleOptimize}
|
onOptimize={handleOptimize}
|
||||||
optimizing={optimizing}
|
optimizing={optimizing}
|
||||||
timeLeft={timeLeft}
|
timeLeft={timeLeft}
|
||||||
|
lastScore={lastScore}
|
||||||
/>
|
/>
|
||||||
<div className="stage">
|
<div className="stage">
|
||||||
<div className="canvas-frame">
|
<div className="canvas-frame">
|
||||||
|
|||||||
+10
-5
@@ -1,6 +1,6 @@
|
|||||||
import type { LayoutEvaluation, LayoutState } from '../types';
|
import type { LayoutEvaluation, LayoutState } from '../types';
|
||||||
import { evaluateLayout } from '../layout/evaluator';
|
import { evaluateLayout } from '../layout/evaluator';
|
||||||
import { clampRectToRoom } from '../geometry/bounds';
|
import { entranceRect, findFreeSpot, overlapsAny, resolveMove } from '../geometry/bounds';
|
||||||
|
|
||||||
export interface NNOptions {
|
export interface NNOptions {
|
||||||
gridSize?: number;
|
gridSize?: number;
|
||||||
@@ -56,10 +56,15 @@ export function optimizeWithNN(state: LayoutState, options: NNOptions = {}): { b
|
|||||||
}
|
}
|
||||||
if (targetX < 0) targetX = 0;
|
if (targetX < 0) targetX = 0;
|
||||||
if (targetY < 0) targetY = 0;
|
if (targetY < 0) targetY = 0;
|
||||||
const b = clampRectToRoom(
|
const room = candidate.room;
|
||||||
{ x: targetX, y: targetY, width: f.shape.width, height: f.shape.height },
|
const others: any[] = candidate.furniture
|
||||||
candidate.room,
|
.filter((o) => o.id !== f.id)
|
||||||
);
|
.map((o) => ({ x: o.shape.x, y: o.shape.y, width: o.shape.width, height: o.shape.height }));
|
||||||
|
const ent = entranceRect(candidate);
|
||||||
|
if (ent) others.push(ent);
|
||||||
|
const b = overlapsAny({ x: targetX, y: targetY, width: f.shape.width, height: f.shape.height }, others)
|
||||||
|
? findFreeSpot(room, { width: f.shape.width, height: f.shape.height }, others)
|
||||||
|
: resolveMove({ x: targetX, y: targetY, width: f.shape.width, height: f.shape.height }, others, room);
|
||||||
f.shape.x = b.x;
|
f.shape.x = b.x;
|
||||||
f.shape.y = b.y;
|
f.shape.y = b.y;
|
||||||
f.shape.width = b.width;
|
f.shape.width = b.width;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ interface ControlPanelProps {
|
|||||||
onOptimize: () => void;
|
onOptimize: () => void;
|
||||||
optimizing: boolean;
|
optimizing: boolean;
|
||||||
timeLeft: number;
|
timeLeft: number;
|
||||||
|
lastScore: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONDITION_KINDS: ConditionKind[] = [
|
const CONDITION_KINDS: ConditionKind[] = [
|
||||||
@@ -47,6 +48,7 @@ export function ControlPanel({
|
|||||||
onOptimize,
|
onOptimize,
|
||||||
optimizing,
|
optimizing,
|
||||||
timeLeft,
|
timeLeft,
|
||||||
|
lastScore,
|
||||||
}: ControlPanelProps) {
|
}: ControlPanelProps) {
|
||||||
const [openId, setOpenId] = useState<string | null>(null);
|
const [openId, setOpenId] = useState<string | null>(null);
|
||||||
const objects = state.presets || [];
|
const objects = state.presets || [];
|
||||||
@@ -369,6 +371,12 @@ export function ControlPanel({
|
|||||||
<button className="primary" onClick={onOptimize} disabled={optimizing} style={{ width: '100%' }}>
|
<button className="primary" onClick={onOptimize} disabled={optimizing} style={{ width: '100%' }}>
|
||||||
{optimizing ? `Optimizing… ${timeLeft}s` : 'Optimize Layout'}
|
{optimizing ? `Optimizing… ${timeLeft}s` : 'Optimize Layout'}
|
||||||
</button>
|
</button>
|
||||||
|
<div className="score-row">
|
||||||
|
<span className="score-label">Score</span>
|
||||||
|
<span className="score-value">
|
||||||
|
{lastScore === null ? '—' : lastScore.toFixed(1)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{optimizing && (
|
{optimizing && (
|
||||||
<div className="progress">
|
<div className="progress">
|
||||||
<div style={{ width: `${((15 - timeLeft) / 15) * 100}%` }} />
|
<div style={{ width: `${((15 - timeLeft) / 15) * 100}%` }} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import type { LayoutState, Point } from '../types';
|
import type { LayoutState, Point } from '../types';
|
||||||
import { computePaths } from '../pathfinding/paths';
|
import { computePaths } from '../pathfinding/paths';
|
||||||
import { clampRectToRoom } from '../geometry/bounds';
|
import { resolveMove } from '../geometry/bounds';
|
||||||
import { makePlaced } from '../optimization/prepare';
|
import { makePlaced } from '../optimization/prepare';
|
||||||
|
|
||||||
interface RoomCanvasProps {
|
interface RoomCanvasProps {
|
||||||
@@ -202,16 +202,24 @@ export function RoomCanvas({ state, onStateChange, mode, selectedId, onSelect, s
|
|||||||
const pos = getMousePos(e);
|
const pos = getMousePos(e);
|
||||||
if (drag.id === '__entrance__' && state.entrance) {
|
if (drag.id === '__entrance__' && state.entrance) {
|
||||||
const en = state.entrance;
|
const en = state.entrance;
|
||||||
|
const others = state.furniture.map((o) => ({
|
||||||
|
x: o.shape.x,
|
||||||
|
y: o.shape.y,
|
||||||
|
width: o.shape.width,
|
||||||
|
height: o.shape.height,
|
||||||
|
}));
|
||||||
if (drag.resizing) {
|
if (drag.resizing) {
|
||||||
const b = clampRectToRoom(
|
const b = resolveMove(
|
||||||
{ x: en.x, y: en.y, width: pos.x - en.x, height: pos.y - en.y },
|
{ x: en.x, y: en.y, width: pos.x - en.x, height: pos.y - en.y },
|
||||||
|
others,
|
||||||
state.room,
|
state.room,
|
||||||
10,
|
10,
|
||||||
);
|
);
|
||||||
onStateChange({ ...state, entrance: { ...en, ...b } });
|
onStateChange({ ...state, entrance: { ...en, ...b } });
|
||||||
} else {
|
} else {
|
||||||
const b = clampRectToRoom(
|
const b = resolveMove(
|
||||||
{ x: pos.x - drag.offsetX, y: pos.y - drag.offsetY, width: en.width ?? 20, height: en.height ?? 20 },
|
{ x: pos.x - drag.offsetX, y: pos.y - drag.offsetY, width: en.width ?? 20, height: en.height ?? 20 },
|
||||||
|
others,
|
||||||
state.room,
|
state.room,
|
||||||
10,
|
10,
|
||||||
);
|
);
|
||||||
@@ -221,15 +229,22 @@ export function RoomCanvas({ state, onStateChange, mode, selectedId, onSelect, s
|
|||||||
}
|
}
|
||||||
const updated = state.furniture.map((f) => {
|
const updated = state.furniture.map((f) => {
|
||||||
if (f.id !== drag.id) return f;
|
if (f.id !== drag.id) return f;
|
||||||
const b = clampRectToRoom(
|
const others: any[] = state.furniture
|
||||||
|
.filter((o) => o.id !== drag.id)
|
||||||
|
.map((o) => ({ x: o.shape.x, y: o.shape.y, width: o.shape.width, height: o.shape.height }));
|
||||||
|
if (state.entrance) {
|
||||||
|
const e = state.entrance;
|
||||||
|
others.push({ x: e.x, y: e.y, width: e.width ?? 20, height: e.height ?? 20 });
|
||||||
|
}
|
||||||
|
const b = resolveMove(
|
||||||
{
|
{
|
||||||
x: pos.x - drag.offsetX,
|
x: pos.x - drag.offsetX,
|
||||||
y: pos.y - drag.offsetY,
|
y: pos.y - drag.offsetY,
|
||||||
width: f.shape.width,
|
width: f.shape.width,
|
||||||
height: f.shape.height,
|
height: f.shape.height,
|
||||||
},
|
},
|
||||||
|
others,
|
||||||
state.room,
|
state.room,
|
||||||
10,
|
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
...f,
|
...f,
|
||||||
|
|||||||
+61
-1
@@ -1,4 +1,4 @@
|
|||||||
import type { Entrance, FurnitureConfig, LayoutState, Rect, RoomConfig } from '../types';
|
import type { Entrance, FurnitureConfig, LayoutState, Rect, RoomConfig, Size } from '../types';
|
||||||
|
|
||||||
export interface Bounds {
|
export interface Bounds {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -39,3 +39,63 @@ export function clampStateToRoom(state: LayoutState): LayoutState {
|
|||||||
export function rectsOverlap(a: Rect, b: Rect): boolean {
|
export function rectsOverlap(a: Rect, b: Rect): boolean {
|
||||||
return !(a.x + a.width <= b.x || b.x + b.width <= a.x || a.y + a.height <= b.y || b.y + b.height <= a.y);
|
return !(a.x + a.width <= b.x || b.x + b.width <= a.x || a.y + a.height <= b.y || b.y + b.height <= a.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function overlapsAny(rect: Rect, others: Rect[]): boolean {
|
||||||
|
return others.some((o) => rectsOverlap(rect, o));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveMove(moving: Bounds, others: Rect[], room: RoomConfig, minSize = 10): Bounds {
|
||||||
|
let r = clampRectToRoom(moving, room, minSize);
|
||||||
|
for (let iter = 0; iter < 12; iter++) {
|
||||||
|
let pushed = false;
|
||||||
|
for (const o of others) {
|
||||||
|
if (!rectsOverlap(r, o)) continue;
|
||||||
|
const overlapLeft = r.x + r.width - o.x;
|
||||||
|
const overlapRight = o.x + o.width - r.x;
|
||||||
|
const overlapTop = r.y + r.height - o.y;
|
||||||
|
const overlapBottom = o.y + o.height - r.y;
|
||||||
|
const minX = Math.min(overlapLeft, overlapRight);
|
||||||
|
const minY = Math.min(overlapTop, overlapBottom);
|
||||||
|
if (minX < minY) {
|
||||||
|
r.x = overlapLeft < overlapRight ? o.x - r.width : o.x + o.width;
|
||||||
|
} else {
|
||||||
|
r.y = overlapTop < overlapBottom ? o.y - r.height : o.y + o.height;
|
||||||
|
}
|
||||||
|
r = clampRectToRoom(r, room, minSize);
|
||||||
|
pushed = true;
|
||||||
|
}
|
||||||
|
if (!pushed) break;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findFreeSpot(room: RoomConfig, size: Size, others: Rect[], tries = 40): Bounds {
|
||||||
|
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 entranceRect(state: LayoutState): Rect | 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -503,6 +503,31 @@ label {
|
|||||||
transition: width 0.9s linear;
|
transition: width 0.9s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.score-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-input);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-mute);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
color: var(--text-mute);
|
color: var(--text-mute);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { AccessibilityResult, FurnitureConfig, LayoutEvaluation, LayoutState, Point, Rect } from '../types';
|
import type { AccessibilityResult, FurnitureConfig, LayoutEvaluation, LayoutState, Point, Rect } from '../types';
|
||||||
import { buildGrid, gridToWorld, nearestWalkable } from '../pathfinding/grid';
|
import { buildGrid, gridToWorld, nearestWalkable } from '../pathfinding/grid';
|
||||||
import { aStar } from '../pathfinding/aStar';
|
import { aStar } from '../pathfinding/aStar';
|
||||||
|
import { entranceRect, rectsOverlap } from '../geometry/bounds';
|
||||||
|
|
||||||
export function evaluateLayout(state: LayoutState, gridSize: number = 10): LayoutEvaluation {
|
export function evaluateLayout(state: LayoutState, gridSize: number = 10): LayoutEvaluation {
|
||||||
const obstacles: Rect[] = state.furniture
|
const obstacles: Rect[] = state.furniture
|
||||||
@@ -18,6 +19,18 @@ export function evaluateLayout(state: LayoutState, gridSize: number = 10): Layou
|
|||||||
let totalConditionScore = 0;
|
let totalConditionScore = 0;
|
||||||
const conditionScores: Record<string, number> = {};
|
const conditionScores: Record<string, number> = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < obstacles.length; i++) {
|
||||||
|
for (let j = i + 1; j < obstacles.length; j++) {
|
||||||
|
if (rectsOverlap(obstacles[i], obstacles[j])) penalty += 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const entrance = entranceRect(state);
|
||||||
|
if (entrance) {
|
||||||
|
for (const o of obstacles) {
|
||||||
|
if (rectsOverlap(o, entrance)) penalty += 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const entrancePoint = state.entrance ? { x: state.entrance.x, y: state.entrance.y } : { x: 0, y: 0 };
|
const entrancePoint = state.entrance ? { x: state.entrance.x, y: state.entrance.y } : { x: 0, y: 0 };
|
||||||
const entranceCell = nearestWalkable(grid, entrancePoint, gridSize);
|
const entranceCell = nearestWalkable(grid, entrancePoint, gridSize);
|
||||||
const entranceWorld = entranceCell ? gridToWorld(entranceCell.col, entranceCell.row, gridSize) : entrancePoint;
|
const entranceWorld = entranceCell ? gridToWorld(entranceCell.col, entranceCell.row, gridSize) : entrancePoint;
|
||||||
|
|||||||
+25
-18
@@ -1,16 +1,13 @@
|
|||||||
import type { FurnitureConfig, FurniturePreset, LayoutState, RoomConfig } from '../types';
|
import type { FurnitureConfig, FurniturePreset, LayoutState, Rect, RoomConfig } from '../types';
|
||||||
import { clampRectToRoom, clampStateToRoom } from '../geometry/bounds';
|
import { clampStateToRoom, entranceRect, findFreeSpot, resolveMove } from '../geometry/bounds';
|
||||||
|
|
||||||
export function makeGenerated(preset: FurniturePreset, index: number, room: RoomConfig): FurnitureConfig {
|
export function makeGenerated(
|
||||||
const b = clampRectToRoom(
|
preset: FurniturePreset,
|
||||||
{
|
index: number,
|
||||||
x: Math.random() * Math.max(0, room.width - preset.shape.width),
|
room: RoomConfig,
|
||||||
y: Math.random() * Math.max(0, room.height - preset.shape.height),
|
others: Rect[] = [],
|
||||||
width: preset.shape.width,
|
): FurnitureConfig {
|
||||||
height: preset.shape.height,
|
const b = findFreeSpot(room, { width: preset.shape.width, height: preset.shape.height }, others);
|
||||||
},
|
|
||||||
room,
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
id: `${preset.id}_gen_${Date.now()}_${index}`,
|
id: `${preset.id}_gen_${Date.now()}_${index}`,
|
||||||
name: preset.name,
|
name: preset.name,
|
||||||
@@ -22,11 +19,14 @@ export function makeGenerated(preset: FurniturePreset, index: number, room: Room
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makePlaced(preset: FurniturePreset, x: number, y: number, room: RoomConfig): FurnitureConfig {
|
export function makePlaced(
|
||||||
const b = clampRectToRoom(
|
preset: FurniturePreset,
|
||||||
{ x, y, width: preset.shape.width, height: preset.shape.height },
|
x: number,
|
||||||
room,
|
y: number,
|
||||||
);
|
room: RoomConfig,
|
||||||
|
others: Rect[] = [],
|
||||||
|
): FurnitureConfig {
|
||||||
|
const b = resolveMove({ x, y, width: preset.shape.width, height: preset.shape.height }, others, room);
|
||||||
return {
|
return {
|
||||||
id: `${preset.id}_placed_${Date.now()}_${Math.round(Math.random() * 1e6)}`,
|
id: `${preset.id}_placed_${Date.now()}_${Math.round(Math.random() * 1e6)}`,
|
||||||
name: preset.name,
|
name: preset.name,
|
||||||
@@ -41,6 +41,7 @@ export function makePlaced(preset: FurniturePreset, x: number, y: number, room:
|
|||||||
|
|
||||||
export function prepareForOptimize(state: LayoutState): LayoutState {
|
export function prepareForOptimize(state: LayoutState): LayoutState {
|
||||||
const presets = state.presets || [];
|
const presets = state.presets || [];
|
||||||
|
const entrance = entranceRect(state);
|
||||||
let furniture = [...state.furniture];
|
let furniture = [...state.furniture];
|
||||||
|
|
||||||
for (const preset of presets) {
|
for (const preset of presets) {
|
||||||
@@ -54,7 +55,13 @@ export function prepareForOptimize(state: LayoutState): LayoutState {
|
|||||||
generated = generated.slice(0, need);
|
generated = generated.slice(0, need);
|
||||||
}
|
}
|
||||||
while (generated.length < need) {
|
while (generated.length < need) {
|
||||||
generated.push(makeGenerated(preset, generated.length, state.room));
|
const others: Rect[] = [
|
||||||
|
...placed,
|
||||||
|
...generated,
|
||||||
|
...furniture.filter((f) => f.type !== preset.id),
|
||||||
|
...(entrance ? [entrance] : []),
|
||||||
|
].map((f: any) => ({ x: f.shape.x, y: f.shape.y, width: f.shape.width, height: f.shape.height }));
|
||||||
|
generated.push(makeGenerated(preset, generated.length, state.room, others));
|
||||||
}
|
}
|
||||||
|
|
||||||
const synced = [...placed, ...generated].map((f) => ({
|
const synced = [...placed, ...generated].map((f) => ({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { LayoutEvaluation, LayoutState } from '../types';
|
import type { LayoutEvaluation, LayoutState, Rect } from '../types';
|
||||||
import { evaluateLayout } from '../layout/evaluator';
|
import { evaluateLayout } from '../layout/evaluator';
|
||||||
import { Furniture } from '../furniture/Furniture';
|
import { Furniture } from '../furniture/Furniture';
|
||||||
import { clampRectToRoom } from '../geometry/bounds';
|
import { entranceRect, findFreeSpot } from '../geometry/bounds';
|
||||||
|
|
||||||
export interface RandomSearchOptions {
|
export interface RandomSearchOptions {
|
||||||
iterations: number;
|
iterations: number;
|
||||||
@@ -19,17 +19,15 @@ export function runRandomSearch(
|
|||||||
|
|
||||||
for (let i = 0; i < options.iterations; i++) {
|
for (let i = 0; i < options.iterations; i++) {
|
||||||
const candidate = cloneState(bestState);
|
const candidate = cloneState(bestState);
|
||||||
|
const room = { width: options.roomWidth, height: options.roomHeight };
|
||||||
|
const entrance = entranceRect(candidate);
|
||||||
for (const f of candidate.furniture) {
|
for (const f of candidate.furniture) {
|
||||||
if (f.lockedForOptimization) continue;
|
if (f.lockedForOptimization) continue;
|
||||||
const b = clampRectToRoom(
|
const others: Rect[] = candidate.furniture
|
||||||
{
|
.filter((o) => o.id !== f.id)
|
||||||
x: Math.random() * Math.max(0, options.roomWidth - f.shape.width),
|
.map((o) => ({ x: o.shape.x, y: o.shape.y, width: o.shape.width, height: o.shape.height }));
|
||||||
y: Math.random() * Math.max(0, options.roomHeight - f.shape.height),
|
if (entrance) others.push(entrance);
|
||||||
width: f.shape.width,
|
const b = findFreeSpot(room, { width: f.shape.width, height: f.shape.height }, others);
|
||||||
height: f.shape.height,
|
|
||||||
},
|
|
||||||
{ width: options.roomWidth, height: options.roomHeight },
|
|
||||||
);
|
|
||||||
f.shape.x = b.x;
|
f.shape.x = b.x;
|
||||||
f.shape.y = b.y;
|
f.shape.y = b.y;
|
||||||
f.shape.width = b.width;
|
f.shape.width = b.width;
|
||||||
|
|||||||
Reference in New Issue
Block a user