import type { CellComplex, CellGroup } from '../geometry/cell-complex.js'; import type { HyperplaneSliceN } from './slice.js'; /** * Codimension-one sections of simplicial cells in ℝⁿ, with ancestry that * survives being sectioned again. * * A section is an **intersection**, not a projection: the emitted geometry is * the part of the input that lies in the hyperplane. It is injective on what it * keeps — each output point came from exactly one ambient point — and what it * loses is dimension rather than distinctness. A projection loses distinctness * instead, which is why one of them can name a source point and the other * cannot. */ /** * Each output vertex as a sparse affine combination of **original** source * vertices, in compressed-row form. * * One source edge plus an interpolation parameter is enough ancestry for a * single cut and provably not enough for two: an edge of an already-sectioned * complex is generally not an edge of the original source, so a second section * would name a vertex that never existed in the input a reader started from. * Affine weights compose — the `t`-blend of two combinations is a combination — * so ancestry stays expressed in the original numbering however many times the * geometry is cut. * * Rows are sorted ascending by source vertex with duplicates merged, so * identical geometry produces identical rows bitwise. */ export interface SourceAffineLineageN { /** Row offsets into {@link sourceVertices} and {@link weights}. */ readonly offsets: Uint32Array; /** Original source vertex indices, ascending within each row. */ readonly sourceVertices: Uint32Array; /** Affine weights, summing to one per row. */ readonly weights: Float64Array; } /** What one section call cost and found, so "empty" is never guessed. */ export interface SectionSimplexGroupNDiagnosticsN { /** Source cells examined. */ readonly sourceCells: number; /** Source cells that produced at least one output cell. */ readonly sectionedCells: number; /** * Cells suppressed because no vertex was strictly below the hyperplane. * * This is the population that distinguishes "the plane misses this complex" * from "the plane grazes or contains it": a wholly on-plane cell lands here, * and so does a cell tangent at one vertex whose remaining vertices are all * on or above the plane. The reverse tangency — an on-plane vertex whose * companions are all strictly below — straddles the plane and is counted in * {@link collapsedSectionCells} instead. */ readonly suppressedOnPlaneCells: number; /** Cells with no vertex on or above the hyperplane. */ readonly cellsBelow: number; /** * Cells that straddle the hyperplane but whose every section cell collapsed. * * A tangency whose on-plane vertex is its only non-below vertex, or an * on-plane sub-face that drops the section below full dimension, welds every * crossing onto too few distinct vertices for any staircase cell to survive. * Nothing dimensionally false is emitted, but the welded vertices remain in * `ambientPositions`, `chartPositions` and `lineage` while belonging to no * cell — which is why `weldedVertices` can exceed the vertices any cell * names, and why a renderer must draw cells, never bare vertices. * * The four populations partition the input: * `sourceCells === sectionedCells + suppressedOnPlaneCells + cellsBelow + * collapsedSectionCells`. */ readonly collapsedSectionCells: number; /** Distinct output vertices after welding. */ readonly weldedVertices: number; /** Output vertices before welding, so the saving is visible. */ readonly crossingsFound: number; } /** What to section, with what, and what ancestry to compose through. */ export interface SectionSimplexGroupNOptions { /** The complex whose packed ambient positions are authoritative. */ readonly complex: CellComplex; /** One simplicial group of that complex. */ readonly group: CellGroup; /** The hyperplane and the chart the result is expressed in. */ readonly slice: HyperplaneSliceN; /** * Classification tolerance: `|signedDistance| <= epsilon` counts as exactly * on the hyperplane, and on-plane counts as non-negative. Default `1e-9`. */ readonly epsilon?: number; /** * Ancestry of the *input* vertices over an original source, for chaining. * * Omit for a first section, where each input vertex is its own ancestor. * Supply the previous section's `lineage` to keep the result expressed in the * original numbering rather than in the intermediate complex's. */ readonly lineage?: SourceAffineLineageN; } /** One codimension-one section: geometry, topology, ancestry, diagnostics. */ export interface SectionSimplexGroupNResultN { /** Ambient dimension, the same as the input complex's. */ readonly ambientDim: number; /** Chart dimension, one less than {@link ambientDim}. */ readonly chartDim: number; /** Intrinsic dimension of the emitted cells: one less than the input's. */ readonly cellDim: number; /** `cellDim + 1`, because the output is simplicial too. */ readonly verticesPerCell: number; /** Distinct output vertices after welding. */ readonly vertexCount: number; /** Emitted `(k-1)`-simplices. */ readonly cellCount: number; /** Packed ambient coordinates, `ambientDim` per vertex, all on the plane. */ readonly ambientPositions: Float64Array; /** Packed chart coordinates, `chartDim` per vertex. */ readonly chartPositions: Float64Array; /** * Flat vertex indices, `verticesPerCell` per cell. * * Within one parent the emitted simplices are coherently oriented — as the * boundary of the parent's below-plane region under the parent's own * vertex-order orientation — so reversing the hyperplane normal reverses * them, an odd relabelling of a parent reverses that parent's cells, and * consistently oriented adjacent parents induce opposite orientations on a * shared section face. Global coherence across an inconsistently oriented * input complex is not promised, and a degenerate parent's class is * deterministic but unspecified. Points (`verticesPerCell === 1`) carry no * orientation. */ readonly cells: Uint32Array; /** Source cell index each output cell was cut from. */ readonly parentCells: Uint32Array; /** Each output vertex as an affine combination of original source vertices. */ readonly lineage: SourceAffineLineageN; /** Populations that distinguish an empty section from a suppressed one. */ readonly diagnostics: SectionSimplexGroupNDiagnosticsN; } /** * Sections one simplicial cell group with a hyperplane, in any ambient * dimension. * * Every emitted vertex lies on the hyperplane and carries a sparse affine * combination of original source vertices, so ancestry survives a second * section. Vertices are welded by original source identity — a source vertex for * an on-plane crossing, an unordered source edge for an interpolated one — so * adjacent cells sharing a cut feature share one output vertex rather than * cracking. * * Non-simplicial input is refused rather than guessed: simplexize it first (see * `simplexizeCuboidGroupN`). Cells with no vertex strictly below the hyperplane * are suppressed rather than emitted, which is how a wholly on-plane cell avoids * being returned twice; straddling cells whose every section cell collapses are * counted apart from both, so the four diagnostic populations partition the * input and "empty" is never ambiguous. * * Emitted cells are oriented: each parent's section is the coherently wound * boundary of its below-plane region (staircase shuffle parity for coherence, * one barycentric determinant per parent for the class), so oriented area and * flux integrals over a section accumulate instead of cancelling. See the * `cells` field for exactly what is and is not promised. * * @param options - The complex and one of its simplicial groups, the hyperplane, * the classification tolerance, and the ancestry to compose through. * @returns Ambient and chart geometry, `(k-1)`-simplices, parent cells, * composable ancestry, and diagnostics. * * @example * A triangle group in R4 sections to a 1-complex whose vertices still name R4 * source vertices, with weights that reconstruct the ambient point: * ```ts * const positions = Float64Array.from([ * 0, 0, 0, -1, * 2, 0, 0, 1, * 0, 2, 0, 1 * ]); * const complex = new CellComplex(4, positions, [ * { dim: 2, verticesPerCell: 3, kind: 'simplex', indices: Uint32Array.from([0, 1, 2]) } * ]); * const group = complex.groups[0]; * if (group === undefined) throw new Error('expected one group'); * * const slice = HyperplaneSliceN.axisAligned(4, 3, 0); * const section = sectionSimplexGroupN({ complex, group, slice }); * * section.cellDim; // 1 — a triangle cut by a hyperplane is a segment * section.cellCount; // 1 * section.vertexCount; // 2 * section.diagnostics.sectionedCells; // 1 * * // Every emitted vertex is on the plane, and names its ancestors. * const w = section.ambientPositions[3]; * Math.abs(w ?? 1) < 1e-12; // true * const first = section.lineage.offsets[0] ?? 0; * const past = section.lineage.offsets[1] ?? 0; * past - first; // 2 — a crossing is an affine combination of two source vertices * ``` */ export declare function sectionSimplexGroupN(options: SectionSimplexGroupNOptions): SectionSimplexGroupNResultN; //# sourceMappingURL=section.d.ts.map