import { type Aabb, type Vec2 } from "./geometry.js"; /** * Deterministic spatial sampling — draw one or many positions from a geometric region under * explicit constraints, with an injected RNG, bounded attempts, and a structured pass/fail result. * * The three concerns stay separate on purpose (issue #940): a {@link SampleRegion} owns *where and * how* candidates are drawn (its distribution policy — area- vs radial-uniform, edge-biased, weighted * subregions); {@link SampleConstraints} own *which* candidates survive (include/exclude regions, * keep-out distance via disc/sphere exclusions, caller predicates, surface projection, minimum * separation); and {@link samplePoint}/{@link sampleBatch} own *how hard we try* (attempt budget and * fallback policy). Nothing silently accepts an invalid point — exhaustion is reported, not hidden. */ /** A 3D point `[x, y, z]` — the volume/surface counterpart to {@link Vec2}. */ export type Point3 = readonly [number, number, number]; /** A sampled point: 2D `[x, z]` or 3D `[x, y, z]`. Every point in one run shares its dimensionality. */ export type SamplePoint = readonly number[]; /** * A geometric region a sampler can draw from. Built-ins cover point sets, rect/box, circle/sphere, * annulus/shell, polygon, and weighted composites; {@link customRegion} wraps a caller-defined * sampler + bounds. `sample` draws one raw candidate under the region's own distribution policy; * `contains` is the membership test reused for include/exclude constraints and rejection. */ export interface SampleRegion
{ /** 2 for planar regions (`[x, z]`), 3 for volumetric/surface regions (`[x, y, z]`). */ readonly dimensions: 2 | 3; /** True when the region can never yield a point (degenerate size, empty set) — reported, not sampled. */ readonly isEmpty: boolean; /** Draw one raw candidate using `rng` (may still fail constraints). Consumes a fixed number of draws. */ sample(rng: () => number): P; /** Membership test on the XZ (or XYZ) footprint — powers include gates and exclude keep-outs. */ contains(point: P): boolean; } /** * Post-draw gates a candidate must clear. All are optional; an empty constraint set accepts every * in-region draw. `exclude` discs/spheres are the keep-out distance primitive ("no closer than R to * this center"); `accept` is a free-form caller predicate (biome, slope, ownership); `project` snaps * a candidate onto a surface/navmesh before validation and may reject by returning `null`. */ export interface SampleConstraints
{ /** Candidate must lie inside *every* listed region (intersection); omit for no inclusion gate. */ include?: readonly SampleRegion
[]; /** Candidate must lie outside *every* listed region — the keep-out/exclusion set. */ exclude?: readonly SampleRegion
[]; /** Extra caller acceptance test applied last; `false` rejects the candidate. */ accept?: (point: P) => boolean; /** Optional surface/navmesh projection applied before validation; `null` rejects the candidate. */ project?: (point: P) => P | null; /** Reject a candidate within this Euclidean distance of an already-accepted point in the same run. */ minSeparation?: number; } /** * What to return when the attempt budget is exhausted. `"none"` yields no point (honest failure); * `"last-candidate"` returns the final rejected draw (post-projection); `{ point }` returns a caller * fixed fallback (a hand-placed safe spot). Explicit, so a caller never mistakes a fallback for a hit. */ export type FallbackPolicy
= "none" | "last-candidate" | { readonly point: P; }; /** Why a sample ended: a clean hit, budget exhausted, or the region was degenerate. */ export type SampleReason = "accepted" | "exhausted" | "empty-region"; /** Structured single-sample outcome — never a bare point, so failure and fallback are visible. */ export interface SampleResult
{ /** True only for `"accepted"` — a genuine in-constraints hit (fallbacks are never `ok`). */ readonly ok: boolean; /** The accepted point, or the fallback point, or `null` when no fallback applied. */ readonly point: P | null; /** How many candidates were drawn (0 for an empty region). */ readonly attempts: number; /** Outcome classification. */ readonly reason: SampleReason; /** True when `point` came from the fallback policy rather than a passing draw. */ readonly usedFallback: boolean; } /** Inputs for a single {@link samplePoint} draw. */ export interface SamplePointOptions
{ region: SampleRegion
; /** Injected deterministic RNG in `[0, 1)`; same stream → same result. */ rng: () => number; constraints?: SampleConstraints
; /** Maximum candidates to draw before giving up. Default 30. Bounds the work. */ maxAttempts?: number; /** What to return on exhaustion. Default `"none"`. */ fallback?: FallbackPolicy
; /** Already-placed points the new point must respect `minSeparation` against (batch threads this). */ placed?: readonly P[]; } /** * Draw one point from `region` that clears every constraint, retrying up to `maxAttempts` times with * the injected `rng`, then apply the `fallback` policy. Returns a structured {@link SampleResult} — * a clean hit is `ok: true`; exhaustion or a degenerate region is reported with the attempt count and * an explicit fallback flag, never a silently-accepted bad point. * * @capability spatial-sample draw one deterministic position from a region under include/exclude/distance constraints */ export declare function samplePoint
(options: SamplePointOptions
): SampleResult
; /** Inputs for a {@link sampleBatch} draw of many spaced points. */ export interface SampleBatchOptions
{ region: SampleRegion
; rng: () => number; /** How many points to place. */ count: number; constraints?: SampleConstraints
; /** Attempt budget **per point**. Default 30. */ maxAttempts?: number; } /** Structured batch outcome: the placed points plus whether the full count was met. */ export interface SampleBatchResult
{ /** Accepted points in deterministic draw order. */ readonly points: readonly P[]; /** The requested count. */ readonly requested: number; /** How many were actually placed (`<= requested`). */ readonly placed: number; /** True when `placed === requested`. */ readonly complete: boolean; /** Total candidates drawn across all points. */ readonly attempts: number; } /** * Place up to `count` points from `region`, each clearing the shared constraints and honoring * `minSeparation` against every point already placed in this batch (via a spatial hash, not an * O(n²) scan). Order is deterministic for a given `rng` stream. Batches never silently under-fill: * the result reports `placed`, `complete`, and the total attempt count. * * @capability spatial-sample-batch place many deterministic spaced points from a region with minimum separation */ export declare function sampleBatch
(options: SampleBatchOptions
): SampleBatchResult
; /** Fill policy for a circle/ring: `"area"` = area-uniform (even density); `"radial"` = radius-uniform (clumps toward center). */ export type RadialDistribution = "area" | "radial"; /** Fill policy for a sphere/shell: `"volume"` = volume-uniform (even density); `"radial"` = radius-uniform. */ export type VolumeDistribution = "volume" | "radial"; /** Fill policy for a rect/box: `"uniform"` = even coverage; `"edge"` = biased to a boundary band. */ export type AreaDistribution = "uniform" | "edge"; /** A weighted member of a {@link weightedRegion} composite. */ export interface WeightedRegionEntry
{ region: SampleRegion
;
/** Relative selection weight; non-positive entries are never chosen. */
weight: number;
}
/**
* A fixed set of candidate points sampled by (optionally weighted) selection — the "spawn point
* table" region. `contains` is exact membership. Empty when the set is empty.
*
* @capability spatial-region-point-set sample from a fixed, optionally weighted candidate point table
*/
export declare function pointSetRegion(points: readonly Vec2[], options?: {
weights?: readonly number[];
}): SampleRegion (entries: readonly WeightedRegionEntry []): SampleRegion ;
/**
* Wrap a caller-defined sampler and bounds test as a {@link SampleRegion} — the escape hatch for
* regions the built-ins do not cover (a navmesh cell, a heightfield mask, a spline tube).
*
* @capability spatial-region-custom wrap a caller-defined sampler and bounds test as a region
*/
export declare function customRegion (spec: {
dimensions: 2 | 3;
sample: (rng: () => number) => P;
contains: (point: P) => boolean;
isEmpty?: boolean;
}): SampleRegion ;
/**
* An axis-aligned box `[min..max]` in 3D. Uniform density; draws x, y, z in order.
*
* @capability spatial-region-box sample uniformly within an axis-aligned 3D box
*/
export declare function boxRegion(min: Point3, max: Point3): SampleRegion