@@ -27,6 +27,7 @@ use log::info;
2727/// Re-export the version of `nalgebra` used by this crate
2828pub use nalgebra;
2929use nalgebra:: Vector3 ;
30+ use std:: borrow:: Cow ;
3031use std:: hash:: Hash ;
3132use thiserror:: Error as ThisError ;
3233/// Re-export the version of `vtkio` used by this crate, if vtk support is enabled
@@ -258,7 +259,7 @@ pub struct Parameters<R: Real> {
258259 /// The surface reconstruction always results in a closed mesh around the particles.
259260 /// The final mesh can extend beyond this AABB due to the smoothing of the kernel.
260261 /// If not provided, the smallest AABB enclosing all particles is computed instead.
261- pub domain_aabb : Option < Aabb3d < R > > ,
262+ pub particle_aabb : Option < Aabb3d < R > > ,
262263 /// Whether to allow multi threading within the surface reconstruction procedure
263264 pub enable_multi_threading : bool ,
264265 /// Parameters for the spatial decomposition of the surface reconstruction
@@ -275,7 +276,7 @@ impl<R: Real> Parameters<R> {
275276 compact_support_radius : self . compact_support_radius . try_convert ( ) ?,
276277 cube_size : self . cube_size . try_convert ( ) ?,
277278 iso_surface_threshold : self . iso_surface_threshold . try_convert ( ) ?,
278- domain_aabb : map_option ! ( & self . domain_aabb , aabb => aabb. try_convert( ) ?) ,
279+ particle_aabb : map_option ! ( & self . particle_aabb , aabb => aabb. try_convert( ) ?) ,
279280 enable_multi_threading : self . enable_multi_threading ,
280281 spatial_decomposition : map_option ! ( & self . spatial_decomposition, sd => sd. try_convert( ) ?) ,
281282 } )
@@ -287,12 +288,14 @@ impl<R: Real> Parameters<R> {
287288pub struct SurfaceReconstruction < I : Index , R : Real > {
288289 /// Background grid that was used as a basis for generating the density map for marching cubes
289290 grid : UniformGrid < I , R > ,
290- /// Octree constructed for domain decomposition
291+ /// Octree constructed for domain decomposition (if octree-based spatial decomposition was enabled)
291292 octree : Option < Octree < I , R > > ,
292293 /// Point-based density map generated from the particles that was used as input to marching cubes
293294 density_map : Option < DensityMap < I , R > > ,
294- /// Per particle densities
295+ /// Per particle densities (contains only data of particles inside the domain)
295296 particle_densities : Option < Vec < R > > ,
297+ /// If an AABB was specified to restrict the reconstruction, this stores per input particle whether they were inside
298+ particle_inside_aabb : Option < Vec < bool > > ,
296299 /// Surface mesh that is the result of the surface reconstruction
297300 mesh : TriMesh3d < R > ,
298301 /// Workspace with allocated memory for subsequent surface reconstructions
@@ -307,6 +310,7 @@ impl<I: Index, R: Real> Default for SurfaceReconstruction<I, R> {
307310 octree : None ,
308311 density_map : None ,
309312 particle_densities : None ,
313+ particle_inside_aabb : None ,
310314 mesh : TriMesh3d :: default ( ) ,
311315 workspace : ReconstructionWorkspace :: default ( ) ,
312316 }
@@ -409,13 +413,52 @@ pub fn reconstruct_surface_inplace<'a, I: Index, R: Real>(
409413 // Clear the existing mesh
410414 output_surface. mesh . clear ( ) ;
411415
416+ // Filter out particles
417+ let filtered_particle_positions = if let Some ( particle_aabb) = & parameters. particle_aabb {
418+ profile ! ( "filtering particles" ) ;
419+
420+ use rayon:: prelude:: * ;
421+ let mut particle_inside = output_surface
422+ . particle_inside_aabb
423+ . take ( )
424+ . unwrap_or_default ( ) ;
425+ utils:: reserve_total ( & mut particle_inside, particle_positions. len ( ) ) ;
426+ particle_positions
427+ . par_iter ( )
428+ . map ( |p| particle_aabb. contains_point ( p) )
429+ . collect_into_vec ( & mut particle_inside) ;
430+ let particle_inside_count = particle_inside. par_iter ( ) . copied ( ) . filter ( |i| * i) . count ( ) ;
431+
432+ // Take temporary storage for filtered particles from workspace
433+ let mut filtered_particles =
434+ std:: mem:: take ( output_surface. workspace . filtered_particles_mut ( ) ) ;
435+ filtered_particles. clear ( ) ;
436+ utils:: reserve_total ( & mut filtered_particles, particle_inside_count) ;
437+
438+ // Collect filtered particles
439+ filtered_particles. extend (
440+ particle_positions
441+ . iter ( )
442+ . zip ( particle_inside. iter ( ) . copied ( ) )
443+ . filter ( |( _, is_inside) | * is_inside)
444+ . map ( |( p, _) | p)
445+ . cloned ( ) ,
446+ ) ;
447+
448+ output_surface. particle_inside_aabb = Some ( particle_inside) ;
449+ Cow :: Owned ( filtered_particles)
450+ } else {
451+ Cow :: Borrowed ( particle_positions)
452+ } ;
453+ let particle_positions = filtered_particle_positions. as_ref ( ) ;
454+
412455 // Initialize grid for the reconstruction
413456 output_surface. grid = grid_for_reconstruction (
414457 particle_positions,
415458 parameters. particle_radius ,
416459 parameters. compact_support_radius ,
417460 parameters. cube_size ,
418- parameters. domain_aabb . as_ref ( ) ,
461+ parameters. particle_aabb . as_ref ( ) ,
419462 parameters. enable_multi_threading ,
420463 ) ?;
421464
@@ -443,6 +486,12 @@ pub fn reconstruct_surface_inplace<'a, I: Index, R: Real>(
443486 ) ?,
444487 }
445488
489+ // Put back temporary storage for filtered particles for next reconstruction
490+ if let Cow :: Owned ( mut filtered_particles) = filtered_particle_positions {
491+ filtered_particles. clear ( ) ;
492+ * output_surface. workspace . filtered_particles_mut ( ) = filtered_particles;
493+ }
494+
446495 Ok ( ( ) )
447496}
448497
@@ -458,39 +507,38 @@ pub fn grid_for_reconstruction<I: Index, R: Real>(
458507 particle_radius : R ,
459508 compact_support_radius : R ,
460509 cube_size : R ,
461- domain_aabb : Option < & Aabb3d < R > > ,
510+ particle_aabb : Option < & Aabb3d < R > > ,
462511 enable_multi_threading : bool ,
463512) -> Result < UniformGrid < I , R > , ReconstructionError < I , R > > {
464- let domain_aabb = if let Some ( domain_aabb ) = domain_aabb {
465- domain_aabb . clone ( )
513+ let mut particle_aabb = if let Some ( particle_aabb ) = particle_aabb {
514+ particle_aabb . clone ( )
466515 } else {
467516 profile ! ( "compute minimum enclosing aabb" ) ;
468517
469- let mut domain_aabb = {
518+ let particle_aabb = {
470519 let mut aabb = if enable_multi_threading {
471520 Aabb3d :: par_from_points ( particle_positions)
472521 } else {
473522 Aabb3d :: from_points ( particle_positions)
474523 } ;
524+ // TODO: Is this really necessary?
475525 aabb. grow_uniformly ( particle_radius) ;
476526 aabb
477527 } ;
478528
479529 info ! (
480530 "Minimal enclosing bounding box of particles was computed as: {:?}" ,
481- domain_aabb
531+ particle_aabb
482532 ) ;
483533
484- // Ensure that we have enough margin around the particles such that the every particle's kernel support is completely in the domain
485- let kernel_margin = density_map:: compute_kernel_evaluation_radius :: < I , R > (
486- compact_support_radius,
487- cube_size,
488- )
489- . kernel_evaluation_radius ;
490- domain_aabb. grow_uniformly ( kernel_margin) ;
491-
492- domain_aabb
534+ particle_aabb
493535 } ;
494536
495- Ok ( UniformGrid :: from_aabb ( & domain_aabb, cube_size) ?)
537+ // Ensure that we have enough margin around the particles such that the every particle's kernel support is completely in the domain
538+ let kernel_margin =
539+ density_map:: compute_kernel_evaluation_radius :: < I , R > ( compact_support_radius, cube_size)
540+ . kernel_evaluation_radius ;
541+ particle_aabb. grow_uniformly ( kernel_margin) ;
542+
543+ Ok ( UniformGrid :: from_aabb ( & particle_aabb, cube_size) ?)
496544}
0 commit comments