Add narrow-band SDF reinitialize/retopologize ops#669
Merged
Conversation
Adds a signed-distance-field reinitialization operator and a narrow-band retopologization helper for sparse grids, complementing the existing marching-cubes / TSDF tooling. **New ops** * `reinitialize_sdf` (C++/CUDA): redistances a per-voxel signed field to |grad phi| = 1 with a TVD-RK Godunov upwind eikonal solve (order 1/2/3, frozen Peng sign), then optionally de-staircases it with mean-curvature or volume-preserving Taubin Laplacian smoothing. Grid topology is preserved. The 6-face stencil is fused into every sweep via the NanoVDB VoxelBlockManager, which is built once per grid and reused across all sweeps. Supports float32/f 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
This PR adds a CUDA-only narrow-band SDF reinitialization operator (Godunov upwind eikonal solve with optional Laplacian de-staircasing) plus a Python-level retopologize_sdf helper that composes padding/injection, reinitialization, and band pruning to produce a clean narrow-band SDF aligned with the output grid topology.
Changes:
- Add
reinitialize_sdfCUDA op backed by NanoVDB VoxelBlockManager and expose it through the C++/pybind layer. - Add Python functional + OO APIs for
reinitialize_sdfandretopologize_sdf(single + batch), and introduce a cross-boundarySmoothingModeenum. - Add focused unit tests for correctness, pruning alignment, padding behavior, batch parity, and float64.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/fvdb/detail/ops/ReinitializeSdf.h |
Declares the new CUDA SDF reinitialization API + SmoothingMode. |
src/fvdb/detail/ops/ReinitializeSdf.cu |
Implements the VBM-based narrow-band redistancing + optional smoothing. |
src/python/GridBatchOps.cpp |
Binds SmoothingMode and reinitialize_sdf into _fvdb_cpp. |
fvdb/functional/_sdf.py |
Adds Python functional wrappers and retopologize_sdf composition logic. |
fvdb/functional/__init__.py |
Exports the new SDF functional APIs. |
fvdb/enums.py |
Adds public SmoothingMode IntEnum. |
fvdb/grid.py |
Adds Grid.reinitialize_sdf / Grid.retopologize_sdf. |
fvdb/grid_batch.py |
Adds GridBatch.reinitialize_sdf / GridBatch.retopologize_sdf. |
fvdb/_fvdb_cpp.pyi |
Adds type stubs for _fvdb_cpp.SmoothingMode and reinitialize_sdf. |
fvdb/__init__.py |
Re-exports SmoothingMode at top-level. |
tests/unit/test_sdf.py |
New unit tests for the SDF reinit/retopo behavior. |
src/CMakeLists.txt |
Registers the new CUDA source file in the build. |
src/cmake/get_nanovdb.cmake |
Updates NanoVDB pin for required VBM APIs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The public `smoothing` parameter was annotated inconsistently across the SDF entry points -- the `Grid` / `GridBatch` methods allowed `str` (which `_to_cpp_smoothing` could not accept and would raise on), while the functional wrappers allowed `int`. Neither matched the documented enum API. Annotate `smoothing` as `SmoothingMode` everywhere, matching the existing cross-boundary enum convention (`CameraModel` / `ProjectionMethod`), and simplify `_to_cpp_smoothing` to a name-based lookup that accepts the public enum (and passes a bound `_fvdb_cpp.SmoothingMode` through unchanged). Update the smoothing test to select the mode with the enum. Addresses review feedback on openvdb#669. Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
harrism
approved these changes
Jun 17, 2026
harrism
left a comment
Contributor
There was a problem hiding this comment.
Looks good. Just had one nit.
…more similar in their implementations. Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
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.
Adds an SDF reinitialization operator and a narrow-band retopologization helper. The core is a VoxelBlockManager (VBM) eikonal solver ported from standalone code used in a NanoVDB meshing prototype.
Motivation
This is the first needed component of a port of the OpenVDB
VolumeToMeshdual-contouring meshing tool to NanoVDB + VoxelBlockManager that I've been developing as a standalone example and I imagined is likely useful for other applications which usefvdb-core.Changes
Core CUDA op —
src/fvdb/detail/ops/ReinitializeSdf.{h,cu}(new)reinitialize_sdfredistances a per-voxel signed field to|grad phi| = 1on the same grid (topology unchanged):smoothpasses), selected viaSmoothingMode.[-band*vx, band*vx]each sweep; inactive face-neighbours read the+band*vx"outside" value.JaggedTensor.float32/float64; CUDA only (the VBM is a CUDA structure — CPU raises a clearTORCH_CHECK).Python composition —
fvdb/functional/_sdf.py(new)retopologize_sdfcomposesreinitialize_sdfwith existing ops, no new C++ plumbing:pad=True) dilate bybandviadilated_gridand carry the field onto the new voxels viainject, seeding them as exterior, so the output band is a fullbandvoxels wide even when the input grid's active band was thinner;reinitialize_sdf; (optional, defaultprune=True) keep|phi| < band*vx*0.999via the existingpruned_gridand select the field withJaggedTensor.rmask. The mask-based prune preserves canonical voxel order, so the field stays aligned with the pruned grid without a re-inject.Public API
fvdb.functional.{reinitialize,retopologize}_sdf_{single,batch}(+ exports).Grid.reinitialize_sdf/Grid.retopologize_sdfand theGridBatchequivalentsSmoothingModeenum following fvdb's cross-boundary-enum conventionDependency —
src/cmake/get_nanovdb.cmakefec59777 → f9754140for the VoxelBlockManager build/decode API (buildVoxelBlockManager,decodeInverseMaps,VoxelBlockManagerHandle).Test plan
Built and tested in the
fvdbconda env (PyTorch 2.11, CUDA 13, sm_120):tests/unit/test_sdf.py: 10 passed — sphere-SDF preserved under redistancing (mean err ~0.09·vx), recovered from a crude sign step (~0.25·vx), band clamp, narrow-band prune, prune voxel-order alignment guard (grid.ijk[mask] == pruned.ijk), padding widens a thin band, batch-vs-single parity, andfloat64.tests/unit/test_inject.py+test_io.py(234 passed) andtest_dual.py+test_basic_ops_single.py(94 passed).ReinitializeSdf.cucompiles clean under-Werror=all-warnings/-Xcompiler=-Wall,-Werror.Notes / risks
retopologize_sdf(pad=True)seeding. New voxels created with padding are seeded as exterior (+band*vx), which is correct when the dilation extends outward — i.e. the grid's interior (phi < 0) is already represented (occupancy/TSDF/mesh-derived fields, the common case). For a hollow thin shell that doesn't fill its interior, passpad=Falsewith a pre-banded grid. Documented in the docstring.float32/float64only —float16is intentionally rejected (the eikonal solve needs the precision).