Skip to content

Commit 73bc254

Browse files
authored
Merge pull request #201 from InteractiveComputerGraphics/remove_octree
Remove octree-based domain decomposition
2 parents e668d88 + 0469cb7 commit 73bc254

25 files changed

+256
-4700
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
The following changes are present in the `main` branch of the repository and are not yet part of a release:
44

5-
- N/A
5+
- Lib: Remove octree-based domain decomposition (superseded by regular grid subdivision approach `--subdomain-grid`)
6+
- CLI: Set `--subdomain-grid=on` by default
7+
- CLI: Remove all arguments for octree-based domain decomposition
8+
- CLI: Remove options to output some debug files (octree grid, density map, etc.)
69

710
## Version 0.10.0
811

splashsurf/src/reconstruction.rs

Lines changed: 7 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ use clap::value_parser;
44
use indicatif::{ProgressBar, ProgressStyle};
55
use log::info;
66
use rayon::prelude::*;
7-
use splashsurf_lib::mesh::{AttributeData, Mesh3d, MeshAttribute, MeshWithData, PointCloud3d};
7+
use splashsurf_lib::mesh::{AttributeData, Mesh3d, MeshAttribute, MeshWithData};
88
use splashsurf_lib::nalgebra::{Unit, Vector3};
99
use splashsurf_lib::sph_interpolation::SphInterpolator;
10-
use splashsurf_lib::{density_map, profile, Aabb3d, Index, Real};
10+
use splashsurf_lib::{profile, Aabb3d, Index, Real};
1111
use std::borrow::Cow;
1212
use std::convert::TryFrom;
1313
use std::path::PathBuf;
@@ -122,7 +122,7 @@ pub struct ReconstructSubcommandArgs {
122122
#[arg(
123123
help_heading = ARGS_OCTREE,
124124
long,
125-
default_value = "off",
125+
default_value = "on",
126126
value_name = "off|on",
127127
ignore_case = true,
128128
require_equals = true
@@ -132,55 +132,6 @@ pub struct ReconstructSubcommandArgs {
132132
#[arg(help_heading = ARGS_OCTREE, long, default_value="64")]
133133
pub subdomain_cubes: u32,
134134

135-
/// Enable spatial decomposition using an octree (faster) instead of a global approach
136-
#[arg(
137-
help_heading = ARGS_OCTREE,
138-
long,
139-
default_value = "on",
140-
value_name = "off|on",
141-
ignore_case = true,
142-
require_equals = true
143-
)]
144-
pub octree_decomposition: Switch,
145-
/// Enable stitching of the disconnected local meshes resulting from the reconstruction when spatial decomposition is enabled (slower, but without stitching meshes will not be closed)
146-
#[arg(
147-
help_heading = ARGS_OCTREE,
148-
long,
149-
default_value = "on",
150-
value_name = "off|on",
151-
ignore_case = true,
152-
require_equals = true
153-
)]
154-
pub octree_stitch_subdomains: Switch,
155-
/// The maximum number of particles for leaf nodes of the octree, default is to compute it based on the number of threads and particles
156-
#[arg(help_heading = ARGS_OCTREE, long)]
157-
pub octree_max_particles: Option<usize>,
158-
/// Safety factor applied to the kernel compact support radius when it's used as a margin to collect ghost particles in the leaf nodes when performing the spatial decomposition
159-
#[arg(help_heading = ARGS_OCTREE, long)]
160-
pub octree_ghost_margin_factor: Option<f64>,
161-
/// Enable computing particle densities in a global step before domain decomposition (slower)
162-
#[arg(
163-
help_heading = ARGS_OCTREE,
164-
long,
165-
default_value = "off",
166-
value_name = "off|on",
167-
ignore_case = true,
168-
require_equals = true
169-
)]
170-
pub octree_global_density: Switch,
171-
/// Enable computing particle densities per subdomain but synchronize densities for ghost-particles (faster, recommended).
172-
/// Note: if both this and global particle density computation is disabled the ghost particle margin has to be increased to at least 2.0
173-
/// to compute correct density values for ghost particles.
174-
#[arg(
175-
help_heading = ARGS_OCTREE,
176-
long,
177-
default_value = "on",
178-
value_name = "off|on",
179-
ignore_case = true,
180-
require_equals = true
181-
)]
182-
pub octree_sync_local_density: Switch,
183-
184135
/// Enable omputing surface normals at the mesh vertices and write them to the output file
185136
#[arg(
186137
help_heading = ARGS_INTERP,
@@ -337,15 +288,6 @@ pub struct ReconstructSubcommandArgs {
337288
)]
338289
pub output_raw_mesh: Switch,
339290

340-
/// Optional filename for writing the point cloud representation of the intermediate density map to disk
341-
#[arg(help_heading = ARGS_DEBUG, long, value_parser = value_parser!(PathBuf))]
342-
pub output_dm_points: Option<PathBuf>,
343-
/// Optional filename for writing the grid representation of the intermediate density map to disk
344-
#[arg(help_heading = ARGS_DEBUG, long, value_parser = value_parser!(PathBuf))]
345-
pub output_dm_grid: Option<PathBuf>,
346-
/// Optional filename for writing the octree used to partition the particles to disk
347-
#[arg(help_heading = ARGS_DEBUG, long, value_parser = value_parser!(PathBuf))]
348-
pub output_octree: Option<PathBuf>,
349291
/// Enable checking the final mesh for holes and non-manifold edges and vertices
350292
#[arg(
351293
help_heading = ARGS_DEBUG,
@@ -472,7 +414,7 @@ mod arguments {
472414
use log::info;
473415
use regex::{escape, Regex};
474416
use splashsurf_lib::nalgebra::Vector3;
475-
use splashsurf_lib::{Aabb3d, ParticleDensityComputationStrategy};
417+
use splashsurf_lib::Aabb3d;
476418
use std::convert::TryFrom;
477419
use std::fs;
478420
use std::path::{Path, PathBuf};
@@ -577,37 +519,7 @@ mod arguments {
577519
Some(splashsurf_lib::SpatialDecomposition::UniformGrid(
578520
splashsurf_lib::GridDecompositionParameters {
579521
subdomain_num_cubes_per_dim: args.subdomain_cubes,
580-
},
581-
))
582-
} else if args.octree_decomposition.into_bool() {
583-
let subdivision_criterion = if let Some(max_particles) = args.octree_max_particles {
584-
splashsurf_lib::SubdivisionCriterion::MaxParticleCount(max_particles)
585-
} else {
586-
splashsurf_lib::SubdivisionCriterion::MaxParticleCountAuto
587-
};
588-
let ghost_particle_safety_factor = args.octree_ghost_margin_factor;
589-
let enable_stitching = args.octree_stitch_subdomains.into_bool();
590-
591-
let particle_density_computation = if args.octree_global_density.into_bool()
592-
&& args.octree_sync_local_density.into_bool()
593-
{
594-
return Err(anyhow!("Cannot enable both global and merged local particle density computation at the same time. Switch off at least one."));
595-
} else {
596-
if args.octree_global_density.into_bool() {
597-
ParticleDensityComputationStrategy::Global
598-
} else if args.octree_sync_local_density.into_bool() {
599-
ParticleDensityComputationStrategy::SynchronizeSubdomains
600-
} else {
601-
ParticleDensityComputationStrategy::IndependentSubdomains
602-
}
603-
};
604-
605-
Some(splashsurf_lib::SpatialDecomposition::Octree(
606-
splashsurf_lib::OctreeDecompositionParameters {
607-
subdivision_criterion,
608-
ghost_particle_safety_factor,
609-
enable_stitching,
610-
particle_density_computation,
522+
..Default::default()
611523
},
612524
))
613525
} else {
@@ -673,9 +585,6 @@ mod arguments {
673585
is_sequence: bool,
674586
input_file: PathBuf,
675587
output_file: PathBuf,
676-
output_density_map_points_file: Option<PathBuf>,
677-
output_density_map_grid_file: Option<PathBuf>,
678-
output_octree_file: Option<PathBuf>,
679588
sequence_range: (Option<usize>, Option<usize>),
680589
}
681590

@@ -685,17 +594,11 @@ mod arguments {
685594
input_file: P,
686595
output_base_path: Option<P>,
687596
output_file: P,
688-
output_density_map_points_file: Option<P>,
689-
output_density_map_grid_file: Option<P>,
690-
output_octree_file: Option<P>,
691597
sequence_range: (Option<usize>, Option<usize>),
692598
) -> Result<Self, anyhow::Error> {
693599
let input_file = input_file.into();
694600
let output_base_path = output_base_path.map(|p| p.into());
695601
let output_file = output_file.into();
696-
let output_density_map_points_file = output_density_map_points_file.map(|p| p.into());
697-
let output_density_map_grid_file = output_density_map_grid_file.map(|p| p.into());
698-
let output_octree_file = output_octree_file.map(|p| p.into());
699602

700603
if let (Some(start), Some(end)) = sequence_range {
701604
if start > end {
@@ -727,21 +630,13 @@ mod arguments {
727630
is_sequence,
728631
input_file,
729632
output_file,
730-
output_density_map_points_file: output_density_map_points_file
731-
.map(|f| output_base_path.join(f)),
732-
output_density_map_grid_file: output_density_map_grid_file
733-
.map(|f| output_base_path.join(f)),
734-
output_octree_file: output_octree_file.map(|f| output_base_path.join(f)),
735633
sequence_range,
736634
})
737635
} else {
738636
Ok(Self {
739637
is_sequence,
740638
input_file,
741639
output_file,
742-
output_density_map_points_file,
743-
output_density_map_grid_file,
744-
output_octree_file,
745640
sequence_range,
746641
})
747642
}
@@ -821,14 +716,7 @@ mod arguments {
821716
let output_filename_i = output_pattern.replace("{}", index);
822717
let output_file_i = output_dir.join(output_filename_i);
823718

824-
paths.push(ReconstructionRunnerPaths::new(
825-
input_file_i,
826-
output_file_i,
827-
// Don't write density maps etc. when processing a sequence of files
828-
None,
829-
None,
830-
None,
831-
));
719+
paths.push(ReconstructionRunnerPaths::new(input_file_i, output_file_i));
832720
}
833721
}
834722

@@ -851,9 +739,6 @@ mod arguments {
851739
ReconstructionRunnerPaths::new(
852740
self.input_file.clone(),
853741
self.output_file.clone(),
854-
self.output_density_map_points_file.clone(),
855-
self.output_density_map_grid_file.clone(),
856-
self.output_octree_file.clone(),
857742
);
858743
1
859744
]
@@ -950,9 +835,6 @@ mod arguments {
950835
args.input_file_or_sequence.clone(),
951836
args.output_dir.clone(),
952837
output_filename,
953-
args.output_dm_points.clone(),
954-
args.output_dm_grid.clone(),
955-
args.output_octree.clone(),
956838
(args.start_index, args.end_index),
957839
)
958840
}
@@ -963,25 +845,13 @@ mod arguments {
963845
pub(crate) struct ReconstructionRunnerPaths {
964846
pub input_file: PathBuf,
965847
pub output_file: PathBuf,
966-
pub output_density_map_points_file: Option<PathBuf>,
967-
pub output_density_map_grid_file: Option<PathBuf>,
968-
pub output_octree_file: Option<PathBuf>,
969848
}
970849

971850
impl ReconstructionRunnerPaths {
972-
fn new(
973-
input_file: PathBuf,
974-
output_file: PathBuf,
975-
output_density_map_points_file: Option<PathBuf>,
976-
output_density_map_grid_file: Option<PathBuf>,
977-
output_octree_file: Option<PathBuf>,
978-
) -> Self {
851+
fn new(input_file: PathBuf, output_file: PathBuf) -> Self {
979852
ReconstructionRunnerPaths {
980853
input_file,
981854
output_file,
982-
output_density_map_points_file,
983-
output_density_map_grid_file,
984-
output_octree_file,
985855
}
986856
}
987857
}
@@ -1423,74 +1293,6 @@ pub(crate) fn reconstruction_pipeline_generic<I: Index, R: Real>(
14231293
info!("Done.");
14241294
}
14251295

1426-
// Store octree leaf nodes as hex cells
1427-
if let Some(output_octree_file) = &paths.output_octree_file {
1428-
info!("Writing octree to \"{}\"...", output_octree_file.display());
1429-
io::vtk_format::write_vtk(
1430-
reconstruction.octree().unwrap().hexmesh(grid, true),
1431-
output_octree_file,
1432-
"mesh",
1433-
)
1434-
.with_context(|| {
1435-
format!(
1436-
"Failed to write octree to output file \"{}\"",
1437-
output_octree_file.display()
1438-
)
1439-
})?;
1440-
info!("Done.");
1441-
}
1442-
1443-
// Store point cloud density map
1444-
if let Some(output_density_map_points_file) = &paths.output_density_map_points_file {
1445-
info!("Constructing density map point cloud...");
1446-
let density_map = reconstruction
1447-
.density_map()
1448-
.ok_or_else(|| anyhow::anyhow!("No density map was created during reconstruction"))?;
1449-
1450-
let point_cloud: PointCloud3d<R> = {
1451-
let mut points = Vec::with_capacity(density_map.len());
1452-
density_map.for_each(|flat_point_index, _| {
1453-
let point = grid.try_unflatten_point_index(flat_point_index).unwrap();
1454-
points.push(grid.point_coordinates(&point));
1455-
});
1456-
1457-
PointCloud3d::new(points)
1458-
};
1459-
1460-
info!(
1461-
"Saving density map point cloud to \"{}\"...",
1462-
output_density_map_points_file.display()
1463-
);
1464-
1465-
io::vtk_format::write_vtk(
1466-
&point_cloud,
1467-
output_density_map_points_file,
1468-
"density_map_points",
1469-
)?;
1470-
1471-
info!("Done.");
1472-
}
1473-
1474-
// Store hex-mesh density map
1475-
if let Some(output_density_map_grid_file) = &paths.output_density_map_grid_file {
1476-
info!("Constructing density map hex mesh...");
1477-
let density_map = reconstruction
1478-
.density_map()
1479-
.ok_or_else(|| anyhow::anyhow!("No density map was created during reconstruction"))?;
1480-
1481-
let density_mesh =
1482-
density_map::sparse_density_map_to_hex_mesh(&density_map, &grid, R::zero());
1483-
1484-
info!(
1485-
"Saving density map hex mesh to \"{}\"...",
1486-
output_density_map_grid_file.display()
1487-
);
1488-
1489-
io::vtk_format::write_vtk(density_mesh, output_density_map_grid_file, "density_map")?;
1490-
1491-
info!("Done.");
1492-
}
1493-
14941296
if postprocessing.check_mesh_closed
14951297
|| postprocessing.check_mesh_manifold
14961298
|| postprocessing.check_mesh_debug

splashsurf_lib/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ For each of the features, `splashsurf_lib` re-exports the corresponding dependen
3737

3838
Currently, only one method based on a "spatial hashing" strategy is implemented.
3939

40+
`TODO: This section is missing a description of the domain decomposition for more efficient parallelization`
41+
4042
**Short summary**: The fluid density is evaluated or mapped onto a sparse grid using spatial hashing in the support radius of each fluid particle. This implies that memory is only allocated in areas where the fluid density is non-zero. This is in contrast to a naive approach where the marching cubes background grid is allocated for the whole domain. Finally, the marching cubes reconstruction is performed only in those grid cells where an edge crosses the surface threshold. Cells completely in the interior of the fluid are skipped in the marching cubes phase.
4143

4244
**Individual steps**:
4345
1. Construct a "virtual background grid" with the desired resolution of the marching cubes algorithm. In the end, the procedure will place a single surface mesh vertex on each edge of this virtual grid, where the fluid surface crosses the edge (or rather, where the fluid density crosses the specified threshold). Virtual means that no storage is actually allocated for this grid yet; only its topology is used implicitly later.
4446
2. Compute the density of each fluid particle
45-
- Perform a neighborhood search
46-
- Per particle, evaluate an SPH sum over the neighbors to compute its density (based on input parameters of kernel radius and particle rest mass)
47+
- Perform a neighborhood search
48+
- Per particle, evaluate an SPH sum over the neighbors to compute its density (based on input parameters of kernel radius and particle rest mass)
4749
3. Optional: filter out (or rather mask as inactive) single particles if the user provided a "splash detection radius". This is done by performing an additional neighborhood search using this splash detection radius instead of the kernel radius.
4850
4. Compute a "sparse density map": a map from the index of a vertex of the virtual background grid to the corresponding fluid density value. The map will only contain entries for vertices where the fluid density is non-zero. Construction of the map:
4951
- Iterate over all active particles

0 commit comments

Comments
 (0)