import { CellComplex, type HyperplaneSliceN, type SectionSimplexGroupNResultN, TransformN } from '@holotope/core'; import { type MassProperties4 } from './mass-properties4.js'; import { RigidBody4 } from './rigid-body4.js'; /** * A uniform solid ball in R4, described the way {@link GlomeCollider4} is. * * A ball has no preferred axes, so nothing about its section depends on the * body's rotation; that invariance is a property of the shape rather than a * special case in the code. */ export interface GlomeSectionSource4 { /** Selects the ball family when the source is dispatched on. */ readonly kind: 'glome'; /** Radius in the body's own units. */ readonly radius: number; } /** * A solid hyperbox in R4, described the way {@link HyperboxCollider4} is — * except that the half-extents are read **in the order they were authored**. * * A collider has to be handed its half-extents in the body's principal order, * because `RigidBody4.fromMassProperties` adopts the principal rotor as the * body's rotation. Nothing here asks that of a caller: the authored axes are * reconciled against the mass-property frame internally. */ export interface HyperboxSectionSource4 { /** Selects the box family when the source is dispatched on. */ readonly kind: 'hyperbox'; /** Positive half-extents in the caller's own axes, in any order. */ readonly halfExtents: ArrayLike; /** * The mass properties whose principal frame the body's rotation came from. * * Omit it and the frame is recomputed from the half-extents. That * recomputation is deterministic *and always canonical*: a box's covariance * is exactly diagonal, so the eigensolver performs no rotations and returns a * signed permutation every time. It therefore reproduces what * `RigidBody4.fromMassProperties` used — but only because that body was posed * from the same canonical frame. * * Supply this when the body's rotation came from somewhere else — a different * signed permutation of the box's axes, say. Only the frame the body actually * carries cancels against the pose. * * What is accepted is narrower than "any valid principal basis". A rotation * inside a degenerate inertia eigenspace is a valid inertia basis and is not * something a {@link HyperboxCollider4} at the default identity local * transform can follow, so a section taken in one would part company with the * collider sharing its body. Accepted frames are the signed permutations that * sort these half-extents; where extents tie, 90° turns of the tied pair are * among them and 45° turns are not. Express a source that needs an arbitrary * frame as a {@link ComplexSectionSource4}, whose authored coordinates carry * the geometry this record cannot. */ readonly massProperties?: MassProperties4; } /** * An authored 4D complex, sectioned through its own tetrahedral cells. * * The complex is never mutated: the pose is applied into a private buffer, so * the authored positions remain the authority across any number of sections. */ export interface ComplexSectionSource4 { /** Selects the authored-complex family when the source is dispatched on. */ readonly kind: 'complex'; /** The authoritative source. Its positions are read, never written. */ readonly complex: CellComplex; /** * Which tetrahedral group to section. Defaults to the first 3-dimensional * group with four vertices per cell. */ readonly groupKey?: string; /** * The mass properties whose principal frame the body's rotation came from. * * Supply the same object that produced the body to skip re-integrating the * complex. Omit it and the frame is recomputed, which costs an integration * but cannot disagree with itself. */ readonly massProperties?: MassProperties4; } /** Any source this composition path can section. */ export type SectionSource4 = GlomeSectionSource4 | HyperboxSectionSource4 | ComplexSectionSource4; /** * Where a section came from, in the caller's own terms. * * The authored source is retained by reference rather than summarized, so a * consumer can answer "which object is this?" without an identifier anyone had * to invent. Renderer-side ids are a consumer's business and never appear here. */ export interface SectionProvenance4 { /** The authored source, exactly as it was passed in. */ readonly source: SectionSource4; /** The world pose the section was taken at. */ readonly pose: TransformN; /** The hyperplane and chart the section is expressed in. */ readonly slice: HyperplaneSliceN; /** * The composed authored-source-to-world map that was applied, once. * * For a hyperbox and a complex this is `pose ∘ (source → centred principal)`; * for a glome it is the pose itself. Applying it to an authored point * reproduces the world point the section was computed from. */ readonly worldFromSource: TransformN; } /** * The hyperplane misses the source entirely. * * Nothing but the provenance comes back, because there is no geometry to * describe. How far away the source is, and on which side, is recoverable from * `provenance.worldFromSource` and the slice. */ export interface EmptySection4 { /** Narrows a {@link Section4} to the case where nothing was intersected. */ readonly status: 'empty'; /** The source, pose and hyperplane that produced this answer. */ readonly provenance: SectionProvenance4; } /** * The hyperplane touches the source in a set with no volume in the chart — a * single point, or an edge — so there is a contact but no solid to draw. */ export interface TangentSection4 { /** Narrows a {@link Section4} to the case where contact has no volume. */ readonly status: 'tangent'; /** The source, pose and hyperplane that produced this answer. */ readonly provenance: SectionProvenance4; /** Packed R3 chart coordinates of the touched points. */ readonly chartPositions: Float64Array; } /** A glome's section: a 3-ball, exactly. */ export interface BallSection4 { /** Narrows a {@link Section4} to a glome's exact 3-ball section. */ readonly status: 'ball'; /** The source, pose and hyperplane that produced this answer. */ readonly provenance: SectionProvenance4; /** Centre of the section ball, in R3 chart coordinates. */ readonly chartCenter: Float64Array; /** Radius of the section ball. */ readonly radius: number; /** Signed distance from the hyperplane to the glome's world centre. */ readonly signedDistance: number; } /** * A polyhedral section: the triangulated surface of the 3D solid, with the * source lineage the released section machinery already carries. */ export interface PolyhedralSection4 { /** Narrows a {@link Section4} to a section with a drawable surface. */ readonly status: 'polyhedral'; /** The source, pose and hyperplane that produced this answer. */ readonly provenance: SectionProvenance4; /** * The released section result: chart positions, cells, `parentCells` naming * the contributing source cell of each primitive, and `lineage` naming the * source vertices and barycentric weights behind each section vertex. */ readonly section: SectionSimplexGroupNResultN; } /** Every shape a section of an R4 source can take. */ export type Section4 = EmptySection4 | TangentSection4 | BallSection4 | PolyhedralSection4; /** How to place a source in the world and where to cut it. */ export interface Section4Options { /** The hyperplane and the R3 chart the result is expressed in. */ readonly slice: HyperplaneSliceN; /** The body whose current pose places the source. Mutually exclusive with `pose`. */ readonly body?: RigidBody4; /** * An explicit pose, for sources no rigid body carries. * * This stands in for a body's pose, so it maps the source's **centred * principal frame** into the world — not the authored frame. The identity * therefore places a box in its principal axes (half-extents ascending) and * moves an off-centre complex onto the origin, exactly as an unpositioned * body would. To place an authored box in its own axes, pass its * `principalRotor` as the rotation — or use a body, which carries it already. */ readonly pose?: TransformN; /** * Classification tolerance, as an absolute world length. Default `1e-9`. * * On a box or a complex it is a distance from the hyperplane: a vertex within * `epsilon` of the plane counts as lying on it. On a glome it is applied to * the grazing depth `|d| − r` instead, which makes the band asymmetric in the * section's own radius — a ball is still called tangent at the inner edge of * the band, where its section radius is `√(2rε − ε²)`, or `√(2rε)` to the * precision that matters here. At `r = 1.3` and the default `ε = 1e-9` that * is a section of radius about 5.1e-5, not 1e-9. * * Because it is absolute rather than relative, a source authored at a very * large or very small scale should set it. */ readonly epsilon?: number; } /** * Exact 3D section of a moving uniform solid ball in R4. * * The section of a glome by a hyperplane is a 3-ball whose centre is the * orthogonal projection of the glome's centre into the chart, and whose radius * is `√(r² − d²)` for `d` the signed distance from the plane to that centre. * Only the centre moves, so the result is invariant under any rotation the body * carries — a property of the sphere, not a branch in this function. * * @example * ```ts * const body = RigidBody4.fromMassProperties(massPropertiesOfGlome4(1.3)); * const slice = HyperplaneSliceN.axisAligned(4, 3, 0.25); * const section = sectionOfGlome4({ kind: 'glome', radius: 1.3 }, { body, slice }); * if (section.status === 'ball') { * section.radius; // √(1.3² − 0.25²) * } * ``` */ export declare function sectionOfGlome4(source: GlomeSectionSource4, options: Section4Options): BallSection4 | TangentSection4 | EmptySection4; /** * Exact 3D section of a moving uniform solid hyperbox in R4. * * Half-extents are read in the axes the caller authored them in. The frame the * body actually carries is the principal one, so the authored axes are * reconciled through `massPropertiesOfHyperbox4`'s own frame and composed with * the pose into a single map, applied once. A caller never sorts extents, * rebases vertices or composes a principal rotor to get the right answer. * * The box is sectioned through its tetrahedralized 3-boundary, so the result is * the triangulated surface of the section solid and carries the same lineage * any other complex section does. * * @example * ```ts * const authored = [3.5, 2, 1.25, 0.5]; // unsorted on purpose * const body = RigidBody4.fromMassProperties(massPropertiesOfHyperbox4(authored)); * const section = sectionOfHyperbox4( * { kind: 'hyperbox', halfExtents: authored }, * { body, slice: HyperplaneSliceN.axisAligned(4, 3, 0) } * ); * ``` */ export declare function sectionOfHyperbox4(source: HyperboxSectionSource4, options: Section4Options): PolyhedralSection4 | TangentSection4 | EmptySection4; /** * Exact 3D section of a moving authored 4D complex. * * The complex's own positions stay authoritative: the body pose composed with * the source-to-principal frame is applied into a private buffer, once, and the * released section machinery does the rest — so `parentCells` and `lineage` * name the authored cells and vertices, not intermediate ones. * * @example * ```ts * const authoredComplex = tetrahedralizeCuboidCells( * createHyperrectangle({ dim: 4, edgeLengths: [1, 2.5, 4, 7], maxCellDimension: 3 }) * ); * const properties = massPropertiesFromCellComplex4(authoredComplex); * const complexBody = RigidBody4.fromMassProperties(properties); * const complexSection = sectionOfComplex4( * { kind: 'complex', complex: authoredComplex, massProperties: properties }, * { body: complexBody, slice: HyperplaneSliceN.axisAligned(4, 3, 0) } * ); * // Every drawn primitive names the authored cell it came from. * if (complexSection.status === 'polyhedral') { * complexSection.section.parentCells.length === complexSection.section.cellCount; * } * ``` */ export declare function sectionOfComplex4(source: ComplexSectionSource4, options: Section4Options): PolyhedralSection4 | TangentSection4 | EmptySection4; /** * Sections whichever source it is given, dispatching on the source's own kind. * * Use this where the source family is data rather than something the call site * knows; the three named entries are otherwise identical and better typed. */ export declare function sectionOfSource4(source: SectionSource4, options: Section4Options): Section4; //# sourceMappingURL=section4.d.ts.map