From 3d4b29fedf2d2bc26953f4f7f28ef5a3f2159d0e Mon Sep 17 00:00:00 2001 From: mrleo1nid <43583385+mrleo1nid@users.noreply.github.com> Date: Fri, 30 Oct 2026 18:37:59 +0300 Subject: [PATCH] Restore cars in parking lots and garages only --- README.md | 4 +++- src/gen/catalog.ts | 7 +++++++ src/gen/city.ts | 10 ++++++++-- src/gen/furnish.ts | 2 ++ src/gen/props.ts | 11 +++++++++++ src/view/renderer.ts | 4 ++-- 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index cd576fa..e6e3f5c 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,14 @@ Выделяете на карте небольшой город или район → по данным OSM строится подробная карта с видом строго сверху: дороги реальной ширины с тротуарами и разметкой, парковки -без машин, деревья, и **у каждого здания — этажи, комнаты, двери, окна, лестницы +с машинами, деревья, и **у каждого здания — этажи, комнаты, двери, окна, лестницы и мебель**, нарисованные простыми геометрическими фигурами, как на плане квартиры. Всё работает в браузере, без сервера: данные берутся из Overpass API, генерация идёт в Web Worker'ах, готовый мир сохраняется в IndexedDB. +Машины появляются только на парковках и в гаражах; вдоль улиц они не генерируются. + ## Запуск Нужен Node.js 18+. diff --git a/src/gen/catalog.ts b/src/gen/catalog.ts index 09a459d..aa9754d 100644 --- a/src/gen/catalog.ts +++ b/src/gen/catalog.ts @@ -186,6 +186,13 @@ export function meetingTable(L: number): Def { export const filing = (): Def => ({ kind: 'filing_cabinet', w: 0.5, d: 0.6, tall: true, front: 0.6, shapes: [R(0, 0, 0.5, 0.6, 'metal', 0.02), Ln(-0.2, 0.0, 0.2, 0.0, 'dark')] }); export const mailboxes = (): Def => ({ kind: 'mailboxes', w: 1.2, d: 0.3, shapes: [R(0, 0, 1.2, 0.3, 'metal', 0.02), Ln(-0.3, -0.14, -0.3, 0.14, 'dark'), Ln(0, -0.14, 0, 0.14, 'dark'), Ln(0.3, -0.14, 0.3, 0.14, 'dark')] }); +export function carDef(): Def { + return { + kind: 'car', w: 1.8, d: 4.4, tags: ['vehicle'], + shapes: [R(0, 0, 1.8, 4.4, 'car', 0.45), R(0, -0.75, 1.5, 0.55, 'glass', 0.15), R(0, 1.25, 1.45, 0.4, 'glass', 0.12), R(0, 0.25, 1.5, 1.4, 'dark', 0.2)], + }; +} + export function shift(s: Shape, dx: number, dy: number): Shape { if (s.t === 'line') return { ...s, x1: s.x1 + dx, x2: s.x2 + dx, y1: s.y1 + dy, y2: s.y2 + dy }; return { ...s, x: s.x + dx, y: s.y + dy }; diff --git a/src/gen/city.ts b/src/gen/city.ts index 70e9217..753c47e 100644 --- a/src/gen/city.ts +++ b/src/gen/city.ts @@ -11,7 +11,7 @@ import { import { Rng, hashString } from '../util/rng'; import { SpatialGrid } from '../util/spatial'; import { classify, clampLevels, defaultLevels, parseLevels } from './classify'; -import { treeProp } from './props'; +import { carShapes, CAR_COLORS, treeProp } from './props'; export interface InteriorJob { building: Building; @@ -332,6 +332,8 @@ export function buildCity(elements: OsmElement[], bbox: BBox, name: string, seed const ang = minAreaRectAngle(outer); const local = outer.map((p) => rotate(p, -ang)); const lb = bboxOf(local); + const pr = new Rng(hashString(a.id) ^ seed); + const fill = pr.range(0.45, 0.85); // полоса: ряд мест 5 м, проезд 6 м, ряд 5 м, ... let y = lb[1] + 0.5; let row = 0; @@ -341,8 +343,12 @@ export function buildCity(elements: OsmElement[], bbox: BBox, name: string, seed if (!corners.every((q) => pointInRings(q, a.rings))) continue; const cc = rotate([x + 1.25, y + 2.5], ang); if (!inBounds(cc) || blockedByBuilding(cc, 2)) continue; - // Только разметка парковочных мест. + // Разметка и припаркованные машины; вдоль улиц машины не создаём. props.push({ id: `ps${a.id}_${props.length}`, kind: 'parking_spot', pos: cc, rot: ang, w: 2.5, d: 5, shapes: [{ t: 'line', x1: -1.25, y1: -2.5, x2: -1.25, y2: 2.5, s: 'line' }, { t: 'line', x1: 1.25, y1: -2.5, x2: 1.25, y2: 2.5, s: 'line' }], blocksMove: false, blocksSight: false }); + if (pr.chance(fill)) { + const flip = row % 2 === 1 ? Math.PI : 0; + props.push({ id: `c${a.id}_${props.length}`, kind: 'car', pos: cc, rot: ang + flip + pr.range(-0.04, 0.04), w: 1.8, d: 4.4, shapes: carShapes(), blocksMove: true, blocksSight: false, color: pr.pick(CAR_COLORS), tags: ['vehicle'] }); + } } y += row % 2 === 0 ? 5 + 6 : 5 + 0.0; row++; diff --git a/src/gen/furnish.ts b/src/gen/furnish.ts index b8125f3..088dbf7 100644 --- a/src/gen/furnish.ts +++ b/src/gen/furnish.ts @@ -9,6 +9,7 @@ import * as K from './catalog'; import type { Def } from './catalog'; import { Rng } from '../util/rng'; import { distToSegment } from '../util/geom'; +import { CAR_COLORS } from './props'; export interface PropL { prop: Prop; @@ -462,6 +463,7 @@ export function furnishFloor(ctx: Ctx): PropL[] { break; } case 'garage': { + if (a > 14 && shortSide(room) >= 2.6) inMiddle(room, K.carDef(), 0, undefined, rng.pick(CAR_COLORS)); atWall(room, K.workbench(1.6)); atWall(room, K.shelf(1.2)); break; diff --git a/src/gen/props.ts b/src/gen/props.ts index 3b9184a..d1d0737 100644 --- a/src/gen/props.ts +++ b/src/gen/props.ts @@ -1,6 +1,17 @@ import type { Prop, Shape, Vec } from '../types'; import { Rng } from '../util/rng'; +export const CAR_COLORS = ['#c94c4c', '#3d6fb6', '#e8e8e8', '#2b2b2b', '#8c9298', '#d9c27a', '#4f8a5b', '#b5b9bd', '#6b4a8c', '#e07b39']; + +export function carShapes(): Shape[] { + return [ + { t: 'rect', x: 0, y: 0, w: 1.8, h: 4.4, r: 0.45, s: 'car' }, + { t: 'rect', x: 0, y: -0.75, w: 1.5, h: 0.55, r: 0.15, s: 'glass' }, // лобовое + { t: 'rect', x: 0, y: 1.25, w: 1.45, h: 0.4, r: 0.12, s: 'glass' }, // заднее + { t: 'rect', x: 0, y: 0.25, w: 1.5, h: 1.4, r: 0.2, s: 'dark' }, // крыша + ]; +} + export function treeProp(id: string, p: Vec, rng: Rng, t: Record): Prop { const scrub = !!t.scrub; const r0 = scrub ? rng.range(0.8, 1.6) : t.orchard ? rng.range(1.6, 2.4) : rng.range(2.2, 4.2); diff --git a/src/view/renderer.ts b/src/view/renderer.ts index 001d1cb..3a7747e 100644 --- a/src/view/renderer.ts +++ b/src/view/renderer.ts @@ -148,8 +148,8 @@ export class Renderer { private propLayers(props: Prop[]): Layer[] { const map = new Map(); for (const p of props) { - // Старые сохранённые миры также показываем без машин. - if (p.kind === 'car') continue; + // В старых мирах скрываем только машины вдоль улиц (префикс cs). + if (p.kind === 'car' && !p.roomId && p.id.startsWith('cs')) continue; const shapes = this.shapesOf(p); const c = Math.cos(p.rot), s = Math.sin(p.rot); const T = (x: number, y: number): Vec => [p.pos[0] + x * c - y * s, p.pos[1] + x * s + y * c];