CultLib/src/GameCult.Geometry/SURFACE-NETS-PROVENANCE.md
Meta Vens d64de1b69d fix: reject non-finite isoValue and origin in Extract
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>
2026-09-25 20:31:44 +02:00

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 isoValue or origin components are rejected, matching the existing rejection of non-finite samples and cell size;
  • an extreme but finite input (a cellSize near 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.