Show optimization score in optimize tab; prevent overlaps via collision resolution and overlap penalty

This commit is contained in:
2026-10-17 21:57:44 -04:00
parent 8f8211c9b7
commit d1af53cf07
10 changed files with 179 additions and 43 deletions
+61 -1
View File
@@ -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 {
x: number;
@@ -39,3 +39,63 @@ export function clampStateToRoom(state: LayoutState): LayoutState {
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);
}
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,
};
}