import { BufferGeometry, type Material, type Object3D } from 'three'; import { CellComplex, type CellGroup, type HyperplaneSliceN, type SectionSimplexGroupNResultN, type SourceAffineLineageN, type TransformN } from '@holotope/core'; /** Construction options for a {@link SectionChart3D}. */ export interface SectionChart3DOptions { /** * Material for the emitted primitives. Caller-owned: the product never * disposes a material it was given. Omit it and the product creates and owns * a sensible default for the primitive kind instead. */ readonly material?: Material; /** * Ancestry of the complex's vertices over an original source, for rendering * a *chained* section: pass the previous section's `lineage` and every * picked primitive still names the original vertices rather than the * intermediate complex's. */ readonly lineage?: SourceAffineLineageN; /** Classification tolerance forwarded to the section. Default `1e-9`. */ readonly epsilon?: number; } /** * Render product: the exact section of one simplicial cell group, drawn in the * hyperplane's own chart. * * This is the adapter an RN section needs to reach a renderer at all — * `sectionSimplexGroupN` hands back `chartDim` Float64 coordinates and * `(k−1)`-simplices, which is neither triangles nor Float32. The product * performs **exactly one section evaluation per {@link update}**, writes the * chart coordinates into the display axes, and keeps the whole immutable * result observable as {@link section}, so diagnostics, parent cells, and * original-source lineage stay auditable after every update. * * The intrinsic cell dimension is fixed by the group — `group.dim - 1` — so * the product constructs the one three.js primitive that dimension needs and * `object` never changes identity: `Points` for 0-cells, `LineSegments` for * 1-cells, a double-sided `Mesh` for 2-cells. Charts above dimension 3 and * cells above dimension 2 are refused by name: they have no display axes left, * and pretending otherwise would be a projection, not a section. * * Emitted triangles are coherently wound per parent (the section is the * oriented boundary of the parent's below-plane region), so normal-based * shading shows no seam and reversing the slice normal visibly flips the * facing. Renderer primitive `p` is emitted cell `p`: its parent source cell * is `section.parentCells[p]`, and each corner's affine ancestry row is in * `section.lineage` — which is what `representationHitFromSectionChart` reads. * * @example * A tetrahedron group in R4 sectioned into its own 3D chart, swept through the * body, with the evidence read back at every step: * ```ts * const positions = Float64Array.from([ * 0, 0, 0, -1, * 2, 0, 0, 1, * 0, 2, 0, 1, * 0, 0, 2, 1 * ]); * const complex = new CellComplex(4, positions, [ * { dim: 3, verticesPerCell: 4, kind: 'simplex', indices: Uint32Array.from([0, 1, 2, 3]) } * ]); * const group = complex.groups[0]; * if (group === undefined) throw new Error('expected the tetrahedron group'); * * const chart = new SectionChart3D(complex, group, HyperplaneSliceN.axisAligned(4, 3, 0)); * scene.add(chart.object); * * log('cells', chart.section.cellCount); // 1 — one coherently wound triangle * log('parent', chart.sourceCellOfPrimitive(0)); // 0 — the tetrahedron, exactly * // Each drawn corner is an affine blend of source vertices — the ancestry a * // projection cannot give. * const corner = chart.primitiveVertices(0)[0]; * if (corner !== undefined) log('ancestry', chart.vertexAncestry(corner)); * * // Sweep the hyperplane: one section evaluation per update, and the * // diagnostics say WHY a frame is empty rather than leaving it a guess. * onFrame((t) => { * chart.slice.offset = Math.sin(t * 0.001) * 1.5; * chart.update(); * const d = chart.section.diagnostics; * if (chart.cellCount === 0) { * log(d.cellsBelow ? 'plane above the body' : 'plane below the body'); * } * }); * ``` */ export declare class SectionChart3D { /** The authoritative complex. Read live at each update, never mutated. */ readonly complex: CellComplex; /** The simplicial group being sectioned. */ readonly group: CellGroup; /** The live chart: set `offset` or `setNormal(...)`, then {@link update}. */ readonly slice: HyperplaneSliceN; /** Intrinsic dimension of the drawn cells: `group.dim - 1`. */ readonly cellDim: number; /** * The drawn buffers: positions in chart coordinates, draw range 0 when empty. * * Only `[0, drawRange.count)` is live. The attribute is grown by doubling and * never shrunk, so slots past the draw range hold zeroes or a previous * frame's values; they are neither drawn, picked, nor measured. `boundingBox` * and `boundingSphere` are maintained by the product over exactly the live * range at every update — three.js's own conventions (component min/max box; * box-centre sphere with the largest live-vertex distance) applied to the * live slots only, so both are safe to read for framing a camera or sizing a * helper. An empty section reports explicitly empty volumes (`isEmpty()` on * both), never a stale or origin-inflated one. Calling three.js's * `computeBoundingBox`/`computeBoundingSphere` yourself would overwrite them * with capacity-wide values — don't; the next `update` restores the live * contract. */ readonly geometry: BufferGeometry; /** `Points`, `LineSegments`, or `Mesh`, fixed at construction. */ readonly object: Object3D; private readonly lineage; private readonly epsilon; private readonly posed; private readonly ownsMaterial; private readonly material; private positionAttribute; private latest; /** * Builds the product and performs its first section evaluation. * * @param complex - The authoritative complex; read live, never mutated. * @param group - The simplicial group of that complex to section. * @param slice - The hyperplane and chart; keep it and steer `offset`. * @param options - Material ownership, chained ancestry, and tolerance. */ constructor(complex: CellComplex, group: CellGroup, slice: HyperplaneSliceN, options?: SectionChart3DOptions); /** * The latest immutable section result: geometry, `parentCells`, `lineage`, * and `diagnostics` — including `collapsedSectionCells`, which is how an * empty draw range is told apart from a plane that missed the complex. * Replaced whole by each {@link update}; safe to retain and compare. */ get section(): SectionSimplexGroupNResultN; /** Emitted cells in the latest section; `0` draws nothing. */ get cellCount(): number; /** * Re-sections the current pose and rewrites the drawn buffers. * * @param transform - Optional pose in the source's ambient dimension, * applied into a product-private buffer; the authoritative complex is * never mutated. */ update(transform?: TransformN): void; /** Parent source cell of one rendered primitive, exactly. */ sourceCellOfPrimitive(primitive: number): number; /** Emitted section-vertex indices of one rendered primitive, in draw order. */ primitiveVertices(primitive: number): number[]; /** * Original-source ancestry of one emitted section vertex: parallel arrays of * source vertex indices and affine weights, from the latest result. */ vertexAncestry(vertex: number): { sourceVertices: number[]; weights: number[]; }; /** * Releases the geometry, and the material only if the product created it — * a caller-supplied material is caller-owned. (This deliberately differs * from `ProjectedEdges3D`, which disposes whatever it was handed.) */ dispose(): void; private evaluate; private write; /** * three.js's `computeBoundingBox`/`computeBoundingSphere` stop at the * attribute's end, not the draw range, so on this padded buffer they would * report the unused capacity — a section at x 40…41 in a fresh 64-slot * buffer came back as a box containing the origin and a sphere 25× its true * radius. Same two conventions, live slots only: component-wise min/max box, * then the box centre with the largest live-vertex distance. An empty * section gets explicitly empty volumes, not the previous frame's. */ private writeBounds; } //# sourceMappingURL=section-chart.d.ts.map