/** * Street-aware building placement: derive building LOTS along ROAD FRONTAGE. Given road segments * (centerline + width) and a district area, walk each road by arc length and emit lots on both * sides at a consistent setback — a curb + sidewalk strip — with each lot carrying the yaw that * makes the building FRONT face its road. Unlike `cityBlocks` (which needs a CLOSED street network * to extract planar block faces), this works on a sparse set of segments — the common authoring * case — so buildings line the streets instead of scattering on plinths around a center point. * * Pure seeded math over `cityGeometry` `Vec2` primitives: no rendering, deterministic from the * inputs, bounded by `maxLots`. * * @capability world-environment lots aligned to and facing road frontage, with a setback sidewalk strip */ import { type Vec2 } from "./cityGeometry.js"; import type { WorldBounds } from "./features.js"; /** One road the frontage placer lines with buildings: a centerline polyline and its full width. */ export interface RoadFrontage { /** Road centerline vertices in world XZ; at least two points. */ path: readonly Vec2[]; /** Road full width in world units; lots set back from `width / 2`. */ width: number; } /** Rectangular clip region lots must fall within (world center + half-extents). */ export interface LotArea { /** World-space center of the region; default `[0, 0]`. */ center?: Vec2; /** Half-extents along world x and z. */ halfExtents: Vec2; } /** One weighted plot size a frontage can mix in: `w` along the road, `d` into the block. */ export interface PlotVariant extends WorldBounds { /** Relative pick weight among the variants. Default 1. */ weight?: number; } /** Options for {@link deriveBuildingLots}. */ export interface BuildingLotOptions { /** Roads whose frontage is lined with lots. */ roads: readonly RoadFrontage[]; /** * Plot size(s): `w` is frontage width (along the road), `d` is depth (into the block). Pass one * {@link WorldBounds} for a uniform district, or a weighted {@link PlotVariant} list to mix plot * sizes along the same frontage — narrow-deep apartment slices beside wide detached-house * parcels beside tight terraces. Variant rolls are seeded per road AND side, so the two sides of * a street can read differently. Default `{ w: 12, d: 10 }`. */ footprint?: WorldBounds | readonly PlotVariant[]; /** Gap between neighbouring lots along a frontage, world units. Default 2. */ spacing?: number; /** Sidewalk strip between the curb (`width / 2`) and the building front, world units. Default 3. */ setback?: number; /** * Manhattan streetwall dial (0..1). Omit (default) keeps the legacy look byte-for-byte. When set, * frontage is compacted as it rises: the along-road gap between neighbours scales from `spacing` at * `~0.45` down to 0 at 1 (buildings shoulder-to-shoulder), and each lot's frontage width widens by * a modest factor above 0.45 so consecutive lots touch instead of leaving gaps. Below ~0.45 the gap * grows past `spacing`, so a low dial reads sparser than the default. Depth is unchanged. This is * pure frontage compaction; block-interior fill lives in the content pass (`resolveCityLotContent`). */ blockFill?: number; /** Place lots on both sides of each road. Default true. */ bothSides?: boolean; /** Clip region; lots whose center falls outside are dropped. Omit to keep every lot. */ area?: LotArea; /** * Extra road corridors lots must keep clear of WITHOUT lining them with frontage — service * alleys, lanes, trails. Every lot's full footprint is kept off these (and off `roads`). */ avoid?: readonly RoadFrontage[]; /** * Seed for the plot-variant rolls when `footprint` is a variant list (identical inputs always * yield the identical layout, with or without it). With a single footprint, lot geometry is * fully determined by the roads and dials; per-building variation (floors, massing) is seeded * downstream where the lots become buildings. */ seed?: string; /** Hard cap on emitted lots so a long network stays bounded. Default 400. */ maxLots?: number; } /** One placed building lot: where a building stands and how it is turned to face its road. */ export interface PlacedBuildingLot { /** Lot (building footprint) center in world XZ. */ center: Vec2; /** Building yaw (radians) so its FRONT (+z local face) points at the road. */ rotationY: number; /** Footprint of the lot: `w` along the frontage, `d` into the block. */ footprint: WorldBounds; /** Index into the input `roads`. */ road: number; /** Which side of the road: +1 = left normal, -1 = right normal. */ side: 1 | -1; /** Distance from the lot's front face to the road centerline (= `width / 2 + setback`). */ frontDistance: number; } /** * Frontage-fill reference point: at this dial value the compaction is a no-op (legacy spacing/width), * so callers can leave `blockFill` unset for the classic look and this value reproduces it exactly. */ export declare const FRONTAGE_FILL_REFERENCE = 0.45; /** * Map a `blockFill` dial to frontage compaction factors. `undefined` ⇒ identity (legacy path stays * byte-identical). At {@link FRONTAGE_FILL_REFERENCE} the factors are exactly `{1, 1}`; toward 1 the * gap collapses and lots widen, toward 0 the gap grows (sparser than default). @internal */ export declare function frontageCompaction(blockFill: number | undefined): { spacingScale: number; widthScale: number; }; /** * Yaw that turns the building FRONT (local +z) to face the road. The lot center sits at * `roadPoint + outwardNormal * d`, so the road lies along `-outwardNormal` from the center. In the * engine's Y-rotation convention a local +z of `(0, 1)` maps to world `(sin y, cos y)`; setting * that equal to `-outwardNormal` gives `y = atan2(-nx, -nz)`. * @internal */ export declare function facingRotation(outwardNormal: Vec2): number; /** * Derive street-aligned building lots (PLOTS) from road frontage. Each side of each road walks the * frontage placing plots at `plot.w + spacing` pitch — rolling the next plot size from the weighted * `footprint` variants when several are given — offset outward by `width / 2 + setback + plot.d / 2` * and turned to face the road. The PLOT CONTRACT is enforced here, exactly: no two plots ever overlap * (true oriented-rect separation with the spacing gap, so corners and curved frontage can't stack * buildings), and no plot footprint ever touches any road corridor in `roads` or `avoid` — not * just its own frontage road. Deterministic: identical inputs (including seed) always yield the * identical list. * @capability building-lots derive street-facing building lots from road frontage */ export declare function deriveBuildingLots(options: BuildingLotOptions): PlacedBuildingLot[];