/** * The seed-driven procedural CITY GENERATOR — plot-first composition over the street and block * engines. {@link generateStreets} grows the street network (with a seeded organic outline, so no * two seeds share a silhouette), {@link extractBlocks} turns it into closed block polygons, and the * plot pass subdivides every buildable block's street frontage into PLOTS of many sizes — narrow * rowhouse slivers up to block-scale grand parcels. Everything that stands in the city stands on a * plot, and every plot fronts a street by construction: there is no landmark special case (a * landmark is just a grand plot) and no street-less interior fill. One call, one seed, one * deterministic city. Local (volume-centered) coords; the caller translates into world space. * * @capability city-generator compose a deterministic city — street network, blocks, street-fronting plots of varied size — from one seed */ import type { PlacedBuildingLot } from "./buildingLots.js"; import { type CityFillerClass, type CityLandmarkClass, type CityLotClass, type CityLotPiece, type CityZoneBand, type CityZoneProfile } from "./cityContent.js"; import { type Vec2 } from "./cityGeometry.js"; import type { WeightedParamEntry } from "../scene/sceneKinds.js"; import { type StreetLevel, type StreetNetwork, type StreetNetworkContext, type StreetNetworkRules } from "./streetGenerator.js"; /** Options for {@link generateCity}: a seed, street-dial overrides, and plot pass-through options. */ export interface CityGeneratorOptions { /** Drives the street network and all downstream plot/class/massing variation. */ seed: string; /** Street-dial overrides; anything omitted takes the city defaults above (including `outline`). */ streets?: Partial>; /** Plot-pass options (sizes scale off `footprint`, gaps off `spacing`, fronts off `setback`). */ lots?: { /** Base plot-size hint: tier frontage/depth ranges scale by `w/12` and `d/10`. Default 12×10. */ footprint?: { w: number; d: number; }; /** Base gap between neighbouring plots along a frontage, world units. Default 2. */ spacing?: number; /** Mix between uniform medium plots (0) and the full zone/street tier distribution (1). Default 1. */ variety?: number; /** Sidewalk strip between the curb and the building front, world units. Default 3. */ setback?: number; /** Manhattan streetwall dial (0..1): frontage coverage and plot depth grow with it. Default {@link DEFAULT_BLOCK_FILL}. */ blockFill?: number; /** Also line `lane`-level streets (alleys) with plots. Default false. */ laneFrontage?: boolean; /** Hard cap on emitted plots so a big network stays bounded. Default 600. */ maxLots?: number; }; /** Ground sampler + bridge/tunnel toggles forwarded to the street generator. */ context?: StreetNetworkContext; /** * Also resolve each plot into zone/class/floors/massing. Pass `true` for city defaults, or an * overrides object to tune bands/mixes/floors. Omit (default) to keep the cheap geometry-only * path — `lotContent` stays absent. See {@link resolveCityLotContent}. */ content?: boolean | CityContentOverrides; } /** Size tier a plot was cut at. `grand` is the block-scale tier that used to be a landmark pass. */ export type CityPlotTier = "small" | "medium" | "large" | "grand"; /** All plot tiers, for schema hints and validation. */ export declare const CITY_PLOT_TIERS: readonly CityPlotTier[]; /** The street frontage a plot faces: which street, and the frontage chord along it. */ export interface CityPlotFrontage { /** Index into `network.streets`. */ street: number; /** Frontage chord endpoints on the plot boundary. */ a: Vec2; b: Vec2; } /** One polygonal plot cut from a block's street frontage. Every plot fronts a street. */ export interface CityPlot { /** Plot polygon (CCW) in local XZ. */ polygon: Vec2[]; /** Building-rect center inside the plot (matches the paired {@link PlacedBuildingLot}). */ center: Vec2; /** Yaw turning the building front toward its street. */ rotationY: number; /** Building-rect frontage width / depth fitted inside the plot polygon. */ width: number; depth: number; frontage: CityPlotFrontage; /** Hierarchy level of the fronted street. */ streetLevel: StreetLevel; /** Index of the block the plot was cut from; `-1` for plots lining a cul-de-sac/branch corridor. */ block: number; tier: CityPlotTier; /** True when the plot wraps a block corner (two frontages meet inside it). */ corner: boolean; } /** A generated city: streets, plots (with rect building lots as a compat view), and park blocks. */ export interface GeneratedCity { network: StreetNetwork; /** Rect building-lot view of `plots` (same order, same indices) — the renderer-compat seam. */ lots: PlacedBuildingLot[]; /** The polygonal plots the city is made of. `plots[i]` pairs with `lots[i]`. */ plots: CityPlot[]; /** Land polygons of whole blocks reserved as parks/open space (no plots cut). */ parks: Vec2[][]; /** * Present only when {@link CityGeneratorOptions.content} was requested: every plot enriched with * its zone band, building class, floors, and lot-local massing pieces. Same order as `lots`. */ lotContent?: readonly ResolvedCityLot[]; } /** * Grow a street network inside the `hx`/`hz` half-extents, extract its blocks, and subdivide their * frontage into plots. Deterministic: identical options ⇒ identical city. */ export declare function generateCity(options: CityGeneratorOptions, hx: number, hz: number): GeneratedCity; /** Default `blockFill`: gently gapped frontage — plots with breathing room, parks reserved. */ export declare const DEFAULT_BLOCK_FILL = 0.45; /** Default grand-plot share dial — a handful of block-scale parcels per default city. */ export declare const DEFAULT_LANDMARK_SHARE = 0.04; /** Hard cap on grand plots emitted regardless of dial/city size. */ export declare const LANDMARK_HARD_CAP = 12; /** Options for {@link deriveCityPlots}. */ export interface CityPlotOptions { seed: string; /** Half-extents the city was grown in — zone tiers normalize to these. */ halfExtents: readonly [number, number]; /** Cut plots along `lane`-level frontage too. Default false. */ laneFrontage?: boolean; /** Sidewalk strip between curb and building front, meters. Default 3. */ setback?: number; /** Base gap between neighbouring plots, meters. Default 2. */ spacing?: number; /** Mix between uniform medium plots (0) and the full zone/street tier distribution (1). Default 1. */ variety?: number; /** Streetwall dial (0..1): frontage coverage, plot depth, and park share ride on it. Default {@link DEFAULT_BLOCK_FILL}. */ blockFill?: number; /** Grand-plot share dial (0..1): probability weight of the block-scale tier. Default {@link DEFAULT_LANDMARK_SHARE}; `0` disables grand plots. */ landmarks?: number; /** Tier size multiplier `[wScale, dScale]`. Default `[1, 1]`. */ plotScale?: readonly [number, number]; /** Hard cap on emitted plots. Default 600. */ maxPlots?: number; /** Zone profile the tier weights read. Defaults match {@link resolveCityLotContent}. */ profile?: CityZoneProfile; coreExtent?: number; midExtent?: number; } /** Output of {@link deriveCityPlots}: the plots plus whole-block park polygons. */ export interface CityPlotResult { plots: CityPlot[]; parks: Vec2[][]; } /** * Subdivide every buildable block's street frontage into size-tiered polygonal plots. Blocks come * from the street network's planar faces ({@link extractBlocks}), so every plot fronts a real * street; block interiors stay open (courtyards) and a seeded fraction of whole blocks becomes * parks. Grand plots — the old "landmarks" — are just the biggest tier of the same pass. * Deterministic per seed, bounded by `maxPlots` and per-block caps. * * @capability city-generator subdivide street blocks into size-tiered street-fronting plots */ export declare function deriveCityPlots(network: StreetNetwork, options: CityPlotOptions): CityPlotResult; /** Weighted building-class mix per zone band; the radial profile decides which band a lot falls in. */ export interface CityZoneMixes { core: readonly WeightedParamEntry[]; mid: readonly WeightedParamEntry[]; edge: readonly WeightedParamEntry[]; } /** Default zoned-metropolis mixes: towers/slabs downtown, slabs+rowhouses mid, houses at the edge. */ export declare const DEFAULT_CITY_ZONE_MIXES: CityZoneMixes; /** * Per-street-level class weight multipliers applied to the band mix before the class pick, so towers * and slabs bias toward wide boulevard/avenue frontage while houses bias toward lanes and quiet * streets. A missing class ⇒ multiplier 1. Pure data modulating the weighted mixes. */ export type CityLevelClassBias = Record>>; /** Default street-level bias — boulevards favor big massing, lanes favor small/rural massing. */ export declare const DEFAULT_CITY_LEVEL_BIAS: CityLevelClassBias; /** Tunable overrides for {@link resolveCityLotContent} (seed/halfExtents/laneFrontage are supplied). */ export interface CityContentOverrides { /** Radial band profile: `core-out` (default), `inverted`, or spatially `uniform`. */ profile?: CityZoneProfile; /** Fraction of the half-extent the core band covers. Default 0.35. */ coreExtent?: number; /** Fraction of the half-extent where the mid band ends and the edge begins. Default 0.7. */ midExtent?: number; /** Weighted class mix per band; any omitted band takes {@link DEFAULT_CITY_ZONE_MIXES}. */ mixes?: Partial; /** Global massing-size multiplier forwarded to the class placement roll. Default 1. */ lotScale?: number; /** Minimum building floors (district clamp over class ranges). Default 1. */ floorsMin?: number; /** Maximum building floors. Default 30. */ floorsMax?: number; /** Height of one floor in meters. Default 3. */ floorHeight?: number; /** Base road setback in meters (class factors scale it). Default 2.5. */ setback?: number; /** Base side spacing in meters between neighbours. Default 1.4. */ spacing?: number; /** * Bias the band mix by the plot's frontage street level before the class pick. `true` (default) * uses {@link DEFAULT_CITY_LEVEL_BIAS}; pass a table to customise; `false` disables the bias. */ streetLevelBias?: boolean | CityLevelClassBias; /** * Grand-plot share dial (0..1): the probability weight of the block-scale plot tier at geometry * time — a landmark is nothing but a grand plot. Default {@link DEFAULT_LANDMARK_SHARE}; `0` * yields a city with no grand plots. Count is hard-capped at {@link LANDMARK_HARD_CAP}. */ landmarks?: number; /** * Restrict grand plots to these classes (a subset of {@link CITY_LANDMARK_CLASSES}). Omit to * allow every class the zone table would pick. */ landmarkClasses?: readonly CityLandmarkClass[]; /** * Streetwall dial (0..1). Default {@link DEFAULT_BLOCK_FILL}. As it rises frontage coverage * approaches a contiguous wall, plots deepen toward the block spine, and fewer whole blocks are * reserved as parks; falling it loosens the frontage and greens the city. Block interiors are * never filled with street-less buildings — every plot fronts a street. */ blockFill?: number; } /** Full options for {@link resolveCityLotContent}: the city geometry frame plus content overrides. */ export interface CityContentOptions extends CityContentOverrides { /** Seed for the deterministic class/floor/massing rolls. Reuse the city seed for a coherent city. */ seed: string; /** Half-extents the city was grown in — the radial zone metric normalizes to these. */ halfExtents: readonly [number, number]; /** Whether lanes were lined with plots (mirror the geometry option). Default false. */ laneFrontage?: boolean; } /** Axis-aligned massing extents (lot-local): full width along x and depth along z. */ export interface MassingFootprint { /** Full width of the massing bounding box along local x (frontage). */ w: number; /** Full depth of the massing bounding box along local z (into the block). */ d: number; /** Bounding-box corners in lot-local space, so a renderer can ground/cull the parcel. */ bounds: { minX: number; maxX: number; minZ: number; maxZ: number; }; } /** One plot enriched with its zone/class/floors/massing — the renderer instances `pieces` at `center`. */ export interface ResolvedCityLot { /** The paired rect building lot (`city.lots[i]`). */ lot: PlacedBuildingLot; /** Zone band the plot center fell in under the profile. */ zone: CityZoneBand; /** * Building class. Ordinary plots roll a {@link CityLotClass} from the band mix; grand plots roll * a {@link CityLandmarkClass}. ({@link CityFillerClass} remains in the union for renderer compat.) */ class: CityLotClass | CityLandmarkClass | CityFillerClass; /** * Set only on grand plots; equals {@link ResolvedCityLot.class} there and is `undefined` for * every ordinary building. Renderers branch on this. */ landmark?: CityLandmarkClass; /** Hierarchy level of the frontage street (`street` when it can't be recovered). */ streetLevel: StreetLevel; /** Building floor count after the district clamp. */ floors: number; /** Deterministic massing pieces in lot-local space (x along frontage, z into block, y up from grade). */ pieces: readonly CityLotPiece[]; /** Lot center in world XZ (mirrors `lot.center`) — the anchor the renderer offsets pieces from. */ center: Vec2; /** Building yaw so its front faces the road (mirrors `lot.rotationY`). */ rotationY: number; /** Actual massing extents in lot-local space (from `pieces`), for grounding and culling. */ footprint: MassingFootprint; } /** * Enrich a generated city's plots into classed, massed buildings: each plot gets its zone band * (radial position under a {@link CityZoneProfile}), a building class rolled from the band's * weighted mix — biased by street level AND plot size, with grand plots rolling a landmark class — * floors, and the deterministic massing pieces the class composes, sized to the plot's building * rect. Deterministic per `seed` via {@link seededStreams}, bounded (one pass over `city.lots`), * and pure. Same seed + city ⇒ identical resolved lots. * * A city without `plots` (a hand-built `{network, lots}` pair) still resolves: every lot is treated * as an ordinary plot of its footprint size. * * @capability city-generator resolve city plots into zoned, classed, massed buildings */ export declare function resolveCityLotContent(city: Pick & Partial>, options: CityContentOptions): ResolvedCityLot[];