Skip to content

Add dual_contour (DC+QEF) mesh extraction op#670

Draft
swahtz wants to merge 2 commits into
openvdb:mainfrom
swahtz:dual_contouring_meshing
Draft

Add dual_contour (DC+QEF) mesh extraction op#670
swahtz wants to merge 2 commits into
openvdb:mainfrom
swahtz:dual_contouring_meshing

Conversation

@swahtz

@swahtz swahtz commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Add dual_contour, a VoxelBlockManager-based dual-contouring mesher that turns a narrow-band SDF on an OnIndex grid into a triangle mesh, alongside the existing marching_cubes. It returns (vertices, faces, normals) as three JaggedTensors jagged over the grid batch, where normals are the normalized SDF gradient at each vertex.

Algorithm: one vertex per surface cell placed by minimizing a quadratic error function over the 12 edge zero-crossings and their interpolated normals (centroid-recentred + Tikhonov-regularized); dual connectivity (one quad per sign-changing minimal grid edge, triangulated and grad and optional cluster-collapse decimation via reduce (uniform F^3 blocks) or adaptivity (flat-block collapse). CUDA only; consumes a >= ~3 half-width band (e.g. from retopologize_sdf) for a watertight result. Follows the dual-contouring approach of OpenVDB's VolumeToMesh, but pla QEF rather than an averaged mass-point and decimates by clustering rather than a seam-stitched octree merge. Sources are cited in the code: Ju Garland & Heckbert 1997, Kobbelt et al. 2001, Lorensen & Cline 1987.

API: Grid.dual_contour / GridBatch.dual_contour, functional dual_contour_{single,batch}, the pybind binding, and the .pyi s

Factor the shared NanoVDB VBM scaffolding (grid/buffer aliases, the build-once VBMHelper, and the per-block decode + 6-face stencil helpers) into detail/utils/cuda/VoxelBlockManagerHelper.h, and refactor onto it (replacing its VBM_FACES_BEGIN macro with the shared device helpers). Also tidy the SDF op docstrings: describe band as the narrow- and use the repo's shape/dtype docstring style.

Tests (tests/unit/test_dc.py): mesh validity + closed genus-0 (Euler characteristic) topology; analytic ground truth (a planar SDF r exactly, sphere radius/normals); composition with reinitialize/retopologize_sdf; batch-vs-single parity; decimation bounds; and the empty-surfac

swahtz added 2 commits June 24, 2026 05:05
Add dual_contour, a VoxelBlockManager-based dual-contouring mesher that turns
a narrow-band SDF on an OnIndex grid into a triangle mesh, alongside the
existing marching_cubes. It returns (vertices, faces, normals) as three
JaggedTensors jagged over the grid batch, where normals are the normalized
SDF gradient at each vertex.

Algorithm: one vertex per surface cell placed by minimizing a quadratic error
function over the 12 edge zero-crossings and their interpolated
normals (centroid-recentred + Tikhonov-regularized); dual connectivity (one
quad per sign-changing minimal grid edge, triangulated and grad
and optional cluster-collapse decimation via `reduce` (uniform F^3 blocks) or
`adaptivity` (flat-block collapse). CUDA only; consumes a >= ~3
half-width band (e.g. from retopologize_sdf) for a watertight result. Follows
the dual-contouring approach of OpenVDB's VolumeToMesh, but pla
QEF rather than an averaged mass-point and decimates by clustering rather than
a seam-stitched octree merge. Sources are cited in the code: Ju
Garland & Heckbert 1997, Kobbelt et al. 2001, Lorensen & Cline 1987.

API: Grid.dual_contour / GridBatch.dual_contour, functional
dual_contour_{single,batch}, the pybind binding, and the .pyi s

Factor the shared NanoVDB VBM scaffolding (grid/buffer aliases,
the build-once VBMHelper, and the per-block decode + 6-face stencil helpers)
into detail/utils/cuda/VoxelBlockManagerHelper.h, and refactor
onto it (replacing its VBM_FACES_BEGIN macro with the shared device helpers).
Also tidy the SDF op docstrings: describe `band` as the narrow-
and use the repo's shape/dtype docstring style.

Tests (tests/unit/test_dc.py): mesh validity + closed genus-0 (Euler
characteristic) topology; analytic ground truth (a planar SDF r
exactly, sphere radius/normals); composition with reinitialize/retopologize_sdf;
batch-vs-single parity; decimation bounds; and the empty-surfac

Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new CUDA-only dual-contouring meshing operator (dual_contour, DC+QEF) alongside existing meshing/SDF ops, exposing it through the C++ op layer, pybind, Python Grid/GridBatch APIs, typing stubs, and a dedicated unit test suite. The PR also factors shared NanoVDB VoxelBlockManager (VBM) scaffolding into a reusable CUDA helper header and updates SDF-related docstrings for consistency.

Changes:

  • Implement fvdb.detail.ops::dualContour (DC+QEF with optional clustering/decimation) and bind it to Python (_fvdb_cpp.dual_contour, Grid.dual_contour, GridBatch.dual_contour, functional wrappers).
  • Add shared VBM decode + face-stencil helpers in VoxelBlockManagerHelper.h and refactor ReinitializeSdf.cu to use them.
  • Add tests/unit/test_dc.py covering correctness, topology, batching parity, and decimation bounds; plus minor docstring cleanups around narrow-band semantics.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/test_dc.py Adds unit tests for dual contour meshing validity, accuracy (plane/sphere), batching parity, and decimation behavior.
src/python/GridBatchOps.cpp Exposes dual_contour via pybind alongside existing meshing/SDF ops.
src/fvdb/detail/utils/cuda/VoxelBlockManagerHelper.h New shared CUDA helper for VBM build/decode and 6-face stencil reads.
src/fvdb/detail/ops/ReinitializeSdf.h Updates docstrings to match jagged shape/dtype conventions.
src/fvdb/detail/ops/ReinitializeSdf.cu Refactors VBM decode/stencil preamble to use the new helper header.
src/fvdb/detail/ops/DualContour.h Declares the new dual contour op API and documents inputs/outputs and decimation options.
src/fvdb/detail/ops/DualContour.cu Implements the DC+QEF pipeline, optional decimation, connectivity/orientation, and jagged output assembly.
src/CMakeLists.txt Adds DualContour.cu to the CUDA build.
fvdb/grid.py Adds Grid.dual_contour Python API and updates SDF docstrings.
fvdb/grid_batch.py Adds GridBatch.dual_contour Python API and updates SDF docstrings.
fvdb/functional/_sdf.py Updates retopologize_sdf_* docstrings to describe the full ±band*vx band.
fvdb/functional/_meshing.py Adds dual_contour_single / dual_contour_batch functional entrypoints.
fvdb/functional/__init__.py Re-exports the new meshing functions and adjusts import grouping.
fvdb/_fvdb_cpp.pyi Adds typing for _fvdb_cpp.dual_contour.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +200 to +210
gatherFusedKernel(const OnIndexGridT *grid,
const uint32_t *firstLeafID,
const uint64_t *jumpMap,
uint64_t firstOffset,
const float *sdf,
BoxStencilGeometry geometry,
float iso,
int32_t *neighborTable,
float *gradient,
uint8_t *surfaceFlag,
int32_t *voxelCoord) {
Comment on lines +233 to +238
gradient[centerIndex * 3 + 0] =
0.5f * (faceValue(geometry.faceSpoke[1]) - faceValue(geometry.faceSpoke[0]));
gradient[centerIndex * 3 + 1] =
0.5f * (faceValue(geometry.faceSpoke[3]) - faceValue(geometry.faceSpoke[2]));
gradient[centerIndex * 3 + 2] =
0.5f * (faceValue(geometry.faceSpoke[5]) - faceValue(geometry.faceSpoke[4]));
Comment on lines +922 to +932
gatherFusedKernel<<<vbm.blockCount, 1 << kLog2BlockWidth, 0, stream>>>(grid,
vbm.firstLeafID(),
vbm.jumpMap(),
vbm.firstOffset,
sdf,
kGeometry,
iso,
neighborTable,
gradient,
surfaceFlag,
voxelCoord);
Comment on lines +1243 to +1245
VBMHelper vbm(grid, stream);
const int64_t valueCount = (int64_t)vbm.valueCount; // numVoxels + 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants