use super::traits::TriangulationQuery;
use std::collections::hash_map::DefaultHasher;
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
fn stable_hash<T: Hash>(value: &T) -> u64 {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
#[derive(Clone, Debug)]
struct UnorderedPair<V>(V, V);
impl<V: Eq> PartialEq for UnorderedPair<V> {
fn eq(&self, other: &Self) -> bool {
(self.0 == other.0 && self.1 == other.1) || (self.0 == other.1 && self.1 == other.0)
}
}
impl<V: Eq> Eq for UnorderedPair<V> {}
impl<V: Hash> Hash for UnorderedPair<V> {
fn hash<H: Hasher>(&self, state: &mut H) {
let a = stable_hash(&self.0);
let b = stable_hash(&self.1);
if a <= b {
state.write_u64(a);
state.write_u64(b);
} else {
state.write_u64(b);
state.write_u64(a);
}
}
}
#[derive(Clone, Debug)]
struct UnorderedSet<V>(Vec<V>);
impl<V: Eq + Hash> PartialEq for UnorderedSet<V> {
fn eq(&self, other: &Self) -> bool {
if self.0.len() != other.0.len() {
return false;
}
let self_set: HashSet<_> = self.0.iter().collect();
other.0.iter().all(|v| self_set.contains(v))
}
}
impl<V: Eq + Hash> Eq for UnorderedSet<V> {}
impl<V: Hash> Hash for UnorderedSet<V> {
fn hash<H: Hasher>(&self, state: &mut H) {
let mut hashes: Vec<u64> = self.0.iter().map(stable_hash).collect();
hashes.sort_unstable();
for h in hashes {
state.write_u64(h);
}
}
}
fn boundary_facets<B: TriangulationQuery + ?Sized>(tri: &B) -> Vec<Vec<B::VertexHandle>> {
type FacetCounts<V> = HashMap<UnorderedSet<V>, (usize, Vec<V>)>;
let mut facet_counts: FacetCounts<B::VertexHandle> = HashMap::new();
for face in tri.faces() {
let Ok(vertices) = tri.face_vertices(&face) else {
continue;
};
if vertices.len() < 2 {
continue;
}
if vertices.len() == 2 {
for v in &vertices {
let facet = vec![v.clone()];
let key = UnorderedSet(facet.clone());
facet_counts
.entry(key)
.and_modify(|(count, _)| *count += 1)
.or_insert((1, facet));
}
continue;
}
for omit in 0..vertices.len() {
let facet: Vec<_> = vertices
.iter()
.enumerate()
.filter(|(i, _)| *i != omit)
.map(|(_, v)| v.clone())
.collect();
let key = UnorderedSet(facet.clone());
facet_counts
.entry(key)
.and_modify(|(count, _)| *count += 1)
.or_insert((1, facet));
}
}
facet_counts
.into_values()
.filter_map(|(count, facet)| (count == 1).then_some(facet))
.collect()
}
pub trait TriangulationOps: TriangulationQuery {
fn is_delaunay(&self) -> bool {
self.is_valid()
}
fn convex_hull(&self) -> Vec<Self::VertexHandle> {
let mut hull_vertices: HashSet<Self::VertexHandle> = HashSet::new();
for facet in boundary_facets(self) {
for v in facet {
hull_vertices.insert(v);
}
}
hull_vertices.into_iter().collect()
}
fn boundary_edges(&self) -> Vec<Self::EdgeHandle> {
let mut edge_by_vertices: HashMap<UnorderedPair<Self::VertexHandle>, Self::EdgeHandle> =
HashMap::new();
for edge in self.edges() {
if let Ok((v1, v2)) = self.edge_endpoints(&edge) {
edge_by_vertices.insert(UnorderedPair(v1, v2), edge);
}
}
let mut boundary: HashSet<Self::EdgeHandle> = HashSet::new();
for facet in boundary_facets(self) {
for i in 0..facet.len() {
for j in (i + 1)..facet.len() {
let key = UnorderedPair(facet[i].clone(), facet[j].clone());
if let Some(edge) = edge_by_vertices.get(&key) {
boundary.insert(edge.clone());
}
}
}
}
boundary.into_iter().collect()
}
}
impl<T: TriangulationQuery> TriangulationOps for T {}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::backends::mock::MockBackend;
use crate::geometry::traits::TriangulationQuery;
use std::collections::HashSet;
#[test]
fn test_is_delaunay_delegates_to_is_valid() {
let backend = MockBackend::create_triangle();
assert!(backend.is_delaunay());
}
#[test]
fn test_convex_hull_triangle() {
let backend = MockBackend::create_triangle();
let hull = backend.convex_hull();
assert_eq!(hull.len(), 3, "Triangle hull should contain 3 vertices");
let all_vertices: HashSet<_> = backend.vertices().collect();
let hull_vertices: HashSet<_> = hull.into_iter().collect();
assert_eq!(
hull_vertices, all_vertices,
"Hull vertices should match the triangulation's vertex set for a single triangle"
);
}
#[test]
fn test_boundary_edges_triangle() {
let backend = MockBackend::create_triangle();
let boundary = backend.boundary_edges();
assert_eq!(boundary.len(), 3, "Triangle should have 3 boundary edges");
let vertices: HashSet<_> = backend.vertices().collect();
for edge in boundary {
let (v1, v2) = backend
.edge_endpoints(&edge)
.expect("Boundary edge handle should be valid");
assert!(
vertices.contains(&v1) && vertices.contains(&v2),
"Boundary edge endpoints should be valid vertices"
);
assert_ne!(v1, v2, "Boundary edge should not be degenerate");
}
}
#[test]
fn test_triangulation_ops_trait_available() {
let backend = MockBackend::create_triangle();
assert!(backend.is_delaunay()); assert_eq!(backend.convex_hull().len(), 3);
assert_eq!(backend.boundary_edges().len(), 3);
let hull: Vec<_> = backend.convex_hull();
let boundary: Vec<_> = backend.boundary_edges();
assert_eq!(hull.len(), 3);
assert_eq!(boundary.len(), 3);
}
}