use crate::marching_cubes::narrow_band_extraction::construct_mc_input;
use crate::marching_cubes::triangulation::triangulate;
use crate::mesh::TriMesh3d;
use crate::{DensityMap, Index, MapType, Real, UniformGrid, new_map, profile};
use nalgebra::Vector3;
use thiserror::Error as ThisError;
pub mod marching_cubes_lut;
mod narrow_band_extraction;
mod triangulation;
pub use triangulation::TriangulationError;
#[derive(Debug, ThisError)]
pub enum MarchingCubesError {
#[error("error during triangulation stage")]
TriangulationError(
#[source]
#[from]
TriangulationError,
),
}
#[derive(Clone, Debug)]
pub(crate) struct MarchingCubesInput<I: Index> {
pub(crate) cell_data: MapType<I, CellData>,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub(crate) enum RelativeToThreshold {
Below,
Indeterminate,
Above,
}
impl RelativeToThreshold {
fn is_above_unchecked(&self) -> bool {
match self {
RelativeToThreshold::Below => false,
RelativeToThreshold::Above => true,
RelativeToThreshold::Indeterminate => {
panic!("Trying to evaluate cell data with indeterminate data!")
}
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct CellData {
pub(crate) iso_surface_vertices: [Option<usize>; 12],
pub(crate) corner_above_threshold: [RelativeToThreshold; 8],
}
impl CellData {
fn are_vertices_above_unchecked(&self) -> [bool; 8] {
[
self.corner_above_threshold[0].is_above_unchecked(),
self.corner_above_threshold[1].is_above_unchecked(),
self.corner_above_threshold[2].is_above_unchecked(),
self.corner_above_threshold[3].is_above_unchecked(),
self.corner_above_threshold[4].is_above_unchecked(),
self.corner_above_threshold[5].is_above_unchecked(),
self.corner_above_threshold[6].is_above_unchecked(),
self.corner_above_threshold[7].is_above_unchecked(),
]
}
}
impl Default for CellData {
fn default() -> Self {
CellData {
iso_surface_vertices: [None; 12],
corner_above_threshold: [RelativeToThreshold::Indeterminate; 8],
}
}
}
impl<I: Index> Default for MarchingCubesInput<I> {
fn default() -> Self {
Self {
cell_data: new_map(),
}
}
}
pub fn triangulate_density_map<I: Index, R: Real>(
grid: &UniformGrid<I, R>,
density_map: &DensityMap<I, R>,
iso_surface_threshold: R,
) -> Result<TriMesh3d<R>, MarchingCubesError> {
profile!("triangulate_density_map");
let mut mesh = TriMesh3d::default();
triangulate_density_map_append(grid, density_map, iso_surface_threshold, &mut mesh)?;
Ok(mesh)
}
pub fn triangulate_density_map_append<I: Index, R: Real>(
grid: &UniformGrid<I, R>,
density_map: &DensityMap<I, R>,
iso_surface_threshold: R,
mesh: &mut TriMesh3d<R>,
) -> Result<(), MarchingCubesError> {
profile!("triangulate_density_map_append");
let marching_cubes_data =
construct_mc_input(grid, density_map, iso_surface_threshold, &mut mesh.vertices);
triangulate(marching_cubes_data, mesh)?;
Ok(())
}
pub fn check_mesh_consistency<I: Index, R: Real>(
grid: &UniformGrid<I, R>,
mesh: &TriMesh3d<R>,
check_closed: bool,
check_manifold: bool,
debug: bool,
) -> Result<(), String> {
profile!("check_mesh_consistency");
let edge_info = mesh.compute_edge_information();
let boundary_edges = edge_info
.iter()
.filter(|ei| ei.incident_faces == 1)
.map(|ei| (ei.edge, ei.face, ei.local_edge_index))
.collect::<Vec<_>>();
let non_manifold_edges = edge_info
.iter()
.filter(|ei| ei.incident_faces > 2)
.map(|ei| (ei.edge, ei.face, ei.local_edge_index))
.collect::<Vec<_>>();
let non_manifold_vertices = mesh.find_non_manifold_vertices();
if (!check_closed || boundary_edges.is_empty())
&& (!check_manifold || (non_manifold_edges.is_empty() && non_manifold_vertices.is_empty()))
{
return Ok(());
}
let add_edge_errors = |error_strings: &mut Vec<String>, edge: ([usize; 2], usize, usize)| {
let (edge, tri_idx, _) = edge;
let v0 = mesh.vertices[edge[0]];
let v1 = mesh.vertices[edge[1]];
let center = (v0 + v1) / (R::one() + R::one());
let cell = grid.enclosing_cell(¢er);
if let Some(cell_index) = grid.get_cell(cell) {
let point_index = grid
.get_point(*cell_index.index())
.expect("Unable to get point index of cell");
let cell_center = grid.point_coordinates(&point_index)
+ Vector3::repeat(grid.cell_size() * R::from_float(0.5));
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()));
} else {
error_strings.push(format!(
"\tCannot get cell index for edge {:?} of triangle {}",
edge, tri_idx
));
}
};
let mut error_strings = Vec::new();
if check_closed && !boundary_edges.is_empty() {
error_strings.push(format!("Mesh is not closed. It has {} boundary edges (edges that are connected to only one triangle).", boundary_edges.len()));
if debug {
for e in boundary_edges {
add_edge_errors(&mut error_strings, e);
}
}
}
if check_manifold && !non_manifold_edges.is_empty() {
error_strings.push(format!("Mesh is not manifold. It has {} non-manifold edges (edges that are connected to more than two triangles).", non_manifold_edges.len()));
if debug {
for e in non_manifold_edges {
add_edge_errors(&mut error_strings, e);
}
}
}
if check_manifold && !non_manifold_vertices.is_empty() {
error_strings.push(format!("Mesh is not manifold. It has {} non-manifold vertices (vertices with more than one triangle fan).", non_manifold_vertices.len()));
if debug {
error_strings.push(format!(
"\tNon-manifold vertices: {:?}",
non_manifold_vertices
));
}
}
let error_string = error_strings.join("\n");
Err(error_string)
}
#[allow(unused)]
fn check_mesh_with_cell_data<I: Index, R: Real>(
grid: &UniformGrid<I, R>,
marching_cubes_data: &MarchingCubesInput<I>,
mesh: &TriMesh3d<R>,
) -> Result<(), String> {
let edge_info = mesh.compute_edge_information();
let boundary_edges = edge_info
.iter()
.filter(|ei| ei.incident_faces == 1)
.map(|ei| (ei.edge, ei.face, ei.local_edge_index))
.collect::<Vec<_>>();
if boundary_edges.is_empty() {
return Ok(());
}
let mut error_string = String::new();
error_string += &format!(
"Mesh is not closed. It has {} boundary edges (edges that are connected to only one triangle):",
boundary_edges.len()
);
for (edge, tri_idx, _) in boundary_edges {
let v0 = mesh.vertices[edge[0]];
let v1 = mesh.vertices[edge[1]];
let center = (v0 + v1) / (R::one() + R::one());
let cell = grid.enclosing_cell(¢er);
if let Some(cell_index) = grid.get_cell(cell) {
let point_index = grid
.get_point(*cell_index.index())
.expect("Unable to get point index of cell");
let cell_center = grid.point_coordinates(&point_index)
+ Vector3::repeat(grid.cell_size() * R::from_float(0.5));
let cell_data = marching_cubes_data
.cell_data
.get(&grid.flatten_cell_index(&cell_index))
.expect("Unable to get cell data of cell");
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
);
} else {
error_string += &format!(
"\n\tCannot get cell index for boundary edge {:?} of triangle {}",
edge, tri_idx
);
}
}
Err(error_string)
}
#[allow(unused)]
#[inline(never)]
fn assert_cell_data_point_data_consistency<I: Index, R: Real>(
density_map: &DensityMap<I, R>,
cell_data: &MapType<I, CellData>,
grid: &UniformGrid<I, R>,
iso_surface_threshold: R,
) {
for (&flat_cell_index, cell_data) in cell_data {
let mut has_missing_point_data = false;
let mut has_point_data_above_threshold = false;
let cell = grid.try_unflatten_cell_index(flat_cell_index).unwrap();
for i in 0..8 {
let point = cell.global_point_index_of(i).unwrap();
let flat_point_index = grid.flatten_point_index(&point);
if let Some(point_value) = density_map.get(flat_point_index) {
if point_value > iso_surface_threshold {
has_point_data_above_threshold = true;
}
} else {
has_missing_point_data = true;
}
}
assert!(!(has_missing_point_data && has_point_data_above_threshold));
let mut has_point_above_threshold = false;
for &flag_above in cell_data.corner_above_threshold.iter() {
if let RelativeToThreshold::Above = flag_above {
has_point_above_threshold = true;
}
}
assert!(!(has_missing_point_data && has_point_above_threshold));
let mut has_iso_surface_vertex = false;
for vertex in cell_data.iso_surface_vertices.iter() {
if vertex.is_some() {
has_iso_surface_vertex = true;
}
}
assert!(!(has_missing_point_data && has_iso_surface_vertex));
}
}
#[test]
fn test_interpolate_cell_data() {
use nalgebra::Vector3;
let iso_surface_threshold = 0.25;
let mut trimesh = crate::TriMesh3d::default();
let origin = Vector3::new(0.0, 0.0, 0.0);
let n_cubes_per_dim = [1, 1, 1];
let cube_size = 1.0;
let grid = UniformGrid::<i32, f64>::new(&origin, &n_cubes_per_dim, cube_size).unwrap();
assert_eq!(grid.aabb().max(), &Vector3::new(1.0, 1.0, 1.0));
let mut sparse_data = new_map();
let marching_cubes_data = construct_mc_input(
&grid,
&sparse_data.clone().into(),
iso_surface_threshold,
&mut trimesh.vertices,
);
assert_eq!(trimesh.vertices.len(), 0);
assert_eq!(marching_cubes_data.cell_data.len(), 0);
let points = vec![
([0, 0, 0], 0.0),
([1, 0, 0], 0.75),
([1, 1, 0], 1.0),
([0, 1, 0], 0.5),
([0, 0, 1], 0.0),
([1, 0, 1], 0.0),
([1, 1, 1], 1.0),
([0, 1, 1], 0.0),
];
for (ijk, val) in points {
sparse_data.insert(grid.flatten_point_index_array(&ijk), val);
}
let marching_cubes_data = construct_mc_input(
&grid,
&sparse_data.clone().into(),
iso_surface_threshold,
&mut trimesh.vertices,
);
assert_eq!(marching_cubes_data.cell_data.len(), 1);
assert_eq!(trimesh.vertices.len(), 6);
let cell = &marching_cubes_data.cell_data[&0];
assert_eq!(
cell.corner_above_threshold
.iter()
.map(|r| r.is_above_unchecked())
.collect::<Vec<_>>(),
vec![false, true, true, true, false, false, true, false]
);
assert!(cell.iso_surface_vertices[0].is_some());
assert!(cell.iso_surface_vertices[3].is_some());
assert!(cell.iso_surface_vertices[5].is_some());
assert!(cell.iso_surface_vertices[6].is_some());
assert!(cell.iso_surface_vertices[9].is_some());
assert!(cell.iso_surface_vertices[11].is_some());
let _mesh = triangulate(marching_cubes_data, &mut trimesh);
}