Add venue planner: OOP furniture, shapes, conditions, room/birds-eye UI, layout+optimize modes, pathfinding, random search + NN stub

This commit is contained in:
2026-10-17 19:41:09 -04:00
parent 767d9502d6
commit a708a0cfe5
14 changed files with 985 additions and 118 deletions
+86
View File
@@ -0,0 +1,86 @@
import type { Point, Rect, ShapeData } from '../types';
export class Shape {
type: ShapeData['type'];
x: number;
y: number;
width: number;
height: number;
rotation: number;
points: Point[];
constructor(data: ShapeData) {
this.type = data.type;
this.x = data.x;
this.y = data.y;
this.width = Math.max(1, data.width);
this.height = Math.max(1, data.height);
this.rotation = data.rotation ?? 0;
this.points = data.points ? data.points.map((p) => ({ x: p.x, y: p.y })) : [];
}
clone(): Shape {
return new Shape({
type: this.type,
x: this.x,
y: this.y,
width: this.width,
height: this.height,
rotation: this.rotation,
points: this.points.map((p) => ({ x: p.x, y: p.y })),
});
}
getBounds(): Rect {
if (this.type === 'rectangle') {
return { x: this.x, y: this.y, width: this.width, height: this.height };
}
if (this.type === 'circle') {
const r = Math.max(this.width, this.height) / 2;
return { x: this.x - r, y: this.y - r, width: r * 2, height: r * 2 };
}
if (this.points.length > 0) {
const xs = this.points.map((p) => p.x);
const ys = this.points.map((p) => p.y);
const minX = Math.min(...xs);
const maxX = Math.max(...xs);
const minY = Math.min(...ys);
const maxY = Math.max(...ys);
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
}
return { x: this.x, y: this.y, width: this.width, height: this.height };
}
containsPoint(p: Point): boolean {
const b = this.getBounds();
if (p.x < b.x || p.x > b.x + b.width || p.y < b.y || p.y > b.y + b.height) {
if (this.type !== 'polygon') return false;
}
if (this.type === 'circle') {
const cx = b.x + b.width / 2;
const cy = b.y + b.height / 2;
const r = b.width / 2;
const dx = p.x - cx;
const dy = p.y - cy;
return dx * dx + dy * dy <= r * r + 1e-8;
}
if (this.type === 'polygon' && this.points.length > 0) {
let inside = false;
for (let i = 0, j = this.points.length - 1; i < this.points.length; j = i++) {
const xi = this.points[i].x;
const yi = this.points[i].y;
const xj = this.points[j].x;
const yj = this.points[j].y;
const intersect = yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / ((yj - yi) || 1e-9) + xi;
if (intersect) inside = !inside;
}
return inside;
}
return p.x >= b.x && p.x <= b.x + b.width && p.y >= b.y && p.y <= b.y + b.height;
}
intersectsRect(r: Rect): boolean {
const b = this.getBounds();
return !(r.x + r.width < b.x || r.x > b.x + b.width || r.y + r.height < b.y || r.y > b.y + b.height);
}
}