VTK  9.6.20260529
vtkSurfaceNets3DNonManifoldCases Class Reference

Provides utilities to identify vtkSurfaceNets3D's edge-cases that can lead to non-manifold output geometry, and to resolve them by creating duplicate points. More...

#include <vtkSurfaceNets3DNonManifoldCases.h>

Classes

struct  NonManifoldCaseMetadata
 For non-manifold edge cases: More...
 
struct  StateInfo
 Decoded metadata for a state: number of points to generate and the corresponding non-manifold table index. More...
 
struct  VoxelNeighborhood
 Holds the local configuration of the 8 voxels surrounding a triad point in the grid. More...
 

Public Types

enum  TableIndexValues : int8_t {
  EmptyIndex = -3 , ManifoldIndex = -2 , NonManifoldIndexUnsolvable = -1 , NonManifoldIndex0 = 0 ,
  NonManifoldIndex1 = 1 , NonManifoldIndex2 = 2 , NonManifoldIndex3 = 3 , NonManifoldIndex4 = 4 ,
  NonManifoldIndex5 = 5 , NonManifoldIndex6 = 6 , NonManifoldIndex7 = 7 , NonManifoldIndex8 = 8
}
 
using NonManifoldCaseType = unsigned char
 
using EdgeCaseType = unsigned short
 
using VoxelCaseType = unsigned char
 

Static Public Member Functions

template<class T>
static auto GetNonManifoldTableIndex (const VoxelNeighborhood< T > &voxelNeighborhood) -> int8_t
 Returns the index into the non-manifold metadata table for the given voxel neighborhood, or a special sentinel value if the neighborhood is manifold or unresolvable.
 
static VTK_ALWAYS_INLINE auto GetNumberOfNonManifoldCases (EdgeCaseType edgeCase) -> uint8_t
 Returns the number of non-manifold metadata entries for the given edge case.
 
static VTK_ALWAYS_INLINE auto GetNonManifoldCase (EdgeCaseType edgeCase, int8_t tableIndex) -> NonManifoldCaseType
 Given an edge case and the table index for the non-manifold case, returns the corresponding non-manifold case index, where 0 means manifold.
 
static VTK_ALWAYS_INLINE auto GetNonManifoldBasicVoxelCase (EdgeCaseType edgeCase, int8_t tableIndex) -> VoxelCaseType
 Given an edge case and the table index for the non-manifold case, returns the corresponding basic voxel case bitmask for the corresponding non-manifold configuration.
 
static VTK_ALWAYS_INLINE auto IsNonManifoldCaseHomogeneous (EdgeCaseType edgeCase, int8_t tableIndex) -> NonManifoldCaseType
 Given an edge case and the table index for the non-manifold case, returns whether it's homogeneous or not.
 
static constexpr VTK_ALWAYS_INLINE auto GetNumberOfPoints (NonManifoldCaseType nonManifoldCase) -> uint8_t
 Given the non-manifold case, returns the number of duplicate points.
 
template<uint8_t QuadId>
static VTK_ALWAYS_INLINE auto GetNonManiFoldPointIndex (EdgeCaseType edgeCase, int8_t tableIndex) -> int8_t
 Given a quad id (bits 0–3 for yz-plane quads, 4–7 for xz-plane, 8–11 for xy-plane), and the table index for the non-manifold case, returns the duplicate point index for that quad in the current non-manifold configuration.
 
static constexpr VTK_ALWAYS_INLINE auto GetNumberOfManifoldSubCases (NonManifoldCaseType nonManifoldCase) -> uint8_t
 Given the non-manifold case, returns the number of manifold sub-cases.
 
static VTK_ALWAYS_INLINE auto GetManifoldSubEdgeCases (EdgeCaseType edgeCase, int8_t tableIndex) -> const std::array< EdgeCaseType, 4 > &
 Returns the manifold sub-edge cases for a given edge case and table index.
 
static constexpr VTK_ALWAYS_INLINE auto ComputeState (uint8_t numPoints, int8_t tableIndex) -> uint8_t
 Computes a state code from a point count and table index using a variable-width linear mapping.
 
static constexpr VTK_ALWAYS_INLINE auto GetStateInfo (uint8_t state) -> StateInfo
 Decodes both the number of duplicate points and the metadata table index from a state code in a single table lookup.
 

Static Public Attributes

static constexpr uint8_t MaxTableIndex = 8
 

Detailed Description

Provides utilities to identify vtkSurfaceNets3D's edge-cases that can lead to non-manifold output geometry, and to resolve them by creating duplicate points.

Introduction

vtkSurfaceNets3D performs dual contouring on a 3D segmented multi-material image to generate a surface mesh using the Flying Edges approach. As the name implies, the Flying Edges approach goes through the edges of the input grid, instead of the voxels, to determine how the output geometry should be generated.

Assuming you are at the center of a 2x2x2 grid of voxels, an edge case has 12 bits describing which of the 12 possible faces between the voxels will be generated or not. A face is generated if a voxel either does not have a neighboring voxel in a particular direction (i.e., it has a voxel with a background label), or if it has a neighboring voxel with a different label. In the latter case, the generated face is shared by the two voxels, and is generated only once.

In contrast, the more traditional Marching Cubes performs contouring (not dual) and goes through the voxels of the input grid. As the name implies, it generates voxel cases, instead of edge cases, to determine how to generate the output geometry.

Assuming you are at the center of a 2x2x2 grid of voxels, a voxel case has 8 bits describing which of the 8 voxels are present in the case (i.e., have a non-background label).

Both the flying edges approach used by SurfaceNets and the marching cubes approach can lead to non-manifold output geometry for certain configurations of voxels.

Surface Meshes

A manifold surface mesh is one where every edge is shared by exactly two faces and every vertex has a single connected ring of faces around it, meaning the mesh locally resembles a flat disk at every point, with no holes, gaps, or self-intersections.

An open-manifold surface mesh relaxes this definition by allowing boundary edges — edges shared by only one face. It locally resembles a flat disk at interior vertices and a half-disk at boundary vertices.

Since vtkSurfaceNets3D does not cap boundary faces, voxels at the image boundary generate boundary edges, so the resulting surface mesh cannot be strictly manifold. The best we can do is attempt to improve local topology (towards open-manifold behavior) in regions where the local voxel/material configuration is solvable.

Furthermore, since vtkSurfaceNets3D generates faces shared between voxels of different non-background labels, any open-manifold objective is meaningful only per label region: if you extract the faces corresponding to a single label from the output mesh, that sub-mesh is what we try to keep open-manifold where possible. When a configuration cannot be resolved, this class reports it as such and vtkSurfaceNets3D may still produce non-manifold output.

cases

In "Parallel hexahedral meshing from volume fractions", Steven J. Owen, Matthew L. Staten, Marguerite C. Sorensen (https://doi.org/10.1007/s00366-012-0292-8), seven canonical voxel configurations (ignoring different orientations) are described for a 2×2×2 grid of voxels, which can lead to generating a non-manifold surface mesh.

Before describing these cases, let's first provide some mathematical background necessary to understand them and compute all their various rotated/reflected variants.

We work in a 3D binary grid: {0,1}^3 → There are exactly 8 possible coordinates: (x,y,z) with x,y,z ∈ {0,1}. → Each coordinate triple represents the position of a voxel in a 2×2×2 cube.

Hamming distance definition: d_H(a,b) = number of coordinates where a != b.

  • d_H = 1 → voxels share a face.
  • d_H = 2 → voxels touch at an edge or vertex (no shared face).
  • d_H = 3 → voxels are opposite corners of the cube.

For k chosen voxels:

  • There are C(k,2) voxel pairs.
  • We compute all pairwise Hamming distances.
  • Sort them → get a "distance signature".

The sorted distance signature is permutation-invariant and acts as a shape descriptor in the binary cube space. Each non-manifold case (case1...case11) is defined by a unique signature:


Case 1: Two voxels are at opposite corners (body diagonal). Example: voxels at (0,0,0) and (1,1,1).

Pseudo-3D view: · · · ■ (back layer z=1) ■ · (front layer z=0) · ·

Voxel-configurations in the binary grid: 4.

Reason: 2 voxels, signature [3]

Case 2: Two voxels are diagonally adjacent across a face (no shared edge). Example: voxels at (0,0,0) and (1,0,1).

Pseudo-3D view: · · ■ · ■ · · ·

Voxel-configurations in the binary grid = 12.

Reason: 2 voxels, signature [2]

Case 3: Three voxels where, one pair shares a face, one pair shares only a vertex, and one pair is opposite corners. Shape looks like an "L" but spans entire cube. Example: voxels at (0,0,0), (1,0,0), and (1,1,1).

Pseudo-3D view: · · · ■ ■ ■ · ·

Voxel-configurations in the binary grid = 24.

Reason: 3 voxels, signature [1, 2, 3]

Case 4: Three voxels arranged so that all three share a common edge, but no common face, i.e. Forms an equilateral triangle in the cube. Example: voxels at (0,0,1), (1,0,0), and (0,1,0).

Pseudo-3D view: ■ · · · · ■ ■ ·

Voxel-configurations in the binary grid = 8.

Reason: 3 voxels, signature [2, 2, 2]

Case 5: Four voxels with two face-adjacent pairs, two vertex-adjacent pairs, and two body diagonals. Shape is like a bent rectangle spanning the cube. Example: voxels at (0,0,0), (0,0,1), (1,1,0), and (1,1,1).

Pseudo-3D view: ■ · · ■ ■ · · ■

Voxel-configurations in the binary grid = 6.

Reason: 4 voxels, signature [1, 1, 2, 2, 3, 3]

Case 6: Four voxels where three lie in the same horizontal layer and are face-adjacent in an L-shape, and the fourth is above one corner, touching the others only along edges. Example: voxels at (0,0,1), (1,0,0), (0,1,0), and (1,1,0).

Pseudo-3D view: ■ · · · · ■ ■ ■

Voxel-configurations = 24.

Reason: 4 voxels, signature [1, 1, 2, 2, 2, 3]

Case 7: Four voxels placed so each shares exactly three edges with the others, forming a twisted diagonal pattern through the grid. Example: voxels at (0,0,1), (1,0,0), (0,1,0), and (1,1,1).

Pseudo-3D view: ■ · · ■ · ■ ■ ·

Voxel-configurations = 2.

Reason: 4 voxels, signature [2, 2, 2, 2, 2, 2]

We also have the complementary cases of non-manifold cases 1-4, which are not mentioned in the paper but can be derived by inverting the binary grid (i.e., swapping filled and empty voxels).


Case 8: Complement of Case 4. Five voxels fill the cube leaving three empty voxels that form an equilateral triangle (all pairwise distances equal 2). The three empty voxels each touch only diagonally, creating non-manifold edges throughout the surface. Example: all voxels except (0,0,1),(1,0,0),(0,1,0), i.e. (0,0,0),(0,1,1),(1,0,1),(1,1,0),(1,1,1).

Pseudo-3D view: ■ ■ · ■ (back layer z=1) · ■ (front layer z=0) ■ ·

Voxel-configurations in the binary grid: 8.

Reason: 5 voxels, signature [1, 1, 1, 2, 2, 2, 2, 2, 2, 3]

Case 9: Complement of Case 3. Five voxels fill the cube leaving three empty voxels whose distances are [1,2,3] — one face-adjacent pair, one edge-diagonal pair, and one body-diagonal pair. The empty region spans the full cube, creating a non-manifold edge. Example: all voxels except (0,0,0),(1,0,0),(1,1,1), i.e. (0,0,1),(0,1,0),(0,1,1),(1,0,1),(1,1,0).

Pseudo-3D view: ■ · ■ ■ (back layer z=1) ■ ■ (front layer z=0) · ·

Voxel-configurations in the binary grid: 24.

Reason: 5 voxels, signature [1, 1, 1, 1, 2, 2, 2, 2, 3, 3]

Case 10: Complement of Case 2. Six voxels fill the cube leaving two face-diagonal voxels empty. The two empty voxels touch only at an edge, creating a non-manifold edge where four faces meet. Example: all voxels except (0,0,0) and (1,0,1), i.e. (0,0,1),(0,1,0),(0,1,1),(1,0,0),(1,1,0),(1,1,1).

Pseudo-3D view: ■ ■ ■ · (back layer z=1) ■ ■ (front layer z=0) · ■

Voxel-configurations in the binary grid: 12.

Reason: 6 voxels, signature [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3]

Case 11: Complement of Case 1. Six voxels fill the cube leaving two opposite corners (body diagonal) empty. The two empty voxels are at opposite corners, so the surface pinches at two vertices forming a non-manifold bowtie through the solid. Example: all voxels except (0,0,0) and (1,1,1), i.e. (0,0,1),(0,1,0),(0,1,1),(1,0,0),(1,0,1),(1,1,0).

Pseudo-3D view: ■ · ■ ■ (back layer z=1) ■ ■ (front layer z=0) · ■

Voxel-configurations in the binary grid: 4.

Reason: 6 voxels, signature [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3]

Configurations with Non-Manifold Material Variations

Beyond the 11 non-manifold cases above, there exist 10 additional voxel configurations that are geometrically manifold when all active voxels share the same material, but can produce non-manifold edge cases when two or more active voxels carry different material labels. These are referred to as manifold cases 12-21.


Case 12: Single voxel — only one active voxel in the cube. Example: voxel at (0,0,0).

Pseudo-3D view: · · · · (back layer z=1) ■ · (front layer z=0) · ·

Voxel-configurations in the binary grid: 8.

Reason: 1 voxel, signature []

Case 13: Two face-adjacent voxels. Example: voxels at (0,0,0) and (1,0,0).

Pseudo-3D view: · · · · (back layer z=1) ■ ■ (front layer z=0) · ·

Voxel-configurations in the binary grid: 12.

Reason: 2 voxels, signature [1]

Case 14: Three voxels in a gamma (L) shape — two face-adjacent pairs sharing a common voxel, with the two end voxels edge-adjacent. Example: voxels at (0,0,0), (1,0,0), and (0,1,0).

Pseudo-3D view: · · · · (back layer z=1) ■ ■ (front layer z=0) ■ ·

Voxel-configurations in the binary grid: 24.

Reason: 3 voxels, signature [1, 1, 2]

Case 15: Four voxels forming a 2x2 square in one plane. Example: voxels at (0,0,0), (1,0,0), (0,1,0), and (1,1,0).

Pseudo-3D view: · · · · (back layer z=1) ■ ■ (front layer z=0) ■ ■

Voxel-configurations in the binary grid: 6.

Reason: 4 voxels, signature [1, 1, 1, 1, 2, 2]

Case 16: Four voxels in a T-shape — three face-adjacent in a row, with the fourth attached face-adjacent to the middle one. Example: voxels at (0,0,0), (1,0,0), (0,1,0), and (0,0,1).

Pseudo-3D view: ■ · · · (back layer z=1) ■ ■ (front layer z=0) ■ ·

Voxel-configurations in the binary grid: 24.

Reason: 4 voxels, signature [1, 1, 1, 2, 2, 2]

Case 17: Four voxels in a zigzag chain — three sequential face-adjacent pairs forming a 3D chain. Example: voxels at (0,0,0), (1,0,0), (1,1,0), and (1,1,1).

Pseudo-3D view: · · · ■ (back layer z=1) ■ ■ (front layer z=0) · ■

Voxel-configurations in the binary grid: 24.

Reason: 4 voxels, signature [1, 1, 1, 2, 2, 3]

Case 18: Five voxels — four forming a 2x2 square in one plane, with the fifth attached face-adjacent to one corner of the square. Example: voxels at (0,0,0),(1,0,0),(0,1,0),(1,1,0),(0,0,1).

Pseudo-3D view: ■ · · · (back layer z=1) ■ ■ (front layer z=0) ■ ■

Voxel-configurations in the binary grid: 24.

Reason: 5 voxels, signature [1, 1, 1, 2, 2, 2, 2, 2, 2, 3]

Case 19: Six voxels forming an L-shape spanning two layers. Example: voxels at (0,0,0),(1,0,0),(0,1,0),(1,1,0),(0,0,1),(1,0,1).

Pseudo-3D view: ■ ■ · · (back layer z=1) ■ ■ (front layer z=0) ■ ■

Voxel-configurations in the binary grid: 24.

Reason: 6 voxels, signature [1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3]

Case 20: Seven voxels — all eight corners of the cube except one. Example: all voxels except (1,1,1).

Pseudo-3D view: ■ ■ ■ · (back layer z=1) ■ ■ (front layer z=0) ■ ■

Voxel-configurations in the binary grid: 8.

Reason: 7 voxels, signature [1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3]

Case 21: Eight voxels — all eight corners of the cube active. Example: all voxels at (0,0,0) through (1,1,1).

Pseudo-3D view: ■ ■ ■ ■ (back layer z=1) ■ ■ (front layer z=0) ■ ■

Voxel-configurations in the binary grid: 1.

Reason: 8 voxels, signature [1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3]

Non-manifold cases 1-11 can produce non-manifold configurations in two ways:

  1. When they are homogeneous (all active voxels share the same material) — the geometric pinch point of the non-manifold case itself creates a non-manifold junction.
  2. When they are non-homogeneous (active voxels carry different material labels) — a sub-voxel configuration within them matches one of the 11 non-manifold cases (1-11), creating a material-driven non-manifold situation within the larger configuration.

The same applies to manifold cases 12-21: even though they are geometrically manifold, non-homogeneous material assignments can create sub-voxel configurations that match one of the 11 non-manifold cases, producing a non-manifold edge case.

and terminology: face-connected components, splits, and complements

This class resolves non-manifold output by duplicating the point generated at a triad and assigning the faces around that triad to different duplicate points. The key question is: for a given local configuration, which faces should be grouped together (share the same point) to improve local topology (ideally open-manifold per label region) when the configuration is solvable?

We use the following terminology:

  • Face-connected component (or simply component): a set of active voxels in the 2×2×2 cube that are connected through face adjacency (Hamming distance 1). Each component corresponds to one manifold sub-configuration and typically requires its own duplicate point.
  • Split pattern: a particular assignment of the 12 possible faces (the 12 edge-case bits) to component indices. In the tables this is stored as PointIndices[12] plus the corresponding SubVoxelCases/SubEdgeCases.
  • Normal/Primary split: the split pattern obtained from a given basic voxel configuration by grouping its active voxels into face-connected components (Hamming distance 1) and assigning each generated face to the component that produced it.

    Not every case has a normal/primary split:

    • Cases 1–8 have a normal split.
    • Cases 9–11 do not have a normal split (they are complements of cases 1–3).
    • Cases 12–21 are geometrically manifold and do not define normal/complementary splits at this level; they only become relevant through detected non-manifold sub-configurations.
  • Complementary split: some non-manifold situations have a complementary split via its complementary voxel configuration (the occupancy complement inside the 2×2×2 cube).

    Complementary splits are not always available:

    • Cases 1–3 do not have a complementary split.
    • Cases 4–11 do have a complementary split.
    • Cases 9–11 have only the complementary split (no normal split).
    • Cases 12–21 do not have a complementary split at this level.

Important: a voxel configuration and its paired complementary configuration can map to the same 12-bit edge case. Since the lookup table is keyed by edge case, both split patterns may appear in the same metadata span for that edge case as separate entries. In practice, such pairs are usually stored as the first two entries for the edge case (table indices 0 and 1), and the complementary split is selected by swapping between them.

The complementary split matters mainly for homogeneous material assignments: if all active voxels belong to the same label region, using the complementary split can avoid creating artificial separations inside that region while still resolving the non-manifold topology.

non-manifold metadata (CreateNonManifoldMetaDataPerEdgeCase)

vtkSurfaceNets3D’s “flying edges” traversal produces, at each triad point, a compact local description of the 2×2×2 voxel neighborhood:

  • An edge case: a 12-bit mask describing which of the 12 inter-voxel faces are generated.
  • A voxel case: an 8-bit mask describing which of the 8 voxel corners are active (non-background).
  • The material labels at the 8 corners.

The same edge case can arise from many different voxel cases and many different material assignments, and only some of those assignments are actually non-manifold. To make runtime resolution fast, this class precomputes a sparse lookup table that, for each edge case that can be non-manifold, provides one or more candidate entries.

Conceptually, each candidate entry corresponds to a basic voxel configuration (and possibly a material-driven variation) and provides a split pattern: how to assign generated faces to duplicate point indices, plus per-component sub-configurations used for manifold processing and smoothing.

Table generation (high level):

  1. Enumerate all rotated/reflected variants of cases 1–21 and compute their basic voxel masks.
  2. For each voxel configuration, enumerate material variations ("color encodings") and compute the resulting edge case; keep only variations that can yield a non-manifold edge case per material assignment.
  3. For each kept variation, split the 2×2×2 active voxels into face-connected components (Hamming distance 1 connectivity) and record the resulting duplication pattern.

The result is sparse: most of the 4096 theoretical 12-bit edge cases are either geometrically unreachable or always manifold, and therefore have no metadata entries.

Sparsity statistics (informational):

  • Out of the 4096 theoretically possible 12-bit edge cases, only 1097 are geometrically reachable from any voxel configuration.
  • Out of the geometrically reachable configurations, only 54.4% (564/1097) can be non-manifold for some material assignment.
  • The remaining are always treated as manifold and therefore have no metadata entries.

Table-index outcome statistics (informational):

  • Counts like "how often ManifoldIndex vs NonManifoldIndexUnsolvable occurs" depend on how you enumerate material variations. The exhaustive enumeration used for internal testing treats many highly irregular material assignments as equally likely. In typical segmented images, labels tend to be spatially coherent, so many of these exotic permutations are unlikely in practice.
  • These numbers should not be interpreted as a probability distribution for real images; they primarily quantify the behavior under an exhaustive combinatorial enumeration.
  • If we perform an exhaustive search of all the possible table indices that could result from all voxel configurations and their colorings, the following counts were observed: Table index -2 (ManifoldIndex): 5784 Table index -1 (NonManifoldIndexUnsolvable): 14174 Table index 0: 884 Table index 1: 256 Table index 2-9: 6 each

lookup and solvability (GetNonManifoldTableIndex)

At runtime, GetNonManifoldTableIndex() decides whether the neighborhood is:

  • Manifold (ManifoldIndex) → no point duplication is needed.
  • Solvable non-manifold (returns a non-negative table index) → use the selected metadata entry to duplicate points and generate per-component sub-cases.
  • Non-manifold but unresolvable (NonManifoldIndexUnsolvable) → the material assignment creates a non-manifold junction that cannot be eliminated using the available split patterns.

The returned table index is a local indicator for a single triad neighborhood. Whether the final surface contains an observable non-manifold edge/vertex can depend on how neighboring triads are resolved. In particular, a neighborhood that is marked as unresolvable may still not create a non-manifold artifact in the final mesh if adjacent neighborhoods introduce compatible point duplications that avoid the problematic connectivity.

A practical sufficient condition for full resolvability is local homogeneity: if, for every triad neighborhood, all active voxels share the same label (for example, a single-material segmentation, or multiple materials that never meet at a triad), then GetNonManifoldTableIndex() will never return NonManifoldIndexUnsolvable. In that scenario, all detected non-manifold situations are handled through the geometric split patterns, and the resulting surface for each label region is guaranteed to be open-manifold. If label regions are disjoint (do not share faces), the full output mesh is likewise open-manifold as a disjoint union of those regions.

Conceptually, the runtime decision has three stages:

  1. Fast geometric prefilter: if the edge case has no metadata entries, it is guaranteed manifold.
  2. Material-aware classification: the corner labels are canonicalized into a color-encoded voxel case (labels on active corners only), which is used to decide whether the actual material configuration is non-manifold. (An edge case may have multiple metadata entries; entries are first filtered by the exact 8-bit voxel-case bitmask before any material-dependent decision is applied.)
  3. Select a split pattern:
    • If the configuration is homogeneous and the table provides an alternate (complementary) grouping for the same geometric situation, the lookup may flip to that alternate split to preserve contiguous same-material flow through the junction.
    • For multi-material configurations, the lookup may resolve the non-manifoldness via a detected sub-voxel non-manifold case, but only when that sub-case has a complementary split available; otherwise the configuration is reported as unresolvable.

In terms of “when is a configuration solvable?”, the implementation applies these rules:

  • If the edge case has no metadata entries → manifold.
  • If the material-aware classification says the current label assignment is manifold → manifold.
  • For homogeneous non-manifold configurations: if the metadata table provides both a primary and a complementary grouping (typically stored as indices 0 and 1 for that edge case), the complementary grouping is preferred to avoid artificial splits inside one material region.
  • For multi-material configurations: a split is accepted only if it eliminates the non-manifold junction for the given material arrangement. In practice this requires either (a) a detected sub-configuration with a complementary split available, or (b) a primary split whose components do not contain unresolvable sub-non-manifold patterns (cases 1–3), as checked by IsSplitSufficientForColorEncodedVoxelCase().
  • For geometrically manifold cases (12–21): the configuration is only solvable when its non-manifoldness is caused by a solvable sub-configuration (with a complementary split).

Note: when VTK_SURFACE_NETS_3D_ALWAYS_SPLIT_CASES_1_3 is enabled, cases 1–3 are treated as non-manifold and are always split regardless of material labels (these patterns are typically interpreted as sampling artifacts without a meaningful continuous analogue).

a resolved entry (duplicate points, face assignment, and sub-cases)

Once a non-negative table index has been selected, the corresponding metadata entry provides:

  • The duplicate-point assignment for each generated face via PointIndices[12].
  • The manifold sub-cases (SubVoxelCases / SubEdgeCases) for processing each component independently.

Only points are duplicated (faces are not): the intent is to improve local topology per label region without duplicating geometry across labels. Duplicating faces would replicate the same geometric face into multiple label regions. By duplicating points instead, label regions can be separated topologically while shared faces between different labels remain geometrically coincident.

of Smoothing Stencils for Non-Manifold Edge Cases

Given that we can resolve a subset of non-manifold neighborhoods by splitting them into manifold sub-configurations, the next step is to apply smoothing to improve the geometric quality of the output surface. For each point in the output mesh, a smoothing stencil defines which neighboring points it should be averaged with to compute its smoothed position.

To resolve a non-manifold edge case, we split its voxel configuration into face-connected groups, yielding sub-configurations each with their own sub edge case and sub voxel case. Since each sub-configuration is manifold, it can be processed independently to generate a well-defined smoothing stencil for its corresponding duplicate point.

For non-manifold edge cases, we generate stencils for two kinds of points:

  • Each duplicate point, using its corresponding manifold sub edge case.
  • The non-manifold point itself, using the non-manifold edge case.

The stencil for the non-manifold point is needed because, when a neighboring point's stencil needs to connect to a duplicated point, rather than arbitrarily choosing one duplicate, it connects to the non-manifold point whose position is averaged by points included in all sub-configurations. This ensures that the smoothing operation is consistent across groups and keeps all duplicate points geometrically close to each other after smoothing.

(Material-aware split selection is part of the GetNonManifoldTableIndex() decision: when the labels for the basic voxel case are homogeneous and an alternate grouping exists in the metadata table, the lookup can flip to that alternate split to avoid introducing artificial separations inside a single material region.)

Definition at line 603 of file vtkSurfaceNets3DNonManifoldCases.h.

Member Typedef Documentation

◆ NonManifoldCaseType

◆ EdgeCaseType

◆ VoxelCaseType

Member Enumeration Documentation

◆ TableIndexValues

Enumerator
EmptyIndex 
ManifoldIndex 
NonManifoldIndexUnsolvable 
NonManifoldIndex0 
NonManifoldIndex1 
NonManifoldIndex2 
NonManifoldIndex3 
NonManifoldIndex4 
NonManifoldIndex5 
NonManifoldIndex6 
NonManifoldIndex7 
NonManifoldIndex8 

Definition at line 656 of file vtkSurfaceNets3DNonManifoldCases.h.

Member Function Documentation

◆ GetNonManifoldTableIndex()

template<class T>
static auto vtkSurfaceNets3DNonManifoldCases::GetNonManifoldTableIndex ( const VoxelNeighborhood< T > & voxelNeighborhood) -> int8_t
static

Returns the index into the non-manifold metadata table for the given voxel neighborhood, or a special sentinel value if the neighborhood is manifold or unresolvable.

The lookup proceeds as follows:

  1. The edge case is checked against the precomputed metadata table. If no entries exist for this edge case, ManifoldIndex is returned immediately.
  2. The material labels at the active corners are canonicalized into a color-encoded voxel case and checked against the non-manifold case tables to determine whether the actual material configuration is truly non-manifold. This is necessary because the same edge case can arise from multiple material configurations, some of which are manifold and some non-manifold. If the configuration is manifold, ManifoldIndex is returned.
  3. For each non-manifold entry compatible with the actual voxel case, the correct table index is determined based on the following decision tree:
    1. If the voxel config matches one of the 11 non-manifold cases: a. If cases 1-3 (VTK_SURFACE_NETS_3D_ALWAYS_SPLIT_CASES_1_3): → always return table index 0 which represents the normal/primary split. b. If the configuration is homogeneous (all active voxels share the same material): i. If a complementary split exists (cases 4-11): → return the complementary split (swap between the paired entries, typically table indices 0 and 1) to support contiguous same-material flow. ii. Else if a normal/primary split exists (cases 1-8): → return the normal/primary split for the basic voxel case. c. Else if a sub-voxel non-manifold configuration exists: i. If the sub-voxel edge case equals the current edge case: → if a complementary split of the sub-voxel exists (cases 4-11): return the complementary split of the sub-voxel case. ii. Else if the sub-voxel edge case is a subset of the current edge case: → if normal splits exist for both sub-voxel (cases 1-8) and basic (cases 1-8):

      • if the split is sufficient for the material configuration (IsSplitSufficientForColorEncodedVoxelCase), return the normal split of the basic voxel case. For non-manifold case 8, which can have up to 9 valid table entries, the index is capped at MaxTableIndex unless VTK_SURFACE_NETS_3D_SUPPORT_ALL_SOLVABLE_VARIATIONS_FOR_CASE_8 is defined — entries beyond MaxTableIndex are treated as unsolvable by default to limit complexity.
      • otherwise return NonManifoldIndexUnsolvable. → otherwise return NonManifoldIndexUnsolvable.

      Notes:

      • Cases 9-11 have no normal/primary split; they are resolved using the complementary split patterns of their paired configurations.
    2. If the voxel config matches one of the manifold cases (cases 12+): a. These cases are non-manifold only under non-homogeneous material assignments. If a sub-voxel non-manifold configuration exists: i. If the sub-voxel edge case equals the current edge case: → if a complementary split of the sub-voxel exists (cases 4-11): return the complementary split of the sub-voxel case. → otherwise return NonManifoldIndexUnsolvable.
Template Parameters
TThe type of the material labels (e.g., uint8_t).
Parameters
voxelNeighborhoodThe voxel neighborhood containing the edge case, voxel case bitmask, and material labels at the 8 cube corners.
Returns
The 0-based index into the non-manifold metadata table if a compatible non-manifold configuration is found and resolved, ManifoldIndex if the configuration is manifold, or NonManifoldIndexUnsolvable if the configuration is non-manifold but could not be resolved for the given material configuration.

◆ GetNumberOfNonManifoldCases()

static VTK_ALWAYS_INLINE auto vtkSurfaceNets3DNonManifoldCases::GetNumberOfNonManifoldCases ( EdgeCaseType edgeCase) -> uint8_t
inlinestatic

Returns the number of non-manifold metadata entries for the given edge case.

Returns 0 if the edge case has no non-manifold configurations.

Parameters
edgeCaseThe 12-bit edge case to query.
Returns
The number of metadata entries for this edge case. Note that this value is unrelated to MaxTableIndex (which limits how many table indices are encoded in the triad state for certain configurations).

Definition at line 750 of file vtkSurfaceNets3DNonManifoldCases.h.

◆ GetNonManifoldCase()

static VTK_ALWAYS_INLINE auto vtkSurfaceNets3DNonManifoldCases::GetNonManifoldCase ( EdgeCaseType edgeCase,
int8_t tableIndex ) -> NonManifoldCaseType
inlinestatic

Given an edge case and the table index for the non-manifold case, returns the corresponding non-manifold case index, where 0 means manifold.

Parameters
edgeCaseA 12-bit edge case bitmask.
tableIndexThe index in the non-manifold cases table for the given edge case
Returns
The case index in [0, 21], where 0 means manifold.

Definition at line 763 of file vtkSurfaceNets3DNonManifoldCases.h.

◆ GetNonManifoldBasicVoxelCase()

static VTK_ALWAYS_INLINE auto vtkSurfaceNets3DNonManifoldCases::GetNonManifoldBasicVoxelCase ( EdgeCaseType edgeCase,
int8_t tableIndex ) -> VoxelCaseType
inlinestatic

Given an edge case and the table index for the non-manifold case, returns the corresponding basic voxel case bitmask for the corresponding non-manifold configuration.

The basic voxel case is an 8-bit bitmask where each bit indicates whether the corresponding voxel corner is present in the non-manifold configuration.

Parameters
edgeCaseA 12-bit edge case bitmask.
tableIndexThe index in the non-manifold cases table for the given edge case
Returns
The basic voxel case bitmask in [0, 255].

Definition at line 783 of file vtkSurfaceNets3DNonManifoldCases.h.

◆ IsNonManifoldCaseHomogeneous()

static VTK_ALWAYS_INLINE auto vtkSurfaceNets3DNonManifoldCases::IsNonManifoldCaseHomogeneous ( EdgeCaseType edgeCase,
int8_t tableIndex ) -> NonManifoldCaseType
inlinestatic

Given an edge case and the table index for the non-manifold case, returns whether it's homogeneous or not.

Homogeneous means that all active corners of the basic voxel case share the same material label.

Parameters
edgeCaseA 12-bit edge case bitmask.
tableIndexThe index in the non-manifold cases table for the given edge case
Returns
True if all active corners of the basic voxel case share the same material label, false otherwise.

Definition at line 799 of file vtkSurfaceNets3DNonManifoldCases.h.

◆ GetNumberOfPoints()

static constexpr VTK_ALWAYS_INLINE auto vtkSurfaceNets3DNonManifoldCases::GetNumberOfPoints ( NonManifoldCaseType nonManifoldCase) -> uint8_t
inlinestaticconstexpr

Given the non-manifold case, returns the number of duplicate points.

This is equal to the number of face-connected groups plus one additional point used as a stencil point for smoothing. For manifold cases (index 0), this is 1 (no duplicate points, no smoothing stencil needed).

Parameters
nonManifoldCaseThe non-manifold case index in [0, 11], where 0 means manifold.
Returns
The number of duplicate points.

Definition at line 814 of file vtkSurfaceNets3DNonManifoldCases.h.

◆ GetNonManiFoldPointIndex()

template<uint8_t QuadId>
static VTK_ALWAYS_INLINE auto vtkSurfaceNets3DNonManifoldCases::GetNonManiFoldPointIndex ( EdgeCaseType edgeCase,
int8_t tableIndex ) -> int8_t
inlinestatic

Given a quad id (bits 0–3 for yz-plane quads, 4–7 for xz-plane, 8–11 for xy-plane), and the table index for the non-manifold case, returns the duplicate point index for that quad in the current non-manifold configuration.

The duplicate point index identifies which connected component the voxels generating that face belong to. Components are formed by splitting all populated voxels in the 2x2x2 grid into connected components where two voxels are directly connected if their Hamming distance is exactly 1. Note that voxels within the same component do not need to be directly connected to each other — they only need to be reachable through a path of direct connections.

Template Parameters
QuadIdIndex of the quad (face-bit position) in [0, 11].
Parameters
edgeCaseA 12-bit edge case bitmask.
tableIndexThe index in the non-manifold cases table for the given edge case of a non-manifold case, false otherwise.
Returns
The duplicate point index for the given quad, or -1 if no face is generated.

Definition at line 852 of file vtkSurfaceNets3DNonManifoldCases.h.

◆ GetNumberOfManifoldSubCases()

static constexpr VTK_ALWAYS_INLINE auto vtkSurfaceNets3DNonManifoldCases::GetNumberOfManifoldSubCases ( NonManifoldCaseType nonManifoldCase) -> uint8_t
inlinestaticconstexpr

Given the non-manifold case, returns the number of manifold sub-cases.

Each sub-case corresponds to one face-connected group of voxels that needs to be processed independently to resolve the non-manifold configuration. For manifold cases (index 0), this is 1.

Parameters
nonManifoldCaseThe non-manifold case index in [0, 11], where 0 means manifold.
Returns
The number of manifold sub-cases for the given non-manifold case.

Definition at line 867 of file vtkSurfaceNets3DNonManifoldCases.h.

◆ GetManifoldSubEdgeCases()

static VTK_ALWAYS_INLINE auto vtkSurfaceNets3DNonManifoldCases::GetManifoldSubEdgeCases ( EdgeCaseType edgeCase,
int8_t tableIndex ) -> const std::array<EdgeCaseType, 4>&
inlinestatic

Returns the manifold sub-edge cases for a given edge case and table index.

Each sub-edge case is a 12-bit mask representing the faces belonging to one face-connected group of voxels. Unused sub-cases are zero.

Parameters
edgeCaseA 12-bit edge case bitmask.
tableIndexThe index into the non-manifold metadata table for this edge case.
Returns
A reference to the array of up to 4 manifold sub-edge cases.

Definition at line 896 of file vtkSurfaceNets3DNonManifoldCases.h.

◆ ComputeState()

static constexpr VTK_ALWAYS_INLINE auto vtkSurfaceNets3DNonManifoldCases::ComputeState ( uint8_t numPoints,
int8_t tableIndex ) -> uint8_t
inlinestaticconstexpr

Computes a state code from a point count and table index using a variable-width linear mapping.

The state space is partitioned by point count, with offsets for the 4-point and 5-point groups calculated dynamically using MaxTableIndex to ensure the 3-point group is fully utilized.

State space layout (without VTK_SURFACE_NETS_3D_SUPPORT_ALL_SOLVABLE_VARIATIONS_FOR_CASE_8): State 0: Empty voxel (numPoints=0, EmptyIndex) State 1: Manifold point (numPoints=1, ManifoldIndex) State 2: Unsolvable non-manifold point (numPoints=1, NonManifoldIndexUnsolvable) States 3-11: 3-point solvable non-manifold (tableIndex 0-8, MaxTableIndex=8) States 12-13: 4-point solvable non-manifold (tableIndex 0-1) States 14-15: 5-point solvable non-manifold (tableIndex 0-1) Maximum state: 15

State space layout (with VTK_SURFACE_NETS_3D_SUPPORT_ALL_SOLVABLE_VARIATIONS_FOR_CASE_8): State 0: Empty voxel (numPoints=0, EmptyIndex) State 1: Manifold point (numPoints=1, ManifoldIndex) State 2: Unsolvable non-manifold point (numPoints=1, NonManifoldIndexUnsolvable) States 3-12: 3-point solvable non-manifold (tableIndex 0-9, MaxTableIndex=9) States 13-14: 4-point solvable non-manifold (tableIndex 0-1) States 15-16: 5-point solvable non-manifold (tableIndex 0-1) Maximum state: 16

Parameters
numPointsThe number of duplicate points to generate (0, 1, 3, 4, or 5).
tableIndexThe metadata table index (EmptyIndex, ManifoldIndex, NonManifoldIndexUnsolvable, or 0 to MaxTableIndex).
Returns
The computed state code.

Definition at line 936 of file vtkSurfaceNets3DNonManifoldCases.h.

◆ GetStateInfo()

static constexpr VTK_ALWAYS_INLINE auto vtkSurfaceNets3DNonManifoldCases::GetStateInfo ( uint8_t state) -> StateInfo
inlinestaticconstexpr

Decodes both the number of duplicate points and the metadata table index from a state code in a single table lookup.

Parameters
stateThe state code computed via ComputeState. Valid range: [0, 15] without VTK_SURFACE_NETS_3D_SUPPORT_ALL_SOLVABLE_VARIATIONS_FOR_CASE_8, or [0, 16] with it.
Returns
StateInfo containing NumPoints and TableIndex.

Definition at line 982 of file vtkSurfaceNets3DNonManifoldCases.h.

Member Data Documentation

◆ MaxTableIndex

uint8_t vtkSurfaceNets3DNonManifoldCases::MaxTableIndex = 8
staticconstexpr

Definition at line 609 of file vtkSurfaceNets3DNonManifoldCases.h.


The documentation for this class was generated from the following file: