import { CellComplex, VecN, type CellGroup, type SourceSimplexReferenceN } from '@holotope/core'; /** * One axis-aligned coordinate interval per axis. * * Shared with the exhaustive candidate family rather than reimplemented there. * The hierarchy may only reject what the exhaustive scan would reject, and both * decide with these exact numbers — an independently derived "equivalent" bound * would be a second source of truth for the same proof. */ export interface XpbdSourceSimplexBoundsN { readonly min: Float64Array; readonly max: Float64Array; } /** Raw coordinate bounds of one persistent obstacle simplex, plus roundoff. */ export declare function xpbdSourceSimplexBoundsN(reference: SourceSimplexReferenceN): XpbdSourceSimplexBoundsN; /** The swept point envelope: both endpoints, expanded by activation distance. */ export declare function xpbdSweptPointBoundsN(before: VecN, after: VecN, padding: number): XpbdSourceSimplexBoundsN; /** * Inclusive overlap with the axis comparisons it needed. * * Separation on any single axis is a complete rejection proof: if Euclidean * point--simplex distance were at or below the activation distance, the * expanded query interval would meet the simplex interval on every axis. * Touching counts as overlapping, so a pair exactly at the boundary survives. */ export declare function xpbdSourceSimplexBoundsOverlapN(left: XpbdSourceSimplexBoundsN, right: XpbdSourceSimplexBoundsN): { readonly overlaps: boolean; readonly axisTests: number; }; /** Construction options for one immutable static-obstacle hierarchy. */ export interface CompileXpbdSourceSimplexAabbHierarchyNOptions { /** Static obstacle complex owning the indexed simplex group. */ readonly obstacle: CellComplex; /** Non-empty simplex group belonging to `obstacle`. */ readonly simplexGroup: CellGroup; /** * Maximum simplices per leaf. Default 8. * * Smaller leaves prune more and store more nodes; a leaf larger than the * simplex count degenerates to one exhaustive leaf, which is legal and * reported rather than prevented. */ readonly leafSize?: number; } /** * Work one hierarchy query actually performed. * * These are operation counts, never times. A tree that visits fewer nodes but * retains a different set is wrong, not fast, so the counts exist to show the * pruning is real — and, on an obstacle that cannot be separated, that it is * not. */ export interface XpbdSourceSimplexAabbQueryDiagnosticsN { /** Simplices indexed by the whole hierarchy. */ readonly totalSimplices: number; /** Internal and leaf nodes reached, including those immediately rejected. */ readonly visitedNodes: number; /** Leaves whose bound overlapped, so their simplices were tested. */ readonly visitedLeaves: number; /** Individual simplex bounds compared; the exhaustive path tests all. */ readonly testedSimplexBounds: number; /** Simplices whose own bound overlapped the query. */ readonly retainedSimplices: number; /** Axis comparisons across node and simplex tests, after early exits. */ readonly axisTests: number; } /** Retained simplices for one query box, in persistent obstacle-cell order. */ export interface XpbdSourceSimplexAabbQueryN { /** Source references, never tree ordinals; identity survives traversal. */ readonly simplices: readonly SourceSimplexReferenceN[]; /** Persistent group-cell ordinals, ascending, parallel to `simplices`. */ readonly cellIndices: readonly number[]; /** Auditable reduction counts for this exact query. */ readonly diagnostics: XpbdSourceSimplexAabbQueryDiagnosticsN; } /** * An immutable AABB tree over one static source-simplex obstacle. * * It answers exactly one question: which persistent obstacle simplices could * lie within the activation distance of a queried point or segment. It never * measures a distance, never decides that a retained pair is in contact, and * never becomes contact identity — the P44 exact barrier and its conservative * prefix filter remain authoritative behind it. * * The obstacle is static for the hierarchy's lifetime. Bounds are computed once * at compilation, so a later coordinate change would silently invalidate every * one of them; the hierarchy therefore snapshots the coordinates it indexed and * refuses loudly rather than answering from a stale tree. There is no automatic * rebuild, because a rebuild that happens by itself is indistinguishable from a * tree that was never stale. * * Construction is deterministic, so equivalent sources produce equivalent * evidence: split on the axis of greatest centroid extent with ties to the * lowest axis index, stable-sort by centroid with ties to the persistent cell * index, and split at the lower median by count. Splitting by count rather than * by a geometric threshold is what keeps a degenerate obstacle — every centroid * identical — from putting every simplex on one side and recursing forever. */ export declare class XpbdSourceSimplexAabbHierarchyN { /** Ambient obstacle dimension. */ readonly dimension: number; /** Static obstacle complex this hierarchy indexes. */ readonly obstacle: CellComplex; /** Indexed simplex group; identity, not structural equality, is required. */ readonly simplexGroup: CellGroup; /** Persistent obstacle simplices in group-cell order. */ readonly simplices: readonly SourceSimplexReferenceN[]; /** Maximum simplices per leaf, as resolved at compilation. */ readonly leafSize: number; private readonly root; /** Simplex indices permuted into tree order; leaves address ranges of it. */ private readonly order; private readonly bounds; /** Vertices contributing to the indexed group, ascending and deduplicated. */ private readonly contributingVertices; /** Their coordinates at compilation, for exact staleness comparison. */ private readonly coordinateSnapshot; private constructor(); /** Compiles persistent references, bounds, the tree, and the snapshot. */ static compile(options: CompileXpbdSourceSimplexAabbHierarchyNOptions): XpbdSourceSimplexAabbHierarchyN; /** * Throws unless the indexed obstacle is exactly as it was at compilation. * * `O(indexed source coordinates)`, which is the point: it is cheaper than the * `O(dynamic vertices × simplices)` search it guards, and a stale tree that * merely looks plausible is worse than a slow one. */ assertSourceCurrent(caller: string): void; /** * Retained simplices whose bounds meet `bounds`, in obstacle-cell order. * * Traversal happens in tree order; the result is restored to the persistent * cell order the exhaustive path produces, so tree shape can never be * observed downstream as a different candidate sequence. */ query(bounds: { readonly min: ArrayLike; readonly max: ArrayLike; }): XpbdSourceSimplexAabbQueryN; /** * The traversal, with validation and staleness already established. * * The candidate family calls this once per dynamic vertex after checking the * obstacle once per query, so a hundred vertices do not pay for a hundred * identical snapshot comparisons. * * Internal precisely because it skips that check: reaching it directly would * be a way to query a stale tree without being told. Use {@link query}. * * @internal */ queryChecked(box: XpbdSourceSimplexBoundsN): XpbdSourceSimplexAabbQueryN; } /** * Compiles one immutable AABB hierarchy over a static source-simplex obstacle. * * Nothing selects this automatically. The exhaustive scan remains the default * and the correctness oracle, and a caller opts in by passing the compiled * hierarchy to `compileXpbdParticleSourceSimplexBarrierFamilyN`, where it must * name the same obstacle and group objects the family itself indexes. * * @param options - The static obstacle, its simplex group, and an optional * positive `leafSize` (default 8). * @returns An immutable hierarchy bound to those exact objects. * @throws If the group does not belong to the obstacle, is not a complete * non-empty simplex group, carries a non-finite coordinate, or if `leafSize` * is not a positive safe integer. * * @example * Compile once against a static obstacle, then query many times. The hierarchy * returns source references, so identity survives traversal: * ```ts * const obstacle = new CellComplex(4, Float64Array.from([ * 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, * 5, 0, 0, 0, 6, 0, 0, 0, 5, 1, 0, 0, 5, 0, 1, 0 * ]), [{ * key: 'obstacle', dim: 3, verticesPerCell: 4, kind: 'simplex', * indices: Uint32Array.from([0, 1, 2, 3, 4, 5, 6, 7]) * }]); * const [group] = obstacle.cellsOfDim(3); * if (!group) throw new Error('the obstacle has no 3-cells'); * * const hierarchy = compileXpbdSourceSimplexAabbHierarchyN({ * obstacle, simplexGroup: group, leafSize: 1 * }); * * const near = hierarchy.query({ * min: [-0.5, -0.5, -0.5, -0.5], max: [0.5, 0.5, 0.5, 0.5] * }); * log(near.cellIndices); // [0] — the far one is pruned * log(near.diagnostics.testedSimplexBounds); // fewer than totalSimplices * log(near.simplices[0]?.parent.cellIndex); // persistent obstacle identity * ``` */ export declare function compileXpbdSourceSimplexAabbHierarchyN(options: CompileXpbdSourceSimplexAabbHierarchyNOptions): XpbdSourceSimplexAabbHierarchyN; //# sourceMappingURL=xpbd-source-simplex-aabb-hierarchy.d.ts.map