Skip to content

Commit a5f200d

Browse files
committed
Add option to check for flipped normals
1 parent 6a1f8e6 commit a5f200d

File tree

2 files changed

+87
-10
lines changed

2 files changed

+87
-10
lines changed

splashsurf/src/reconstruction.rs

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::reconstruction::arguments::*;
12
use crate::{io, logging};
23
use anyhow::{anyhow, Context};
34
use clap::value_parser;
@@ -9,11 +10,10 @@ use splashsurf_lib::nalgebra::{Unit, Vector3};
910
use splashsurf_lib::sph_interpolation::SphInterpolator;
1011
use splashsurf_lib::{profile, Aabb3d, Index, Real};
1112
use std::borrow::Cow;
13+
use std::collections::HashMap;
1214
use std::convert::TryFrom;
1315
use std::path::PathBuf;
1416

15-
use arguments::*;
16-
1717
// TODO: Detect smallest index type (i.e. check if ok to use i32 as index)
1818

1919
static ARGS_IO: &str = "Input/output";
@@ -318,7 +318,17 @@ pub struct ReconstructSubcommandArgs {
318318
require_equals = true
319319
)]
320320
pub check_mesh_manifold: Switch,
321-
/// Enable debug output for the check-mesh operations (has no effect if no other check-mesh option is enabled)
321+
/// Enable checking the final mesh for inverted triangles (compares angle between vertex normals and adjacent face normals)
322+
#[arg(
323+
help_heading = ARGS_DEBUG,
324+
long,
325+
default_value = "off",
326+
value_name = "off|on",
327+
ignore_case = true,
328+
require_equals = true
329+
)]
330+
pub check_mesh_orientation: Switch,
331+
/// Enable additional debug output for the check-mesh operations (has no effect if no other check-mesh option is enabled)
322332
#[arg(
323333
help_heading = ARGS_DEBUG,
324334
long,
@@ -427,6 +437,7 @@ mod arguments {
427437
pub struct ReconstructionRunnerPostprocessingArgs {
428438
pub check_mesh_closed: bool,
429439
pub check_mesh_manifold: bool,
440+
pub check_mesh_orientation: bool,
430441
pub check_mesh_debug: bool,
431442
pub mesh_cleanup: bool,
432443
pub decimate_barnacles: bool,
@@ -552,6 +563,8 @@ mod arguments {
552563
|| args.check_mesh_closed.into_bool(),
553564
check_mesh_manifold: args.check_mesh.into_bool()
554565
|| args.check_mesh_manifold.into_bool(),
566+
check_mesh_orientation: args.check_mesh.into_bool()
567+
|| args.check_mesh_orientation.into_bool(),
555568
check_mesh_debug: args.check_mesh_debug.into_bool(),
556569
mesh_cleanup: args.mesh_cleanup.into_bool(),
557570
decimate_barnacles: args.decimate_barnacles.into_bool(),
@@ -897,10 +910,6 @@ pub(crate) fn reconstruction_pipeline_generic<I: Index, R: Real>(
897910
) -> Result<(), anyhow::Error> {
898911
profile!("surface reconstruction");
899912

900-
let check_mesh = postprocessing.check_mesh_closed
901-
|| postprocessing.check_mesh_manifold
902-
|| postprocessing.check_mesh_debug;
903-
904913
// Load particle positions and attributes to interpolate
905914
let (particle_positions, attributes) = io::read_particle_positions_with_attributes(
906915
&paths.input_file,
@@ -1294,7 +1303,9 @@ pub(crate) fn reconstruction_pipeline_generic<I: Index, R: Real>(
12941303
})?;
12951304
}
12961305

1297-
if check_mesh {
1306+
// TODO: Option to continue processing sequences even if checks fail. Maybe return special error type?
1307+
1308+
if postprocessing.check_mesh_closed || postprocessing.check_mesh_manifold {
12981309
if let Err(err) = match (&tri_mesh, &tri_quad_mesh) {
12991310
(Some(mesh), None) => splashsurf_lib::marching_cubes::check_mesh_consistency(
13001311
grid,
@@ -1319,5 +1330,71 @@ pub(crate) fn reconstruction_pipeline_generic<I: Index, R: Real>(
13191330
}
13201331
}
13211332

1333+
if postprocessing.check_mesh_orientation {
1334+
if let Err(err) = match (&tri_mesh, &tri_quad_mesh) {
1335+
(Some(mesh), None) => {
1336+
use splashsurf_lib::mesh::TriMesh3dExt;
1337+
1338+
let tri_normals = mesh
1339+
.mesh
1340+
.triangles
1341+
.par_iter()
1342+
.map(|ijk| mesh.mesh.tri_normal_ijk::<R>(ijk))
1343+
.collect::<Vec<_>>();
1344+
let vertex_face_map = mesh.vertex_cell_connectivity();
1345+
let vertex_normals = mesh.mesh.par_vertex_normals();
1346+
1347+
let mut flipped_faces = HashMap::new();
1348+
for i in 0..vertex_normals.len() {
1349+
let n1 = vertex_normals[i];
1350+
for j in 0..vertex_face_map[i].len() {
1351+
let tri = vertex_face_map[i][j];
1352+
let n2 = tri_normals[tri];
1353+
let angle = n1.angle(&n2).to_f64().unwrap();
1354+
if angle > std::f64::consts::PI * 0.99 {
1355+
flipped_faces.insert(tri, (i, angle));
1356+
}
1357+
}
1358+
}
1359+
1360+
if !flipped_faces.is_empty() {
1361+
let mut error_strings = Vec::new();
1362+
error_strings.push(format!("Mesh is not consistently oriented. Found {} faces with normals flipped relative to adjacent vertices.", flipped_faces.len()));
1363+
if postprocessing.check_mesh_debug {
1364+
for (tri, (i, angle)) in flipped_faces.iter() {
1365+
error_strings.push(format!(
1366+
"\tAngle between normals of face {} and vertex {} is {:.2}°",
1367+
tri,
1368+
i,
1369+
angle.to_degrees()
1370+
));
1371+
}
1372+
}
1373+
Err(anyhow!(error_strings.join("\n")))
1374+
} else {
1375+
Ok(())
1376+
}
1377+
}
1378+
(None, Some(_mesh)) => {
1379+
info!(
1380+
"Checking for normal orientation not implemented for quad mesh at the moment."
1381+
);
1382+
return Ok(());
1383+
}
1384+
_ => unreachable!(),
1385+
} {
1386+
error!("Checked mesh orientation (flipped normals), problems were found!");
1387+
error!("{}", err);
1388+
return Err(anyhow!("{}", err))
1389+
.context("Checked mesh orientation (flipped normals), problems were found!")
1390+
.context(format!(
1391+
"Problem found with mesh file \"{}\"",
1392+
paths.output_file.display()
1393+
));
1394+
} else {
1395+
info!("Checked mesh orientation (flipped normals), no problems were found.");
1396+
}
1397+
}
1398+
13221399
Ok(())
13231400
}

splashsurf_lib/src/marching_cubes.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,10 @@ pub fn check_mesh_consistency<I: Index, R: Real>(
169169
let cell_center = grid.point_coordinates(&point_index)
170170
+ Vector3::repeat(grid.cell_size().times_f64(0.5));
171171

172-
error_strings.push(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()));
172+
error_strings.push(format!("\tTriangle {}, boundary edge {:?} is located in cell with {:?} with center coordinates {:?} and edge length {}.", tri_idx, edge, cell_index, cell_center, grid.cell_size()));
173173
} else {
174174
error_strings.push(format!(
175-
"\n\tCannot get cell index for edge {:?} of triangle {}",
175+
"\tCannot get cell index for edge {:?} of triangle {}",
176176
edge, tri_idx
177177
));
178178
}

0 commit comments

Comments
 (0)