/** * Block-first city fabric for the `city` studio: turns the synthesized street network into a * planar graph, extracts its faces as CLOSED BLOCK POLYGONS (a curved street yields a curved * block edge, a diagonal one a diagonal edge), insets each block by its bordering roads' * half-widths into curb and land rings (the sidewalk band lives between them), classifies every * block (buildable, park, plaza, field, buffer sliver), subdivides buildable block frontage into * POLYGONAL PARCELS with street references and corner status, and computes per-parcel BUILDABLE * polygons after setbacks. Buildings then derive from parcel geometry instead of nearest-road * guesses. Pure seeded math over `cityGeometry` primitives — no rendering, bounded work. * * @capability city-district road-derived block polygons, parcel subdivision, buildable footprints */ import { type Vec2 } from "./cityGeometry.js"; /** How a block (a face of the road graph) is used. */ export type CityBlockKind = "buildable" | "park" | "plaza" | "field" | "buffer"; /** One closed block: a face of the road network, inset to its curb and land boundaries. */ export interface CityBlockLocal { id: string; /** Face polygon on road CENTERLINES (CCW) — the raw graph face. */ face: Vec2[]; /** Street index bordering each face edge (edge i = face[i]→face[i+1]); -1 = district boundary. */ faceStreets: number[]; /** Pavement edge: face inset by each bordering road's half width (+ curb margin). */ curb: Vec2[]; /** Land edge: curb inset further by the sidewalk band where the bordering street has one. */ polygon: Vec2[]; kind: CityBlockKind; area: number; } /** One street-frontage reference of a parcel. */ export interface CityParcelFrontage { /** Index into the resolved city's street list. */ street: number; /** Frontage chord endpoints on the parcel boundary. */ edgeStart: Vec2; edgeEnd: Vec2; /** Unit tangent along the frontage chord. */ tangent: Vec2; } /** How a parcel is used after occupancy/fitting rolls. */ export type CityParcelKind = "built" | "vacant" | "yard"; /** One polygonal parcel subdivided out of a block's street frontage. */ export interface CityParcelLocal { id: string; block: number; polygon: Vec2[]; /** Buildable polygon after front/side/rear setbacks; [] when setbacks consume the parcel. */ buildable: Vec2[]; frontage: CityParcelFrontage[]; area: number; /** Usable depth from the frontage chord to the parcel rear. */ depth: number; isCorner: boolean; kind: CityParcelKind; } /** Minimal street shape the fabric needs (matches cityKind's LocalStreet). */ export interface FabricStreet { points: readonly (readonly [number, number])[]; width: number; level: "boulevard" | "avenue" | "street" | "lane"; sidewalk: boolean; } /** Sidewalk band width by street hierarchy (base = the district's street-level width). */ export declare function sidewalkWidthFor(level: FabricStreet["level"], base: number): number; /** One extracted face: closed centerline polygon + the street index along each polygon edge. */ export interface GraphFace { polygon: Vec2[]; streets: number[]; } /** Everything block extraction needs from the district rules. */ export interface FabricParams { streetWidthBase: number; /** Base sidewalk band width in meters at "street" hierarchy level. */ sidewalkBase: number; /** Curb strip between pavement edge and sidewalk, meters. */ curbMargin: number; } /** A block's derived rings plus the sidewalk band (outer = curb line, inner = land line). */ export interface BlockRings { face: Vec2[]; faceStreets: number[]; curb: Vec2[]; land: Vec2[]; } /** * Extract closed blocks from the street network: planar faces of the graph, each inset to its * curb ring (pavement edge) and land ring (behind the sidewalk band). Faces that collapse under * the inset come back with an empty `land` and are the caller's slivers/buffers. */ export declare function extractBlocks(streets: readonly FabricStreet[], hx: number, hz: number, params: FabricParams): { blocks: BlockRings[]; deadEnds: { pts: Vec2[]; width: number; }[]; }; /** Minimal node/edge shapes {@link extractGraphBlocks} needs (matches `StreetNetwork.nodes`/`edges`). */ export interface GraphBlockNode { x: number; z: number; } /** One street-graph edge {@link extractGraphBlocks} traces block faces through. */ export interface GraphBlockEdge { /** Endpoint node indices. `points[0]` sits exactly on node `a`, the last on node `b`. */ a: number; b: number; points: readonly (readonly [number, number])[]; width: number; level: FabricStreet["level"]; /** Owning street index, carried through to pruned dead-end corridors so callers can line them. */ street?: number; } /** One block from {@link extractGraphBlocks}: the face polygon and its inset land ring. */ export interface GraphBlockRings { /** Face polygon on road CENTERLINES (as traced, interior faces CCW). */ face: Vec2[]; /** Land ring: face inset by each bordering edge's half width + curb margin + sidewalk band. */ land: Vec2[]; } /** * Extract closed blocks from an EXACT street graph — the `nodes`/`edges` a street network already * carries — instead of re-deriving topology from welded polylines. Half-edges are ordered by true * departure angle at each node, dangling chains are pruned first (returned as `deadEnds` for * corridor carving), interior faces are traced through the rotation system, and each face is inset * to its land ring by the bordering edges' half-widths (+ curb + sidewalk). Robust against wandered * and arc-filleted centerlines, which defeat proximity welding. Pure bounded geometry. * * @capability city-district extract block polygons directly from a street network's exact graph */ export declare function extractGraphBlocks(nodes: readonly GraphBlockNode[], edges: readonly GraphBlockEdge[], params: { sidewalkBase: number; curbMargin: number; }): { blocks: GraphBlockRings[]; deadEnds: { pts: Vec2[]; width: number; level: FabricStreet["level"]; street?: number; }[]; }; /** Arc-length station lookup along a closed ring. */ export declare class RingWalker { readonly ring: readonly Vec2[]; readonly cum: number[]; readonly total: number; constructor(ring: readonly Vec2[]); /** Point, unit tangent, and inward normal (CCW ring: interior on the left) at arc length `s`. */ at(s: number): { p: Vec2; tangent: Vec2; normal: Vec2; edge: number; }; /** Ring vertex indices whose arc position lies strictly inside (s0, s1) (wrapping). */ verticesBetween(s0: number, s1: number): number[]; /** Interior angle (radians) at ring vertex i — π on straight runs, < π at convex corners. */ interiorAngle(i: number): number; } /** Inputs for one parcel strip cut from a block ring. */ export interface ParcelCut { /** Arc-length start/end stations along the block's land ring. */ s0: number; s1: number; /** Strip depth from the frontage inward. */ depth: number; } /** * Cut one polygonal parcel out of a block land ring: the frontage arc between two stations, * extruded inward along the station normals, clipped back to the block. Returns null when the * strip collapses (over-tight corner, sliver arc). */ export declare function cutParcel(walker: RingWalker, blockRing: readonly Vec2[], cut: ParcelCut): Vec2[] | null; /** * Conform a polygon to a containing ring: sample every edge (a straight edge can bulge across a * concave notch even when its endpoints hug the boundary), then pull each escaping point to its * nearest boundary point. Callers re-run this after any operation that adds mid-edge vertices. */ export declare function conformPolygonToRing(poly: readonly Vec2[], ring: readonly Vec2[]): Vec2[]; /** * Buildable polygon for a parcel: the parcel clipped by the front setback (a half-plane pushed in * from the frontage chord), side gaps along both cut lines, and a rear margin. Works on any * parcel polygon — no vertex correspondence needed. */ export declare function buildablePolygon(parcel: readonly Vec2[], frontA: Vec2, frontB: Vec2, frontSetback: number, sideGap: number, rearMargin: number, depth: number): Vec2[]; /** * Carve dead-end road corridors (cul-de-sac lanes pruned from the face graph) out of a polygon: * for every corridor segment crossing it, keep the polygon piece on the side where `keep` lies. */ export declare function carveCorridors(poly: Vec2[], keep: Vec2, corridors: readonly { pts: readonly Vec2[]; width: number; }[], margin: number): Vec2[]; /** Deterministic sliver verdict shared by classification and tests. */ export declare function isSliverBlock(land: readonly Vec2[], minArea: number, minWidth: number): boolean; /** Sample deterministic points inside a polygon by seeded rejection over its bounds. */ export declare function pointsInPolygon(ring: readonly Vec2[], count: number, rng: () => number, margin?: number): Vec2[];