newton.geometry.ParticleSurface#

class newton.geometry.ParticleSurface(voxel_size, *, kernel_radius=None, threshold=0.25, smooth_lambda=0.5, anisotropic=False, anisotropy_ratio=4.0, kernel_scale=0.5, anisotropy_scale=1.0, anisotropy_min_neighbors=25, padding=2, field_smooth_iterations=0, field_smooth_radius=1, field_mode=None, redistance_iterations=0, mesh_smooth_iterations=0, mesh_smooth_lambda=1.0, device=None, anisotropy_strength=1.0, surface_method='density', particle_sdf_radius_scale=1.0, particle_sdf_band=2.0, max_grid_cells=None, world_count=1, anisotropy_binning=False)[source]#

Bases: object

Reusable context for extracting a triangle mesh from particle data.

Uses the Yu & Turk (2010) anisotropic kernel method: per-particle Weighted PCA determines oriented ellipsoidal kernels that produce a smooth scalar field whose isosurface tightly wraps the particles.

Parameters:
  • voxel_size (float) – Edge length of each grid voxel [m].

  • max_grid_cells (int | None) – Maximum active sparse-grid cell count across all worlds. When set, extraction uses preallocated, graph-capturable buffers. When None, each extraction uses tight sparse field and mesh allocations. Rebuildable topology-node capacities assume spatially coherent surface bands; highly scattered fields can exhaust them before reaching this cell count.

  • world_count (int) – Number of independent particle worlds to extract.

  • kernel_radius (float | None) – Search radius for neighbor queries [m]. Defaults to 3 * voxel_size.

  • threshold (float) – Isosurface level for marching cubes. The scalar field is approximately 1.0 inside dense particle regions. Defaults to 0.25.

  • smooth_lambda (float) – Blending factor for position smoothing [0, 1]. Higher values produce smoother surfaces. Defaults to 0.5.

  • anisotropic (bool) – Enable per-particle WPCA anisotropic kernels. When False (default), all particles use isotropic kernels.

  • anisotropy_ratio (float) – Maximum anisotropic kernel axis ratio. Higher values allow flatter ellipsoids.

  • kernel_scale (float) – Kernel radius multiplier relative to kernel_radius. This sets the isotropic kernel radius and the geometric-mean radius of anisotropic kernels.

  • anisotropy_scale (float) – Relative multiplier for anisotropic kernel radii. Values greater than 1 widen anisotropic kernels without changing the isotropic fallback scale. Defaults to 1.

  • anisotropy_min_neighbors (int) – Minimum number of other particles required for anisotropic kernels. Sparser particles use isotropic kernels.

  • anisotropy_binning (bool) – Share one WPCA kernel among particles in each spatial bin. Bins are half the kernel radius wide. This reduces neighbor-query cost at the expense of spatial resolution.

  • anisotropy_strength (float) – Blend from isotropic kernels to anisotropic kernels [0, 1]. Lower values preserve more normal support from boundary particles back into the interior.

  • surface_method (Literal['density', 'particle_sdf']) – Surface reconstruction method. "density" uses anisotropic density splatting. "particle_sdf" directly unions per-particle anisotropic ellipsoid SDFs and stores an SDF field.

  • particle_sdf_radius_scale (float) – Radius multiplier for surface_method="particle_sdf".

  • particle_sdf_band (float) – Narrow-band half-width in normalized ellipsoid coordinates for surface_method="particle_sdf". Must be at least 1 so the band contains the zero level set.

  • padding (int) – Extra voxels added around the particle bounding box.

  • field_smooth_iterations (int) – Number of separable Gaussian blur passes applied to the scalar field before marching cubes. Defaults to 0.

  • field_smooth_radius (int) – Half-width of the Gaussian blur in voxels. Defaults to 1.

  • field_mode (Literal['density', 'sdf'] | None) – Field representation retained after extraction. "density" keeps the scalar density field used by marching cubes. "sdf" converts it to a signed distance approximation with negative values inside the particle surface. Defaults to "sdf" for surface_method="particle_sdf" and "density" otherwise.

  • redistance_iterations (int) – Number of Eikonal redistancing iterations applied when field_mode="sdf". Set to 0 to skip.

  • mesh_smooth_iterations (int) – Number of Laplacian smoothing passes applied to the extracted mesh. Set to 0 to disable.

  • mesh_smooth_lambda (float) – Laplacian step size [0, 1].

  • device (wp.DeviceLike) – Warp device for computation.

class ExtractionMesh(vertices, indices, normals, counts, active_particle_count, vertex_world_offsets, index_world_offsets, *, exact)#

Bases: object

Particle surface mesh and its device-resident logical counts.

Buffers are exact-sized when max_grid_cells is None and preallocated otherwise.

Vertices and indices from each world occupy contiguous ranges. Their offsets are stored in vertex_world_offsets and index_world_offsets, each with shape (world_count + 1,). World i occupies the half-open range [offsets[i], offsets[i + 1]).

__init__(vertices, indices, normals, counts, active_particle_count, vertex_world_offsets, index_world_offsets, *, exact)#
to_arrays()#

Return one exact-length mesh containing every world as a disconnected component.

class SparseField(volume, voxel_data, background, world_index_offsets, per_world_status)#

Bases: object

Sparse scalar field stored as an index grid and per-voxel data.

Pass volume, voxel_data, and background to warp.volume_sample_index() to sample the field. The voxel-data buffer may include reserved capacity; the volume maps index-space coordinates to the corresponding live entries.

Multiple worlds share one packed index grid. To sample world i at world-space position position, pass position / particle_surface.voxel_size + wp.vec3(world_index_offsets[i]) as the uvw argument to warp.volume_sample_index(). The offset is zero for a single-world surface.

Reacquire ParticleSurface.sparse_field after updating or extracting the field because its underlying storage may be replaced. When using preallocated storage, inspect per_world_status before consuming results; a nonzero entry means the sparse field may be incomplete because its capacity was exceeded.

__init__(volume, voxel_data, background, world_index_offsets, per_world_status)#
background: float#

Value used when sampling outside the indexed topology.

per_world_status: wp.array[wp.int32]#

Extraction status per world, shape (world_count,).

Zero indicates success; a nonzero value indicates sparse-grid overflow.

volume: Volume#

NanoVDB index grid defining the sparse field topology.

voxel_data: wp.array[wp.float32]#

Scalar feature values indexed by volume.

world_index_offsets: wp.array[wp.vec3i]#

Offset of each world’s coordinates in the packed index grid [voxels], shape (world_count,).

__init__(voxel_size, *, kernel_radius=None, threshold=0.25, smooth_lambda=0.5, anisotropic=False, anisotropy_ratio=4.0, kernel_scale=0.5, anisotropy_scale=1.0, anisotropy_min_neighbors=25, padding=2, field_smooth_iterations=0, field_smooth_radius=1, field_mode=None, redistance_iterations=0, mesh_smooth_iterations=0, mesh_smooth_lambda=1.0, device=None, anisotropy_strength=1.0, surface_method='density', particle_sdf_radius_scale=1.0, particle_sdf_band=2.0, max_grid_cells=None, world_count=1, anisotropy_binning=False)#
extract(positions, radii, *, compute_normals=True, particle_flags=None, particle_world=None)#

Extract a triangle mesh from particle positions.

When max_grid_cells is set, this method performs no host synchronization and can be captured in a CUDA graph. Otherwise it allocates exact-size field and mesh arrays.

Parameters:
  • positions (wp.array[wp.vec3f]) – Particle positions [m], shape (N,), dtype wp.vec3.

  • radii (wp.array[wp.float32]) – Per-particle radii [m], shape (N,), dtype wp.float32.

  • compute_normals (bool) – Whether to compute per-vertex normals.

  • particle_flags (wp.array[wp.int32] | None) – Optional per-particle flags. Particles without ACTIVE are skipped.

  • particle_world (wp.array[wp.int32] | None) – Optional world index per particle. Particles with negative or out-of-range world indices are skipped.

Returns:

Mesh buffers and device-resident logical counts.

Return type:

ExtractionMesh

redistance(iterations)#

Apply Eikonal redistancing to the current SDF field.

Parameters:

iterations (int) – Number of redistancing iterations.

resurface(*, compute_normals=True)#

Re-run marching cubes on the current field.

Parameters:

compute_normals (bool) – Whether to compute per-vertex normals.

Returns:

Mesh buffers and device-resident logical counts.

Return type:

ExtractionMesh

update_field(positions, radii, *, particle_flags=None, particle_world=None)#

Update the scalar field without extracting a mesh.

Parameters:
  • positions (wp.array[wp.vec3f]) – Particle positions [m], shape (N,), dtype wp.vec3.

  • radii (wp.array[wp.float32]) – Per-particle radii [m], shape (N,), dtype wp.float32.

  • particle_flags (wp.array[wp.int32] | None) – Optional per-particle flags. Particles without ACTIVE are skipped.

  • particle_world (wp.array[wp.int32] | None) – Optional world index per particle. Particles with negative or out-of-range world indices are skipped.

Returns:

The sparse index grid and its per-voxel data, or None when no sparse field topology was produced.

Return type:

SparseField | None

property field_mode: Literal['density', 'sdf']#

Field representation retained after extraction.

property sparse_field: SparseField | None#

Current sparse scalar field, or None before field extraction.

property voxel_size: float#

Edge length of each grid voxel [m].

property world_count: int#

Number of independent particle worlds.