From e7500c8ef9abfb8c49b5e570e7453a3f8a9d2e09 Mon Sep 17 00:00:00 2001 From: mrleo1nid <43583385+mrleo1nid@users.noreply.github.com> Date: Fri, 30 Oct 2026 18:34:53 +0300 Subject: [PATCH] Remove generated cars and increase map area limits --- README.md | 6 ++++-- src/gen/catalog.ts | 7 ------- src/gen/city.ts | 35 +++-------------------------------- src/gen/furnish.ts | 2 -- src/gen/props.ts | 11 ----------- src/main.ts | 6 +++--- src/view/renderer.ts | 2 ++ 7 files changed, 12 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 4ecc287..cd576fa 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Выделяете на карте небольшой город или район → по данным OSM строится подробная карта с видом строго сверху: дороги реальной ширины с тротуарами и разметкой, парковки -с машинами, деревья, и **у каждого здания — этажи, комнаты, двери, окна, лестницы +без машин, деревья, и **у каждого здания — этажи, комнаты, двери, окна, лестницы и мебель**, нарисованные простыми геометрическими фигурами, как на плане квартиры. Всё работает в браузере, без сервера: данные берутся из Overpass API, генерация @@ -88,7 +88,9 @@ Overpass JSON ─► city.ts ──────────────► зо ## Ограничения MVP -- Область до 6 км². Публичные серверы Overpass рассчитаны на умеренную нагрузку; для +- Область до 25 км²; предупреждение о времени генерации — свыше 10 км². + Скорость и расход памяти зависят от плотности застройки и этажности. + Публичные серверы Overpass рассчитаны на умеренную нагрузку; для публичной игры понадобится своё зеркало или заранее нарезанные данные (например, экстракт Geofabrik → статические файлы). - Подвалы, мансарды, балконы, береговая линия морей пока не генерируются. diff --git a/src/gen/catalog.ts b/src/gen/catalog.ts index aa9754d..09a459d 100644 --- a/src/gen/catalog.ts +++ b/src/gen/catalog.ts @@ -186,13 +186,6 @@ 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 b2dd7f4..70e9217 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 { carShapes, CAR_COLORS, treeProp } from './props'; +import { treeProp } from './props'; export interface InteriorJob { building: Building; @@ -332,9 +332,7 @@ 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 м, ... + // полоса: ряд мест 5 м, проезд 6 м, ряд 5 м, ... let y = lb[1] + 0.5; let row = 0; while (y + 5 <= lb[3]) { @@ -343,40 +341,13 @@ 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++; } } - // Машины вдоль жилых улиц (параллельная парковка) - for (const r of roads) { - if (!['residential', 'tertiary', 'living_street', 'unclassified'].includes(r.kind) || r.tunnel || r.bridge) continue; - const rr = new Rng(hashString(r.id) ^ seed); - if (!rr.chance(0.6)) continue; - for (let i = 1; i < r.pts.length; i++) { - const a = r.pts[i - 1], b = r.pts[i]; - const L = dist(a, b); - if (L < 12) continue; - const dir: Vec = [(b[0] - a[0]) / L, (b[1] - a[1]) / L]; - const nrm: Vec = [-dir[1], dir[0]]; - for (const side of [1, -1]) { - for (let s = 6; s < L - 6; s += 6) { - if (!rr.chance(0.28)) continue; - const off = r.width / 2 - 1.1; - const p: Vec = [a[0] + dir[0] * s + nrm[0] * off * side, a[1] + dir[1] * s + nrm[1] * off * side]; - if (!inBounds(p) || blockedByBuilding(p, 1.5)) continue; - props.push({ id: `cs${r.id}_${props.length}`, kind: 'car', pos: p, rot: Math.atan2(dir[1], dir[0]) + (side > 0 ? Math.PI / 2 : -Math.PI / 2) + rr.range(-0.03, 0.03), w: 1.8, d: 4.4, shapes: carShapes(), blocksMove: true, blocksSight: false, color: rr.pick(CAR_COLORS), tags: ['vehicle'] }); - } - } - } - } - // ---- Задания на интерьеры: здание + ближайшие дороги const jobs: InteriorJob[] = buildings.map((b) => { const bb = bboxOf(b.footprint); diff --git a/src/gen/furnish.ts b/src/gen/furnish.ts index 088dbf7..b8125f3 100644 --- a/src/gen/furnish.ts +++ b/src/gen/furnish.ts @@ -9,7 +9,6 @@ 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; @@ -463,7 +462,6 @@ 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 d1d0737..3b9184a 100644 --- a/src/gen/props.ts +++ b/src/gen/props.ts @@ -1,17 +1,6 @@ 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/main.ts b/src/main.ts index 849f9d5..9461313 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,8 +5,8 @@ import { generateWorld } from './gen/pipeline'; import { downloadWorld, hasWorld, loadWorld, readJsonFile, saveWorld } from './store'; import type { World } from './types'; -const MAX_KM2 = 6; -const WARN_KM2 = 2.5; +const MAX_KM2 = 25; +const WARN_KM2 = 10; const app = document.getElementById('app')!; let viewer: Viewer | null = null; @@ -98,7 +98,7 @@ async function showPicker(initial?: BBox) { return; } const s = bboxSizeKm(b); - info.textContent = `${s.w.toFixed(2)} × ${s.h.toFixed(2)} км · ${s.km2.toFixed(2)} км²` + (s.km2 > MAX_KM2 ? ' — слишком большая область' : s.km2 > WARN_KM2 ? ' — большая область, генерация займёт время' : ''); + info.textContent = `${s.w.toFixed(2)} × ${s.h.toFixed(2)} км · ${s.km2.toFixed(2)} км²` + (s.km2 > MAX_KM2 ? ` — лимит ${MAX_KM2} км², уменьшите выделение` : s.km2 > WARN_KM2 ? ' — генерация плотной застройки может занять больше времени' : ''); info.className = 'sel-info' + (s.km2 > MAX_KM2 ? ' bad' : s.km2 > WARN_KM2 ? ' warn' : ' ok'); goBtn.disabled = s.km2 > MAX_KM2; }); diff --git a/src/view/renderer.ts b/src/view/renderer.ts index c7caa3d..001d1cb 100644 --- a/src/view/renderer.ts +++ b/src/view/renderer.ts @@ -148,6 +148,8 @@ export class Renderer { private propLayers(props: Prop[]): Layer[] { const map = new Map(); for (const p of props) { + // Старые сохранённые миры также показываем без машин. + if (p.kind === 'car') 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];