Initial commit: browser OSM city generator

This commit is contained in:
mrleo1nid committed 2026-10-30 18:10:47 +03:00
commit 2d4bad0dc7
32 files changed
+5915

No files matched your search

+197
View File
@@ -0,0 +1,197 @@
// Модель мира. Все координаты в метрах в локальной проекции мира:
// x — на восток, y — на юг (как на экране). Углы в радианах.
// Модель с самого начала рассчитана на будущую симуляцию: у всего есть id,
// двери и лестницы — это связи между комнатами (граф навигации),
// у объектов есть флаги «блокирует проход / блокирует обзор».
export type Vec = [number, number];
export type Ring = Vec[];
export interface World {
version: 1;
id: string;
name: string;
createdAt: string;
seed: number;
/** Центр проекции и исходная область в градусах [south, west, north, east]. */
origin: { lat: number; lon: number };
bbox: [number, number, number, number];
/** Границы области в метрах. */
bounds: { minX: number; minY: number; maxX: number; maxY: number };
areas: Area[];
roads: Road[];
buildings: Building[];
/** Уличные объекты: деревья, машины, скамейки, фонари. */
props: Prop[];
/** Библиотека общих моделей предметов: prop.model → набор фигур. */
models: Record<string, Shape[]>;
attribution: string;
}
export type AreaKind =
| 'residential' | 'commercial' | 'industrial' | 'retail' | 'farmland' | 'farmyard'
| 'grass' | 'park' | 'forest' | 'scrub' | 'meadow' | 'orchard' | 'vineyard'
| 'water' | 'wetland' | 'beach' | 'sand' | 'parking' | 'pitch' | 'playground'
| 'cemetery' | 'construction' | 'railway' | 'school' | 'hospital' | 'pedestrian' | 'garden';
export interface Area {
id: string;
kind: AreaKind;
/** Первое кольцо — внешнее, остальные — дыры. */
rings: Ring[];
osmId?: string;
name?: string;
}
export type RoadKind =
| 'motorway' | 'trunk' | 'primary' | 'secondary' | 'tertiary' | 'residential'
| 'unclassified' | 'service' | 'living_street' | 'pedestrian' | 'track'
| 'footway' | 'cycleway' | 'path' | 'steps' | 'rail' | 'tram' | 'river' | 'stream' | 'canal' | 'ditch';
export interface Road {
id: string;
kind: RoadKind;
pts: Vec[];
/** Ширина проезжей части (или русла) в метрах. */
width: number;
/** Ширина тротуара с каждой стороны, 0 — нет. */
sidewalk: number;
lanes: number;
oneway: boolean;
layer: number;
bridge: boolean;
tunnel: boolean;
name?: string;
osmId?: string;
}
export type BuildingKind =
| 'house' | 'apartments' | 'mixed' | 'retail' | 'restaurant' | 'office' | 'industrial'
| 'warehouse' | 'school' | 'hospital' | 'hotel' | 'church' | 'civic' | 'garage' | 'shed' | 'farm';
export interface Building {
id: string;
osmId?: string;
kind: BuildingKind;
/** Подтип из OSM-тегов/POI: 'supermarket', 'bakery', 'pharmacy'… */
use?: string;
name?: string;
address?: string;
footprint: Ring;
/** Внутренние дворы (дыры в контуре). */
holes: Ring[];
/** Площадь контура, м². */
area: number;
levels: number;
/** Откуда взялась этажность: 'osm' | 'height' | 'estimate'. */
levelsSource: 'osm' | 'height' | 'estimate';
/** Локальная система здания: поворот сетки планировки. */
frame: { origin: Vec; angle: number };
floors: Floor[];
/** Помещения-юниты: квартиры, магазины, офисы — будущие «домохозяйства» и «рабочие места». */
units: Unit[];
/** Сколько спальных мест (оценка жильцов). */
beds: number;
seed: number;
}
export interface Unit {
id: string;
kind: 'home' | 'shop' | 'office' | 'service' | 'common';
level: number;
roomIds: string[];
beds: number;
}
export type RoomKind =
| 'hall' | 'landing' | 'living' | 'kitchen' | 'bedroom' | 'bath' | 'wc' | 'storage' | 'corridor'
| 'stairs' | 'lift' | 'lobby' | 'sales' | 'backroom' | 'office' | 'openoffice' | 'meeting'
| 'dining' | 'prokitchen' | 'workshop' | 'hall_ind' | 'classroom' | 'staff' | 'ward'
| 'guestroom' | 'nave' | 'garage' | 'utility' | 'void';
export interface Room {
id: string;
kind: RoomKind;
/** Контур комнаты (может быть несколько колец; дыры — по чётности). Рисовать с клипом по контуру здания. */
rings: Ring[];
area: number;
labelPos: Vec;
unitId?: string;
}
export interface Wall {
a: Vec;
b: Vec;
thickness: number;
exterior: boolean;
}
export interface Door {
id: string;
a: Vec;
b: Vec;
/** Комнаты по обе стороны; 'outside' — улица. Это ребро графа навигации. */
rooms: [string, string];
/** 'opening' — проём без полотна (арка, выход на лестницу). */
kind: 'interior' | 'entrance' | 'unit' | 'garage' | 'opening';
/** Сторона, куда открывается полотно (точка внутри комнаты). */
swingTo: Vec;
}
export interface Window {
a: Vec;
b: Vec;
roomId: string;
}
export interface Stairs {
id: string;
rings: Ring[];
/** Ось марша: от низа к верху. */
from: Vec;
to: Vec;
width: number;
up: boolean;
down: boolean;
lift?: boolean;
/** id комнаты-лестничной клетки на этом этаже. */
roomId: string;
}
export interface Floor {
level: number;
rooms: Room[];
walls: Wall[];
doors: Door[];
windows: Window[];
stairs: Stairs[];
props: Prop[];
}
/** Примитив в локальных координатах предмета (центр предмета в 0,0; «спинка» к стене при y<0). */
export type Shape =
| { t: 'rect'; x: number; y: number; w: number; h: number; r?: number; s?: ShapeStyle }
| { t: 'circle'; x: number; y: number; r: number; s?: ShapeStyle }
| { t: 'line'; x1: number; y1: number; x2: number; y2: number; s?: ShapeStyle };
/** Стиль: 'base' — основной предмет, 'soft' — ткань/подушка, 'dark' — техника/акцент, 'green' — растение, 'glass', 'metal'. */
export type ShapeStyle = 'base' | 'soft' | 'dark' | 'green' | 'glass' | 'metal' | 'wood' | 'water' | 'line' | 'car' | 'fabric' | 'accent';
export interface Prop {
id: string;
kind: string;
pos: Vec;
rot: number;
w: number;
d: number;
/** Фигуры предмета; после сборки мира обычно вынесены в world.models (см. model). */
shapes?: Shape[];
model?: string;
blocksMove: boolean;
blocksSight: boolean;
roomId?: string;
/** Цвет для машин и т.п. (иначе по стилю). */
color?: string;
/** Для симуляции: можно сидеть/спать/хранить и т.п. */
tags?: string[];
}