Skip to content

Commit 9c580a3

Browse files
committed
Update check mesh function with non-manifold checks
1 parent d31341b commit 9c580a3

File tree

4 files changed

+122
-62
lines changed

4 files changed

+122
-62
lines changed

splashsurf/src/reconstruction.rs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ pub struct ReconstructSubcommandArgs {
346346
/// Optional filename for writing the octree used to partition the particles to disk
347347
#[arg(help_heading = ARGS_DEBUG, long, value_parser = value_parser!(PathBuf))]
348348
pub output_octree: Option<PathBuf>,
349-
/// Enable checking the final mesh for topological problems such as holes (note that when stitching is disabled this will lead to a lot of reported problems)
349+
/// Enable checking the final mesh for holes and non-manifold edges and vertices
350350
#[arg(
351351
help_heading = ARGS_DEBUG,
352352
long,
@@ -356,6 +356,36 @@ pub struct ReconstructSubcommandArgs {
356356
require_equals = true
357357
)]
358358
pub check_mesh: Switch,
359+
/// Enable checking the final mesh for holes
360+
#[arg(
361+
help_heading = ARGS_DEBUG,
362+
long,
363+
default_value = "off",
364+
value_name = "off|on",
365+
ignore_case = true,
366+
require_equals = true
367+
)]
368+
pub check_mesh_closed: Switch,
369+
/// Enable checking the final mesh for non-manifold edges and vertices
370+
#[arg(
371+
help_heading = ARGS_DEBUG,
372+
long,
373+
default_value = "off",
374+
value_name = "off|on",
375+
ignore_case = true,
376+
require_equals = true
377+
)]
378+
pub check_mesh_manifold: Switch,
379+
/// Enable debug output for the check-mesh operations (has no effect if no other check-mesh option is enabled)
380+
#[arg(
381+
help_heading = ARGS_DEBUG,
382+
long,
383+
default_value = "off",
384+
value_name = "off|on",
385+
ignore_case = true,
386+
require_equals = true
387+
)]
388+
pub check_mesh_debug: Switch,
359389
}
360390

361391
#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
@@ -450,7 +480,9 @@ mod arguments {
450480
use walkdir::WalkDir;
451481

452482
pub struct ReconstructionRunnerPostprocessingArgs {
453-
pub check_mesh: bool,
483+
pub check_mesh_closed: bool,
484+
pub check_mesh_manifold: bool,
485+
pub check_mesh_debug: bool,
454486
pub mesh_cleanup: bool,
455487
pub decimate_barnacles: bool,
456488
pub keep_vertices: bool,
@@ -601,7 +633,11 @@ mod arguments {
601633
}
602634

603635
let postprocessing = ReconstructionRunnerPostprocessingArgs {
604-
check_mesh: args.check_mesh.into_bool(),
636+
check_mesh_closed: args.check_mesh.into_bool()
637+
|| args.check_mesh_closed.into_bool(),
638+
check_mesh_manifold: args.check_mesh.into_bool()
639+
|| args.check_mesh_manifold.into_bool(),
640+
check_mesh_debug: args.check_mesh_debug.into_bool(),
605641
mesh_cleanup: args.mesh_cleanup.into_bool(),
606642
decimate_barnacles: args.decimate_barnacles.into_bool(),
607643
keep_vertices: args.keep_verts.into_bool(),
@@ -1455,11 +1491,18 @@ pub(crate) fn reconstruction_pipeline_generic<I: Index, R: Real>(
14551491
info!("Done.");
14561492
}
14571493

1458-
if postprocessing.check_mesh {
1494+
if postprocessing.check_mesh_closed
1495+
|| postprocessing.check_mesh_manifold
1496+
|| postprocessing.check_mesh_debug
1497+
{
14591498
if let Err(err) = match (&tri_mesh, &tri_quad_mesh) {
1460-
(Some(mesh), None) => {
1461-
splashsurf_lib::marching_cubes::check_mesh_consistency(grid, &mesh.mesh)
1462-
}
1499+
(Some(mesh), None) => splashsurf_lib::marching_cubes::check_mesh_consistency(
1500+
grid,
1501+
&mesh.mesh,
1502+
postprocessing.check_mesh_closed,
1503+
postprocessing.check_mesh_manifold,
1504+
postprocessing.check_mesh_debug,
1505+
),
14631506
(None, Some(_mesh)) => {
14641507
info!("Checking for mesh consistency not implemented for quad mesh at the moment.");
14651508
return Ok(());
@@ -1468,7 +1511,7 @@ pub(crate) fn reconstruction_pipeline_generic<I: Index, R: Real>(
14681511
} {
14691512
return Err(anyhow!("{}", err));
14701513
} else {
1471-
info!("Checked mesh for problems (holes, etc.), no problems were found.");
1514+
info!("Checked mesh for problems (holes: {}, non-manifold edges/vertices: {}), no problems were found.", postprocessing.check_mesh_closed, postprocessing.check_mesh_manifold);
14721515
}
14731516
}
14741517

splashsurf_lib/src/marching_cubes.rs

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -195,21 +195,39 @@ pub(crate) fn triangulate_density_map_to_surface_patch<I: Index, R: Real>(
195195
})
196196
}
197197

198-
/// Checks the consistency of the mesh (currently only checks for holes) and returns a string with debug information in case of problems
198+
/// Checks the consistency of the mesh (currently checks for holes, non-manifold edges and vertices) and returns a string with debug information in case of problems
199199
pub fn check_mesh_consistency<I: Index, R: Real>(
200200
grid: &UniformGrid<I, R>,
201201
mesh: &TriMesh3d<R>,
202+
check_closed: bool,
203+
check_manifold: bool,
204+
debug: bool,
202205
) -> Result<(), String> {
203206
profile!("check_mesh_consistency");
204-
let boundary_edges = mesh.find_boundary_edges();
205-
206-
if boundary_edges.is_empty() {
207+
let edge_info = mesh.compute_edge_information();
208+
209+
let boundary_edges = edge_info
210+
.iter()
211+
.filter(|ei| ei.incident_faces == 1)
212+
.map(|ei| (ei.edge, ei.face, ei.local_edge_index))
213+
.collect::<Vec<_>>();
214+
let non_manifold_edges = edge_info
215+
.iter()
216+
.filter(|ei| ei.incident_faces > 2)
217+
.map(|ei| (ei.edge, ei.face, ei.local_edge_index))
218+
.collect::<Vec<_>>();
219+
220+
let non_manifold_vertices = mesh.find_non_manifold_vertices();
221+
222+
if (!check_closed || boundary_edges.is_empty())
223+
&& (!check_manifold || (non_manifold_edges.is_empty() && non_manifold_vertices.is_empty()))
224+
{
207225
return Ok(());
208226
}
209227

210-
let mut error_string = String::new();
211-
error_string += &format!("Mesh is not closed. It has {} boundary edges (edges that are connected to only one triangle):", boundary_edges.len());
212-
for (edge, tri_idx, _) in boundary_edges {
228+
let add_edge_errors = |error_string: &mut String, edge: ([usize; 2], usize, usize)| {
229+
let (edge, tri_idx, _) = edge;
230+
213231
let v0 = mesh.vertices[edge[0]];
214232
let v1 = mesh.vertices[edge[1]];
215233
let center = (v0 + v1) / (R::one() + R::one());
@@ -221,13 +239,43 @@ pub fn check_mesh_consistency<I: Index, R: Real>(
221239
let cell_center = grid.point_coordinates(&point_index)
222240
+ &Vector3::repeat(grid.cell_size().times_f64(0.5));
223241

224-
error_string += &format!("\n\tTriangle {}, boundary edge {:?} is located in cell with {:?} with center coordinates {:?} and edge length {}.", tri_idx, edge, cell_index, cell_center, grid.cell_size());
242+
*error_string += &format!("\n\tTriangle {}, boundary edge {:?} is located in cell with {:?} with center coordinates {:?} and edge length {}.", tri_idx, edge, cell_index, cell_center, grid.cell_size());
225243
} else {
226-
error_string += &format!(
227-
"\n\tCannot get cell index for boundary edge {:?} of triangle {}",
244+
*error_string += &format!(
245+
"\n\tCannot get cell index for edge {:?} of triangle {}",
228246
edge, tri_idx
229247
);
230248
}
249+
};
250+
251+
let mut error_string = String::new();
252+
253+
if check_closed && !boundary_edges.is_empty() {
254+
error_string += &format!("Mesh is not closed. It has {} boundary edges (edges that are connected to only one triangle).", boundary_edges.len());
255+
if debug {
256+
for e in boundary_edges {
257+
add_edge_errors(&mut error_string, e);
258+
}
259+
}
260+
error_string += &format!("\n");
261+
}
262+
263+
if check_manifold && !non_manifold_edges.is_empty() {
264+
error_string += &format!("Mesh is not manifold. It has {} non-manifold edges (edges that are connected to more than twi triangles).", non_manifold_edges.len());
265+
if debug {
266+
for e in non_manifold_edges {
267+
add_edge_errors(&mut error_string, e);
268+
}
269+
}
270+
error_string += &format!("\n");
271+
}
272+
273+
if check_manifold && !non_manifold_vertices.is_empty() {
274+
error_string += &format!("Mesh is not manifold. It has {} non-manifold vertices (vertices with more than one triangle fan).", non_manifold_vertices.len());
275+
if debug {
276+
error_string += &format!("\n\t{:?}", non_manifold_vertices);
277+
}
278+
error_string += &format!("\n");
231279
}
232280

233281
Err(error_string)
@@ -240,7 +288,13 @@ fn check_mesh_with_cell_data<I: Index, R: Real>(
240288
marching_cubes_data: &MarchingCubesInput<I>,
241289
mesh: &TriMesh3d<R>,
242290
) -> Result<(), String> {
243-
let boundary_edges = mesh.find_boundary_edges();
291+
let edge_info = mesh.compute_edge_information();
292+
293+
let boundary_edges = edge_info
294+
.iter()
295+
.filter(|ei| ei.incident_faces == 1)
296+
.map(|ei| (ei.edge, ei.face, ei.local_edge_index))
297+
.collect::<Vec<_>>();
244298

245299
if boundary_edges.is_empty() {
246300
return Ok(());
@@ -263,7 +317,7 @@ fn check_mesh_with_cell_data<I: Index, R: Real>(
263317
let cell_data = marching_cubes_data
264318
.cell_data
265319
.get(&grid.flatten_cell_index(&cell_index))
266-
.expect("Unabel to get cell data of cell");
320+
.expect("Unable to get cell data of cell");
267321

268322
error_string += &format!("\n\tTriangle {}, boundary edge {:?} is located in cell with {:?} with center coordinates {:?} and edge length {}. {:?}", tri_idx, edge, cell_index, cell_center, grid.cell_size(), cell_data);
269323
} else {

splashsurf_lib/src/mesh.rs

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -987,27 +987,6 @@ impl<R: Real> TriMesh3d<R> {
987987
}
988988
}
989989

990-
/// Returns all boundary edges of the mesh
991-
///
992-
/// Returns edges which are only connected to exactly one triangle, along with the connected triangle
993-
/// index and the local index of the edge within that triangle.
994-
///
995-
/// Note that the output order is not necessarily deterministic due to the internal use of hashmaps.
996-
pub fn find_boundary_edges(&self) -> Vec<([usize; 2], usize, usize)> {
997-
let MeshEdgeInformation {
998-
edge_counts,
999-
edge_info,
1000-
} = self.compute_edge_information();
1001-
1002-
// Take only the faces which have a count of 1, which correspond to boundary faces
1003-
edge_counts
1004-
.into_iter()
1005-
.map(|(_edge, value)| value)
1006-
.filter(|&(_, count)| count == 1)
1007-
.map(move |(edge_idx, _)| edge_info[edge_idx].clone())
1008-
.collect()
1009-
}
1010-
1011990
/// Returns all non-manifold vertices of this mesh
1012991
///
1013992
/// A non-manifold vertex is generated by pinching two surface sheets together at that vertex
@@ -1170,23 +1149,6 @@ mod tri_mesh_tests {
11701149
}
11711150
}
11721151

1173-
#[test]
1174-
fn test_tri_mesh_find_boundary() {
1175-
let mesh = mesh_one_tri();
1176-
1177-
let mut boundary = mesh.find_boundary_edges();
1178-
boundary.sort_unstable();
1179-
1180-
assert_eq!(
1181-
boundary,
1182-
vec![
1183-
([0usize, 1usize], 0, 0),
1184-
([1usize, 2usize], 0, 1),
1185-
([2usize, 0usize], 0, 2),
1186-
]
1187-
);
1188-
}
1189-
11901152
#[test]
11911153
fn test_tri_mesh_edge_info() {
11921154
let mesh = mesh_non_manifold_edge();

splashsurf_lib/tests/integration_tests/test_full.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,11 @@ macro_rules! generate_test {
133133
let reconstruction =
134134
reconstruct_surface::<i64, _>(particle_positions.as_slice(), &parameters).unwrap();
135135

136-
write_vtk(reconstruction.mesh(), output_file, "mesh").unwrap();
136+
write_vtk(reconstruction.mesh(), &output_file, "mesh").unwrap();
137137

138138
println!(
139-
"Reconstructed mesh from particle file '{}' with {} particles has {} triangles.",
139+
"Reconstructed mesh '{}' from particle file '{}' with {} particles has {} triangles.",
140+
output_file.display(),
140141
$input_file,
141142
particle_positions.len(),
142143
reconstruction.mesh().triangles.len()
@@ -158,10 +159,10 @@ macro_rules! generate_test {
158159

159160
if test_for_boundary(&parameters) {
160161
// Ensure that the mesh does not have a boundary
161-
if let Err(e) = check_mesh_consistency(reconstruction.grid(), reconstruction.mesh())
162+
if let Err(e) = check_mesh_consistency(reconstruction.grid(), reconstruction.mesh(), true, true, true)
162163
{
163164
eprintln!("{}", e);
164-
panic!("Mesh contains boundary edges");
165+
panic!("Mesh contains topological/manifold errors");
165166
}
166167
}
167168
}

0 commit comments

Comments
 (0)