Skip to content

Commit b65fdf0

Browse files
authored
Expose the Gaussian splat BVH for refit (#4045)
1 parent e3c5fdf commit b65fdf0

3 files changed

Lines changed: 92 additions & 18 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Expose the Gaussian splat BVH via `Gaussian.bvh` and add `Gaussian.bvh_refit()` to refit it in place after the finalized data changes, mirroring `Model.bvh_refit_shapes`.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deprecate `Gaussian.warp_data` and `Gaussian.warp_bvh`; use the `Gaussian.Data` object returned by `Gaussian.finalize()` and `Gaussian.bvh` instead.

newton/_src/geometry/types.py

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,6 +2481,12 @@ class Data:
24812481
min_response: wp.float32
24822482
sorting_mode: wp.int32
24832483

2484+
_WARP_DATA_DEPRECATION_MSG = (
2485+
"Gaussian.warp_data is deprecated in Newton 1.6; use the Gaussian.Data object returned by "
2486+
"Gaussian.finalize() instead."
2487+
)
2488+
_WARP_BVH_DEPRECATION_MSG = "Gaussian.warp_bvh is deprecated in Newton 1.6; use Gaussian.bvh instead."
2489+
24842490
def __init__(
24852491
self,
24862492
positions: np.ndarray,
@@ -2554,8 +2560,8 @@ def __init__(
25542560
self._sh_coeffs.setflags(write=False)
25552561

25562562
# GPU arrays populated by finalize()
2557-
self.warp_bvh: wp.Bvh = None
2558-
self.warp_data: Gaussian.Data = None
2563+
self._warp_bvh: wp.Bvh = None
2564+
self._warp_data: Gaussian.Data = None
25592565

25602566
# Inertia: Gaussians are render-only so they contribute no mass
25612567
self.has_inertia = False
@@ -2611,6 +2617,46 @@ def sorting_mode(self) -> SortingMode:
26112617
"""Sorting mode, Gaussian.SortingMode."""
26122618
return self._sorting_mode
26132619

2620+
@property
2621+
def bvh(self) -> wp.Bvh | None:
2622+
"""The finalized Warp BVH over the Gaussians, or ``None`` before :meth:`finalize`.
2623+
2624+
Mirrors the scene shape BVH exposed as :attr:`~newton.Model.bvh_shapes`.
2625+
Use :meth:`bvh_refit` to update it in place after the finalized
2626+
:class:`Data` arrays change.
2627+
"""
2628+
return self._warp_bvh
2629+
2630+
@property
2631+
def warp_data(self) -> "Gaussian.Data | None":
2632+
"""Deprecated alias for the finalized Warp Gaussian data.
2633+
2634+
.. deprecated:: 1.6
2635+
Use the :class:`Data` object returned by :meth:`finalize` instead.
2636+
"""
2637+
warnings.warn(self._WARP_DATA_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
2638+
return self._warp_data
2639+
2640+
@warp_data.setter
2641+
def warp_data(self, value: "Gaussian.Data | None") -> None:
2642+
warnings.warn(self._WARP_DATA_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
2643+
self._warp_data = value
2644+
2645+
@property
2646+
def warp_bvh(self) -> wp.Bvh | None:
2647+
"""Deprecated alias for :attr:`bvh`.
2648+
2649+
.. deprecated:: 1.6
2650+
Use :attr:`bvh` instead.
2651+
"""
2652+
warnings.warn(self._WARP_BVH_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
2653+
return self._warp_bvh
2654+
2655+
@warp_bvh.setter
2656+
def warp_bvh(self, value: wp.Bvh | None) -> None:
2657+
warnings.warn(self._WARP_BVH_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
2658+
self._warp_bvh = value
2659+
26142660
def _find_sh_degree(self) -> int:
26152661
"""Spherical harmonics degree (0-3), inferred from *sh_coeffs* shape."""
26162662
c = self._sh_coeffs.shape[1]
@@ -2637,29 +2683,55 @@ def finalize(self, device: Devicelike = None, *, bvh_constructor: str | None = N
26372683
from ..sensors.warp_raytrace.gaussians import compute_gaussian_bvh_bounds # noqa: PLC0415
26382684

26392685
with wp.ScopedDevice(device):
2640-
self.warp_data = Gaussian.Data()
2641-
self.warp_data.transforms = wp.array(
2642-
np.append(self._positions, self._rotations, axis=1), dtype=wp.transformf
2643-
)
2644-
self.warp_data.scales = wp.array(self._scales, dtype=wp.vec3f)
2645-
self.warp_data.opacities = wp.array(self._opacities, dtype=wp.float32)
2646-
self.warp_data.sh_coeffs = wp.array(self._sh_coeffs, dtype=wp.float32)
2647-
self.warp_data.min_response = self.min_response
2648-
self.warp_data.sorting_mode = self.sorting_mode
2649-
self.warp_data.num_points = self.warp_data.transforms.shape[0]
2650-
2686+
warp_data = Gaussian.Data()
2687+
warp_data.transforms = wp.array(np.append(self._positions, self._rotations, axis=1), dtype=wp.transformf)
2688+
warp_data.scales = wp.array(self._scales, dtype=wp.vec3f)
2689+
warp_data.opacities = wp.array(self._opacities, dtype=wp.float32)
2690+
warp_data.sh_coeffs = wp.array(self._sh_coeffs, dtype=wp.float32)
2691+
warp_data.min_response = self.min_response
2692+
warp_data.sorting_mode = self.sorting_mode
2693+
warp_data.num_points = warp_data.transforms.shape[0]
26512694
lowers = wp.zeros(self.count, dtype=wp.vec3f)
26522695
uppers = wp.zeros(self.count, dtype=wp.vec3f)
2653-
26542696
wp.launch(
26552697
kernel=compute_gaussian_bvh_bounds,
26562698
dim=self.count,
2657-
inputs=[self.warp_data, lowers, uppers],
2699+
inputs=[warp_data, lowers, uppers],
26582700
)
2701+
warp_bvh = wp.Bvh(lowers, uppers, constructor=bvh_constructor)
2702+
warp_data.bvh_id = warp_bvh.id
2703+
self._warp_data = warp_data
2704+
self._warp_bvh = warp_bvh
2705+
return warp_data
2706+
2707+
def bvh_refit(self) -> None:
2708+
"""Refit the Gaussian :attr:`bvh` in place for the current finalized data.
26592709
2660-
self.warp_bvh = wp.Bvh(lowers, uppers, constructor=bvh_constructor)
2661-
self.warp_data.bvh_id = self.warp_bvh.id
2662-
return self.warp_data
2710+
Recomputes per-Gaussian bounds from the finalized GPU data and refits
2711+
the BVH in place, keeping its existing topology. Call this after
2712+
mutating the finalized :class:`Data` arrays (e.g. ``transforms`` or
2713+
``scales``) on the device so the acceleration structure tracks the
2714+
moved Gaussians. Structural changes (a different Gaussian count)
2715+
require a full rebuild via :meth:`finalize` instead.
2716+
2717+
This mirrors :meth:`~newton.Model.bvh_refit_shapes` for the scene
2718+
shape BVH.
2719+
2720+
Raises:
2721+
RuntimeError: If :meth:`finalize` has not been called yet.
2722+
"""
2723+
from ..sensors.warp_raytrace.gaussians import compute_gaussian_bvh_bounds # noqa: PLC0415
2724+
2725+
if self._warp_bvh is None or self._warp_data is None:
2726+
raise RuntimeError("Gaussian.bvh_refit() requires Gaussian.finalize() to have been called first.")
2727+
2728+
with wp.ScopedDevice(self._warp_bvh.device):
2729+
wp.launch(
2730+
kernel=compute_gaussian_bvh_bounds,
2731+
dim=self.count,
2732+
inputs=[self._warp_data, self._warp_bvh.lowers, self._warp_bvh.uppers],
2733+
)
2734+
self._warp_bvh.refit()
26632735

26642736
# ---- Factory methods -----------------------------------------------------
26652737

0 commit comments

Comments
 (0)