import { CellComplex, VecN, type CellGroup } from '@holotope/core'; import { type ClampedLogBarrierForceN } from './clamped-log-barrier.js'; import { type GjkResult } from './gjk.js'; import { type XpbdIncrementalPotentialStepFilterContextN, type XpbdIncrementalPotentialStepFilterN } from './xpbd-incremental-potential-step-filter.js'; import { XpbdParticleBindingN } from './xpbd-particle-binding.js'; import { XpbdParticleN, XpbdWorldN, type XpbdConservativeForceProviderEvaluationN, type XpbdConservativeForceProviderN, type XpbdParticlePositionQueryN } from './xpbd-world.js'; /** * Why a convex-hull barrier could not evaluate a candidate position. * * `'at-or-below-minimum-distance'` is the ordinary open-domain refusal shared * with every other barrier: the candidate is at or inside the boundary, which * has no finite energy and must be refused rather than scored. * * `'closest-point-indeterminate'` is different in kind. The distance query ran * out of iteration budget without certifying either separation or intersection, * so the geometry is *unknown* rather than bad. It is reported separately * precisely so it cannot be quietly read as "separated" or as a zero force. */ export type XpbdParticleSourceConvexHullBarrierDomainReasonN = 'at-or-below-minimum-distance' | 'closest-point-indeterminate' | 'barrier-component-outside-float64'; /** Construction options for one dynamic point--static-convex-hull family. */ export interface CompileXpbdParticleSourceConvexHullBarrierFamilyNOptions { /** Stable provider identity and record-ID prefix. */ readonly id: string; /** One live particle per authoritative dynamic source vertex. */ readonly binding: XpbdParticleBindingN; /** Separate static obstacle complex supplying the hull's source vertices. */ readonly obstacle: CellComplex; /** * Group whose cells select which obstacle vertices the hull spans. * * The cells select *vertices*. They do not mean that the union of those cells * is represented: the represented set is the convex hull of the selected * vertices, which fills every concavity between them. */ readonly sourceGroup: CellGroup; /** Open unsigned-distance boundary. Default zero. */ readonly minimumDistance?: number; /** Distance at and above which the barrier energy is exactly zero. */ readonly activationDistance: number; /** Positive energy scale. */ readonly stiffness: number; /** Fraction of each certified Lipschitz prefix retained. Default `0.9`. */ readonly conservativeScale?: number; /** Bound on distance-query iterations per particle. Default `32`. */ readonly maximumQueryIterations?: number; } /** Source evidence behind one closest-point answer. */ export interface XpbdSourceConvexHullWitnessN { /** * Authoritative obstacle vertices the returned support simplex rests on. * * These are indices into the **obstacle complex**, translated back from the * packed hull's own slot numbering. A caller must never receive the packing * slots: selection and source permutation both make them differ. */ readonly sourceVertices: readonly number[]; /** Closest point on the hull. */ readonly closestPoint: VecN; /** Unit vector from the hull toward the particle. */ readonly separationNormal: VecN; /** Complete distance-query evidence, for auditing a surprising answer. */ readonly query: GjkResult; } /** One particle's active barrier against the hull. */ export interface XpbdParticleSourceConvexHullActiveBarrierN { /** Family-scoped stable identity for this particle's record. */ readonly id: string; /** Dynamic source vertex whose bound particle supplied the point. */ readonly sourceVertexIndex: number; /** Unsigned Euclidean distance to the closed convex hull. */ readonly distance: number; /** `distance - minimumDistance`. */ readonly barrierCoordinate: number; /** * The graded order-1 scalar evaluation this force was built from. Both * required components were available (the provider refused otherwise). */ readonly barrier: ClampedLogBarrierForceN; /** Closest point, normal, and source vertices behind this answer. */ readonly witness: XpbdSourceConvexHullWitnessN; } /** What one complete family evaluation cost and found. */ export interface XpbdParticleSourceConvexHullQueryDiagnosticsN { /** Dynamic source vertices considered. */ readonly sourceVertexCount: number; /** Vertices spanning the hull. */ readonly hullVertexCount: number; /** * Distance queries performed: exactly one per dynamic vertex. * * There is no per-cell fan-out here. That is the whole point of the family: * one convex set produces one closest point, not one per decomposition cell. */ readonly setQueries: number; /** Total distance-query iterations, summed over particles. */ readonly queryIterations: number; /** Particles whose exact distance is inside the activation distance. */ readonly activeParticles: number; } /** Aggregate evaluation over only the particles inside the activation band. */ export interface XpbdParticleSourceConvexHullBarrierFamilyEvaluationN extends XpbdConservativeForceProviderEvaluationN { /** One active record per active particle, in source order. */ readonly activeBarriers: readonly XpbdParticleSourceConvexHullActiveBarrierN[]; /** One accumulated force per bound source vertex. */ readonly forces: readonly VecN[]; /** Auditable per-evaluation counts. */ readonly diagnostics: XpbdParticleSourceConvexHullQueryDiagnosticsN; } /** Why the aggregate segment filter could not certify any prefix. */ export type XpbdParticleSourceConvexHullBarrierFamilyStepFilterRefusalReasonN = 'initial-domain-violation' | 'closest-point-indeterminate'; /** One particle's segment certification against the hull. */ export interface XpbdParticleSourceConvexHullSegmentCertificationN { /** Dynamic source vertex this certification describes. */ readonly sourceVertexIndex: number; /** Unsigned distance at the segment start. */ readonly startDistance: number; /** Start distance above the open minimum. */ readonly startMargin: number; /** Euclidean length of the complete proposed path. */ readonly pathLength: number; /** Initial distance derivative along the complete segment. */ readonly startDirectionalDerivative: number; /** Certified fraction of the requested segment, in `[0, 1]`. */ readonly certifiedFraction: number; /** Proof used; never an inferred or exact impact time. */ readonly certification: 'stationary' | 'convex-nondecreasing' | 'global-lipschitz' | 'initial-domain-violation' | 'closest-point-indeterminate'; } /** Aggregate certification evidence for one proposed segment. */ export type XpbdParticleSourceConvexHullBarrierFamilyStepFilterEvaluationN = { /** Per-particle certifications in stable source order. */ readonly certifications: readonly XpbdParticleSourceConvexHullSegmentCertificationN[]; /** Source vertex imposing the aggregate limit or refusal, otherwise `null`. */ readonly blockingSourceVertexIndex: number | null; } & ({ readonly status: 'safe'; readonly maximumStepLength: number; } | { readonly status: 'limited'; readonly maximumStepLength: number; } | { readonly status: 'indeterminate'; readonly reason: XpbdParticleSourceConvexHullBarrierFamilyStepFilterRefusalReasonN; }); /** Provider/filter pair accepted by an incremental-potential problem. */ export interface XpbdParticleSourceConvexHullBarrierFamilyTermsN { /** Existing providers followed by this convex-hull family provider. */ readonly providers: readonly XpbdConservativeForceProviderN[]; /** Existing filters followed by this family's paired segment certificate. */ readonly stepFilters: readonly XpbdIncrementalPotentialStepFilterN[]; } /** * Source-retained RN barriers from dynamic points to one **static convex hull**. * * The represented set is the convex hull of the obstacle vertices selected by * `sourceGroup`. That is a deliberate, narrow contract, and the difference * from the point--simplex family is the reason this exists: * * - `XpbdParticleSourceSimplexBarrierFamilyN` applies one barrier per obstacle * *cell* and sums them. That is exactly right for cells that are meaningful * contact features in their own right, and exactly wrong for cells that are * only a decomposition of one solid — each cell pushes away from itself, so * the sum is not normal to the solid's boundary; * - this family performs one closest-point query against one set, so a point * over a flat support is pushed along the support normal regardless of how * that support happened to be cut up. * * The set is the **hull**, not the union. Any concavity between the selected * vertices is filled. A caller who needs a non-convex obstacle must decompose * it into convex components they manage explicitly. * * The hull is **static** for the lifetime of a solve. Its coordinates are * snapshotted at compile time and every public entry point refuses if the * selected source has since moved or been relaid out, rather than silently * following it. * * The set may be lower-dimensional in its ambient space — a flat slab in R4 is * the motivating case. Proximity is unsigned and two-sided: this is a distance * barrier, not an inside/outside test, and a lower-dimensional set has no * inside to be on. * * ## What the barrier constrains, and what it does not * * One barrier per bound *particle*, so the domain certificate it maintains is * per-vertex: every constrained particle stays strictly outside the hull's * `minimumDistance` shell. * * That is **not** a certificate that a surface interpolated between those * particles is disjoint from the hull. A triangle can cross a convex set while * all three of its vertices remain legally outside it, so a mesh whose vertices * are all constrained here can still have interior geometry intersecting the * support. * * **Refinement does not remove this.** It is tempting to read the gap as a * spacing artefact that a finer mesh closes, and measurement does not support * that: over one authored scene driven to its terminal at two resolutions, the * finer mesh breached earlier in its own run than the coarser one, each * following that scene's own departure from the support rather than its vertex * spacing. Spacing bounds how far *inside* the set the surface can reach; it * does not decide whether the surface reaches inside at all. * * Constraining the surface itself needs edge- and face-level candidates. This * family does not provide them and does not claim to. */ export declare class XpbdParticleSourceConvexHullBarrierFamilyN implements XpbdConservativeForceProviderN { /** Stable conservative-provider identity. */ readonly id: string; /** Ambient dynamic-source and obstacle dimension. */ readonly dimension: number; /** Authoritative dynamic source-to-particle mapping. */ readonly binding: XpbdParticleBindingN; /** Bound particles in source-vertex order. */ readonly particles: readonly XpbdParticleN[]; /** Separate static obstacle source. */ readonly obstacle: CellComplex; /** Group whose cells selected the hull's source vertices. */ readonly sourceGroup: CellGroup; /** Sorted unique authoritative obstacle vertices spanning the hull. */ readonly hullSourceVertices: readonly number[]; /** Open unsigned-distance boundary. */ readonly minimumDistance: number; /** Distance at and above which energy and force are exactly zero. */ readonly activationDistance: number; /** Positive energy scale. */ readonly stiffness: number; /** Strict prefix scale used by the paired filter. */ readonly conservativeScale: number; /** Per-particle distance-query iteration bound. */ readonly maximumQueryIterations: number; /** Segment filter paired with this provider. */ readonly stepFilter: XpbdParticleSourceConvexHullBarrierFamilyStepFilterN; /** Support shape over the snapshotted hull coordinates. */ private readonly hull; /** Coordinates as they stood at compile time, for the staleness check. */ private readonly snapshot; private attachedWorld; private constructor(); /** Compiles the hull snapshot, source mapping, and paired filter. */ static compile(options: CompileXpbdParticleSourceConvexHullBarrierFamilyNOptions): XpbdParticleSourceConvexHullBarrierFamilyN; /** * Distance, closest point, and source witness for one candidate position. * * Throws {@link XpbdPotentialDomainErrorN} with * `'closest-point-indeterminate'` when the query cannot certify an answer. */ queryPoint(position: VecN): XpbdSourceConvexHullWitnessN & { readonly distance: number; }; /** Evaluates from the particles' current positions. */ evaluate(): XpbdParticleSourceConvexHullBarrierFamilyEvaluationN; /** Evaluates active barriers at a candidate state without mutating live state. */ evaluateAt(positionOf: XpbdParticlePositionQueryN): XpbdParticleSourceConvexHullBarrierFamilyEvaluationN; /** Returns this provider and its paired filter, optionally after base terms. */ incrementalPotentialTerms(base?: XpbdParticleSourceConvexHullBarrierFamilyTermsN): XpbdParticleSourceConvexHullBarrierFamilyTermsN; /** Registers this one dynamic provider; particles must already be present. */ addToWorld(world: XpbdWorldN): XpbdWorldN; /** Compares the live selected coordinates against the compile-time snapshot. */ assertSourceCurrent(caller: string): void; /** The distance query itself, without the staleness check callers repeat. */ private queryUnchecked; /** Maps packed hull slots back to authoritative obstacle vertex indices. */ private translateWitness; } /** * Conservative segment filter paired with one convex-hull barrier family. * * Distance to a closed convex set is convex and 1-Lipschitz, which is the same * argument the point--simplex filter uses and it transfers unchanged: a segment * whose initial distance is non-decreasing is safe in full, and otherwise the * global Lipschitz bound certifies a strict prefix. * * `maximumStepLength` is a **certified prefix**, never an exact impact time. * This filter does not solve the closest-feature crossing. */ export declare class XpbdParticleSourceConvexHullBarrierFamilyStepFilterN implements XpbdIncrementalPotentialStepFilterN { /** Stable authored filter identity. */ readonly id: string; /** Ambient particle and hull dimension. */ readonly dimension: number; /** Exact particles whose proposed segments are inspected. */ readonly particles: readonly XpbdParticleN[]; /** Paired barrier family. */ readonly family: XpbdParticleSourceConvexHullBarrierFamilyN; /** Creates the one filter owned by a compiled family. */ constructor(family: XpbdParticleSourceConvexHullBarrierFamilyN); /** Certifies the complete segment or a conservative strict prefix. */ evaluate(context: XpbdIncrementalPotentialStepFilterContextN): XpbdParticleSourceConvexHullBarrierFamilyStepFilterEvaluationN; } /** * Compiles a dynamic point--static-convex-hull barrier family and its filter. * * The represented set is the convex hull of the obstacle vertices selected by * `sourceGroup` — **not** the union of that group's cells. Concavities between * the selected vertices are filled. * * Before the first construction, the contract in one breath: the geometry is * the **convex hull** of the selected vertices — not an arbitrary non-convex * mesh, and not the union of the group's cells, whose only role is selecting * vertices; the hull is **static** for the lifetime of the compiled family; * contact is **point-to-set** for the bound particles, not complete surface * contact; proximity to a lower-dimensional hull is **unsigned and two-sided**, * because such a set has no ambient inside; each particle gets **one closest * set point**, whose witness may rest on several source vertices; an undecided * distance query stays a typed `'closest-point-indeterminate'` refusal rather * than an answer; and the paired filter's prefix is a certificate, never an * impact time. * * @param options Provider identity, binding, obstacle, selection, and barrier scales. * @returns The compiled family, whose `stepFilter` must travel with it. * * @example * One probe particle above a flat two-cell support in R3. The barrier pushes * along the support normal, and the witness names the source vertices behind * the closest point: * ```ts * const obstacle = new CellComplex(3, Float64Array.from([ * 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0 * ]), [{ * key: 'support', dim: 2, verticesPerCell: 3, kind: 'simplex', * indices: Uint32Array.from([0, 1, 2, 1, 3, 2]) * }]); * const body = new CellComplex(3, Float64Array.from([0.4, 0.6, 0.5]), [{ * key: 'probe', dim: 0, verticesPerCell: 1, kind: 'simplex', * indices: Uint32Array.from([0]) * }]); * const binding = compileXpbdParticleBindingN({ * source: body, id: 'probe', mass: 1 * }); * const [supportGroup] = obstacle.groups; * if (supportGroup === undefined) throw new Error('no support group'); * const family = compileXpbdParticleSourceConvexHullBarrierFamilyN({ * id: 'hull-contact', * binding, * obstacle, * sourceGroup: supportGroup, * minimumDistance: 0.05, * activationDistance: 0.8, * stiffness: 2 * }); * * const evaluation = family.evaluate(); * evaluation.diagnostics.setQueries; // 1 — one query per particle * evaluation.activeBarriers.length; // 1 * for (const record of evaluation.activeBarriers) { * record.distance; // 0.5 — the probe's height * record.witness.sourceVertices; // vertices behind the answer * } * for (const force of evaluation.forces) { * (force.data[2] ?? 0) > 0; // true — pushed along +z * Math.abs(force.data[0] ?? 0) < 1e-12; // true — no lateral push * } * ``` */ export declare function compileXpbdParticleSourceConvexHullBarrierFamilyN(options: CompileXpbdParticleSourceConvexHullBarrierFamilyNOptions): XpbdParticleSourceConvexHullBarrierFamilyN; //# sourceMappingURL=xpbd-source-convex-hull-barrier-family.d.ts.map