use axiolid_mesh::{component_count, EdgeAdjacency, TriMesh};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum GenusError {
#[error("genus requires a closed two-manifold; found {boundary} boundary and {non_manifold} non-manifold edges")]
NotClosedManifold {
boundary: usize,
non_manifold: usize,
},
#[error("Euler characteristic {characteristic} is odd; the surface is not orientable")]
NotOrientable {
characteristic: i64,
},
#[error(
"genus requires a single connected component; found {components} \
(Euler characteristic {characteristic}). Use `decompose` and take \
the genus of each component separately."
)]
MultipleComponents {
components: usize,
characteristic: i64,
},
}
pub fn genus(mesh: &TriMesh) -> Result<u32, GenusError> {
let adjacency = EdgeAdjacency::build(mesh);
let boundary = adjacency.boundary_edges().count();
let non_manifold = adjacency.non_manifold_edges().count();
if boundary > 0 || non_manifold > 0 {
return Err(GenusError::NotClosedManifold {
boundary,
non_manifold,
});
}
let characteristic = adjacency.euler_characteristic();
let components = component_count(mesh);
if components != 1 {
return Err(GenusError::MultipleComponents {
components,
characteristic,
});
}
let doubled = 2 - characteristic;
if doubled % 2 != 0 {
return Err(GenusError::NotOrientable { characteristic });
}
u32::try_from(doubled / 2).map_err(|_| GenusError::NotOrientable { characteristic })
}