samples and cellSize were validated but isoValue and origin were not: iso = NaN or +inf silently returned an empty mesh, and origin = NaN gave NaN positions. Extend Extract's existing top-of-method validation authority to reject non-finite isoValue and non-finite origin components with ArgumentException, matching the existing style, with tests for each component. Overflow at extreme magnitudes (cellSize ~3e38, or finite samples whose interpolation overflows to inf) is a separate, float-domain limit rather than a validated-input error; documented in the XML doc and provenance notes, deliberately not range-checked. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2.7 KiB
2.7 KiB
Surface nets extraction provenance
CultGeometrySurfaceNets is a clean implementation of surface nets over a
regular scalar grid. It is implemented fresh from the published algorithm
description; no code or lookup tables were copied from any source. CultGeometryQuadNormals
is likewise a fresh implementation of face-weighted vertex normal averaging.
Cited literature:
- S. F. F. Gibson, "Constrained Elastic Surface Nets: Generating Smooth Surfaces from Binary Segmented Data," MICCAI 1998 (MERL Technical Report TR99-24).
- M. Lysenko, "Smooth Voxel Terrain (Part 2)," 0fps.net, 2012.
- N. Max, "Weights for Computing Vertex Normals from Facet Normals," Journal of Graphics Tools 4(2), 1999.
Deliberate behavior:
- a scalar sample less than or equal to the isovalue is inside, matching
CultGeometryIsoSurface's sign convention; - a cell's vertex is the mean of the linearly interpolated crossings of its own twelve edges;
- Gibson's elastic relaxation of the vertex toward its neighbors is omitted; the vertex stays at the unrelaxed dual-cell mean;
- one quad is emitted per crossing interior grid edge, from the (up to) four cells that share it; an edge whose four surrounding cells do not all exist emits no quad, leaving the mesh open at the field boundary;
- quad winding is decided by which of the crossing edge's two endpoints is
inside, times the per-axis handedness table (
OrientationSign:+1, -1, +1), never by a per-quad geometric test: axis 1's perpendicular pair (X, Z) is left-handed, so its sign is mirrored relative to axes 0 and 2. On a non-degenerate (non-zero-area) quad this decision has the outward-facing property as a corollary: the diagonal cross product of the quad has a positive dot product with the direction from the edge's inside endpoint to its outside endpoint. A degenerate quad carries no such geometric property to check, but still orients consistently with its neighbors because the rule never looks at its geometry; - non-finite
isoValueororigincomponents are rejected, matching the existing rejection of non-finite samples and cell size; - an extreme but finite input (a
cellSizenear float's magnitude limit, or finite samples whose interpolated position overflows to infinity) is a float-domain overflow, not a validated input error, and is deliberately not range-checked; - vertices are welded: one vertex per active cell, shared by every quad touching that cell;
- face-weighted normals sum each quad's diagonal cross product (unnormalized,
so a bigger quad outweighs a sliver) per touched vertex, then normalize; a
vertex touched only by zero-area quads gets a zero normal rather than NaN,
matching
CultGeometryIsoSurface's degenerate-normal rule.