Add dual_contour (DC+QEF) mesh extraction op#670
Draft
swahtz wants to merge 2 commits into
Draft
Conversation
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>
Contributor
There was a problem hiding this comment.
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.hand refactorReinitializeSdf.cuto use them. - Add
tests/unit/test_dc.pycovering 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 | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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) oradaptivity(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
bandas 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