Files
citygen/src/gen/finalize.ts
T

80 lines
3.0 KiB
TypeScript

// Финальная упаковка мира: общие модели предметов и округление координат до сантиметра.
import type { Building, Prop, Shape, World } from '../types';
import { attachInteractions } from './objects';
const r2 = (v: number) => Math.round(v * 100) / 100;
const r3 = (v: number) => Math.round(v * 1000) / 1000;
export class ModelLibrary {
private index = new Map<string, string>();
/** Модели, добавленные с последнего takeNew() — чтобы воркер отдавал только новые. */
private fresh: Record<string, Shape[]> = {};
constructor(public models: Record<string, Shape[]>, private prefix = '') {
for (const [id, shapes] of Object.entries(models)) this.index.set(JSON.stringify(shapes), id);
}
intern(p: Prop) {
if (!p.shapes) return;
const shapes = p.shapes.map(roundShape);
const key = JSON.stringify(shapes);
let id = this.index.get(key);
if (!id) {
id = `${p.kind}#${this.prefix}${this.index.size}`;
this.index.set(key, id);
this.models[id] = shapes;
this.fresh[id] = shapes;
}
p.model = id;
delete p.shapes;
}
takeNew(): Record<string, Shape[]> {
const f = this.fresh;
this.fresh = {};
return f;
}
}
function roundShape(s: Shape): Shape {
if (s.t === 'line') return { ...s, x1: r2(s.x1), y1: r2(s.y1), x2: r2(s.x2), y2: r2(s.y2) };
if (s.t === 'circle') return { ...s, x: r2(s.x), y: r2(s.y), r: r2(s.r) };
return { ...s, x: r2(s.x), y: r2(s.y), w: r2(s.w), h: r2(s.h), ...(s.r !== undefined ? { r: r2(s.r) } : {}) };
}
function roundProp(p: Prop) {
p.pos = [r2(p.pos[0]), r2(p.pos[1])];
p.rot = r3(p.rot);
p.w = r2(p.w);
p.d = r2(p.d);
attachInteractions(p);
if (!p.tags?.length) delete p.tags;
if (!p.roomId) delete p.roomId;
if (!p.color) delete p.color;
}
const rv = (v: [number, number]): [number, number] => [r2(v[0]), r2(v[1])];
export function finalizeBuilding(b: Building, lib: ModelLibrary) {
b.footprint = b.footprint.map(rv);
b.holes = b.holes.map((h) => h.map(rv));
b.area = Math.round(b.area);
for (const f of b.floors) {
for (const r of f.rooms) {
r.rings = r.rings.map((rr) => rr.map(rv));
r.labelPos = rv(r.labelPos);
}
for (const w of f.walls) { w.a = rv(w.a); w.b = rv(w.b); }
for (const d of f.doors) { d.a = rv(d.a); d.b = rv(d.b); d.swingTo = rv(d.swingTo); }
for (const w of f.windows) { w.a = rv(w.a); w.b = rv(w.b); }
for (const s of f.stairs) { s.rings = s.rings.map((rr) => rr.map(rv)); s.from = rv(s.from); s.to = rv(s.to); }
for (const p of f.props) { roundProp(p); lib.intern(p); }
}
}
export function finalizeWorld(w: World, buildings = true) {
const lib = new ModelLibrary(w.models);
for (const a of w.areas) a.rings = a.rings.map((r) => r.map(rv));
for (const r of w.roads) r.pts = r.pts.map(rv);
for (const p of w.props) { roundProp(p); lib.intern(p); }
if (buildings) for (const b of w.buildings) finalizeBuilding(b, lib);
}