97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
import type { LayoutState, RoomConfig } from '../types';
|
|
import { colliderCenter, satMTV, type Collider } from './collision';
|
|
import { overlapsAny } from './collision';
|
|
|
|
export { overlapsAny };
|
|
|
|
export interface Bounds {
|
|
x: number;
|
|
y: number;
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
export function clampRectToRoom(rect: Collider, room: RoomConfig, minSize = 10): Collider {
|
|
const width = Math.min(Math.max(minSize, rect.width), room.width);
|
|
const height = Math.min(Math.max(minSize, rect.height), room.height);
|
|
const x = Math.min(Math.max(0, rect.x), Math.max(0, room.width - width));
|
|
const y = Math.min(Math.max(0, rect.y), Math.max(0, room.height - height));
|
|
return { ...rect, x, y, width, height };
|
|
}
|
|
|
|
export function clampStateToRoom(state: LayoutState): LayoutState {
|
|
const room = state.room;
|
|
const furniture = state.furniture.map((f) => {
|
|
const b = clampRectToRoom(
|
|
{ x: f.shape.x, y: f.shape.y, width: f.shape.width, height: f.shape.height },
|
|
room,
|
|
);
|
|
return { ...f, shape: { ...f.shape, x: b.x, y: b.y, width: b.width, height: b.height } };
|
|
});
|
|
let entrance = state.entrance;
|
|
if (entrance) {
|
|
const ev = clampRectToRoom(
|
|
{ x: entrance.x, y: entrance.y, width: entrance.width ?? 20, height: entrance.height ?? 20 },
|
|
room,
|
|
10,
|
|
);
|
|
entrance = { ...entrance, x: ev.x, y: ev.y, width: ev.width, height: ev.height };
|
|
}
|
|
return { ...state, furniture, entrance };
|
|
}
|
|
|
|
export function resolveMove(moving: Collider, others: Collider[], room: RoomConfig, minSize = 10): Collider {
|
|
let r = clampRectToRoom(moving, room, minSize);
|
|
for (let iter = 0; iter < 12; iter++) {
|
|
let pushed = false;
|
|
for (const o of others) {
|
|
const mtv = satMTV(r, o);
|
|
if (!mtv) continue;
|
|
const cM = colliderCenter(r);
|
|
const cO = colliderCenter(o);
|
|
const dir = (cM.x - cO.x) * mtv.axis.x + (cM.y - cO.y) * mtv.axis.y;
|
|
const sign = dir >= 0 ? 1 : -1;
|
|
r = clampRectToRoom(
|
|
{ ...r, x: r.x + sign * mtv.axis.x * mtv.depth, y: r.y + sign * mtv.axis.y * mtv.depth },
|
|
room,
|
|
minSize,
|
|
);
|
|
pushed = true;
|
|
}
|
|
if (!pushed) break;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
export function findFreeSpot(room: RoomConfig, size: { width: number; height: number }, others: Collider[], tries = 40): Collider {
|
|
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 entranceCollider(state: LayoutState): Collider | 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,
|
|
};
|
|
}
|
|
|
|
export function furnitureCollider(f: { shape: { x: number; y: number; width: number; height: number; rotation?: number } }): Collider {
|
|
return { x: f.shape.x, y: f.shape.y, width: f.shape.width, height: f.shape.height, rotation: f.shape.rotation };
|
|
}
|