/** * Deterministic 2D polygon math for the `city` studio's block/parcel pipeline: signed areas, * point-in-polygon, half-plane clipping, per-edge inward insets (the curb → sidewalk → land and * parcel → buildable transforms), simple-loop recovery after aggressive insets, and rotated-rect * fitting inside arbitrary polygons. Pure functions over `[x, z]` tuples — no allocation-heavy * classes, no rendering, no randomness — so every consumer from the resolver to the tests shares * one geometric truth. * * @capability city-district polygon math for road-derived blocks, parcels, and buildable footprints */ /** A 2D point in district-local or world XZ space. */ export type Vec2 = readonly [number, number]; /** Signed area of a polygon ring (positive = counter-clockwise in XZ). */ export declare function polygonSignedArea(ring: readonly Vec2[]): number; /** Absolute polygon area. */ export declare function polygonArea(ring: readonly Vec2[]): number; /** Ring perimeter length. */ export declare function polygonPerimeter(ring: readonly Vec2[]): number; /** Area centroid of a simple polygon (falls back to the vertex mean for degenerate rings). */ export declare function polygonCentroid(ring: readonly Vec2[]): Vec2; /** Ensure counter-clockwise winding (positive signed area) without mutating the input. */ export declare function ensureCcw(ring: readonly Vec2[]): Vec2[]; /** Even-odd point-in-polygon test. */ export declare function pointInPolygon(ring: readonly Vec2[], x: number, z: number): boolean; /** Distance from a point to the nearest boundary segment of a ring. */ export declare function distanceToRing(ring: readonly Vec2[], x: number, z: number): number; /** * Clip a polygon to the half-plane `dot(p, normal) <= limit` (Sutherland–Hodgman step). Returns * the surviving ring, possibly empty. The subject may be concave; the output of a single * half-plane clip of a simple ring is always a valid (possibly pinched) ring. */ export declare function clipHalfPlane(ring: readonly Vec2[], normal: Vec2, limit: number): Vec2[]; /** Split a polygon by the line `dot(p, normal) = limit` into the `<=` and `>=` sides. */ export declare function splitByLine(ring: readonly Vec2[], normal: Vec2, limit: number): { below: Vec2[]; above: Vec2[]; }; /** Remove consecutive (near-)duplicate vertices; returns [] when fewer than 3 survive. */ export declare function dedupeRing(ring: readonly Vec2[], epsilon?: number): Vec2[]; /** True when any two non-adjacent edges of the ring properly cross. O(n²), fine for block-scale n. */ export declare function ringSelfIntersects(ring: readonly Vec2[]): boolean; /** * Recover the dominant simple loop from a lightly self-intersecting ring: whenever two edges * cross, the smaller of the two loops the crossing pinches off is dropped. Bounded passes; rings * that stay tangled after that return []. This is the cleanup net under aggressive insets — a * concave block inset past a neck pinches into a bow-tie, and the bigger lobe is the block. */ export declare function extractSimpleLoop(ring: readonly Vec2[], maxPasses?: number): Vec2[]; /** * Inset a CCW ring inward with a PER-EDGE distance (edge i runs vertex i → i+1). True corners take * the intersection of the two adjacent offset lines (so a street corner lands exactly where the * two curb lines meet, even with different street widths); near-collinear vertices — polyline * samples along a curve — fall back to the averaged-normal offset, which is exact in the limit. * Returns [] when the inset collapses (sliver) or stays tangled after loop recovery. */ export declare function insetRing(ring: readonly Vec2[], edgeDistances: readonly number[]): Vec2[]; /** Uniform-distance convenience over {@link insetRing}. */ export declare function insetRingUniform(ring: readonly Vec2[], distance: number): Vec2[]; /** * Mean "width" proxy for sliver detection: the diameter of the largest inscribed strip is * approximated by 2·area / perimeter (exact for long rectangles, conservative elsewhere). */ export declare function polygonMeanWidth(ring: readonly Vec2[]): number; /** All four corners of a rotated rect (center, half-extents, yaw in engine rotationY convention). */ export declare function rectCorners(cx: number, cz: number, hw: number, hd: number, rotationY: number): Vec2[]; /** A rotated rectangle: center, half-extents, and yaw in the engine rotationY convention. */ export interface OrientedRect { x: number; z: number; hw: number; hd: number; angle: number; } /** * Separating-axis test for two rotated rectangles: true when a gap exists (no overlap). * @capability city-district exact overlap test between two rotated rectangles (plots, lots, footprints) */ export declare function rectsSeparated(a: OrientedRect, b: OrientedRect): boolean; /** * True when a rotated rect stays at least `clearance` away from every segment of a polyline — * the plot-contract test that keeps building plots off road corridors (pass the road half-width * as the clearance). Exact segment-vs-expanded-rect intersection, not corner sampling. * @capability city-district keep a rotated rect (plot/footprint) clear of a road corridor polyline */ export declare function rectClearsPolyline(rect: OrientedRect, path: readonly Vec2[], clearance: number): boolean; /** True when the whole rotated rect (corners + edge midpoints) sits inside the ring. */ export declare function rectInsidePolygon(ring: readonly Vec2[], cx: number, cz: number, hw: number, hd: number, rotationY: number): boolean; /** * Fit the largest rect (up to `maxW`×`maxD`, oriented to `rotationY`, centered near `[cx, cz]`) * inside a polygon by bounded bisection on a single scale factor. Deterministic; returns null * when even `minScale` of the request does not fit after nudging the center inward. */ export declare function fitRectInPolygon(ring: readonly Vec2[], cx: number, cz: number, maxW: number, maxD: number, rotationY: number, minScale?: number): { w: number; d: number; cx: number; cz: number; } | null; /** * Distance from `origin` along `dir` to the first ring-boundary crossing (ignoring hits closer * than `minT`, e.g. the edge the origin sits on). Infinity when the ray never leaves — degenerate. */ export declare function rayDistanceToRing(ring: readonly Vec2[], origin: Vec2, dir: Vec2, minT?: number): number; /** Axis-aligned bounding box of a ring. */ export declare function ringBounds(ring: readonly Vec2[]): { minX: number; minZ: number; maxX: number; maxZ: number; }; /** * Ear-clip a simple polygon into triangles. Returns the cleaned/CCW-oriented vertex ring plus flat * index triples into it (so a draping consumer can sample a height per returned vertex). O(n²) — * fine for block/park rings. A polygon that stays un-clippable (self-touching after dedupe) yields * whatever ears were found before the guard trips; degenerate input returns no triangles. */ export declare function triangulatePolygon(ring: readonly Vec2[]): { positions: Vec2[]; indices: number[]; }; /** * Triangulate the band between two nested rings (an outer ring with an inner hole, e.g. a curb ring * around a land ring — the sidewalk). Pairs the loops by cumulative arc-length fraction, aligning * the inner start to the outer's first vertex, so it is robust to differing vertex counts and to a * rotational offset between the rings. Returns the merged vertex list (outer, then the rotated * inner) and index triples into it. Zero-area triangles — where the two rings coincide (a street * edge with no sidewalk) — are dropped so `computeVertexNormals` never sees a degenerate face. */ export declare function triangulateRingBand(outer: readonly Vec2[], inner: readonly Vec2[]): { positions: Vec2[]; indices: number[]; }; /** Conservative polygon-overlap test: any vertex of one strictly inside the other, or any edges crossing. */ export declare function polygonsOverlap(a: readonly Vec2[], b: readonly Vec2[], shrink?: number): boolean;