use geomutil_util::{Point2D, Triangle2D, points_average, points_bounding_box, points_unique};
use std::collections::HashMap;
pub struct Triangulation2D {
pub bounding_triangle: Triangle2D,
pub triangles: Vec<Triangle2D>,
}
impl Triangulation2D {
fn new(bounding_triangle: Triangle2D) -> Self {
Self {
bounding_triangle: bounding_triangle.clone(),
triangles: vec![bounding_triangle],
}
}
fn add(&mut self, point: Point2D) {
let mut triangles_to_keep = Vec::with_capacity(self.triangles.len());
let mut triangles_to_add_edges = HashMap::with_capacity(self.triangles.len() * 2);
for t in self.triangles.drain(..) {
if t.is_inside_circumcircle(point) {
for e in t.edges() {
let canonical_edge = e.canonical();
*triangles_to_add_edges.entry(canonical_edge).or_insert(0) += 1;
}
} else {
triangles_to_keep.push(t);
}
}
self.triangles = triangles_to_keep;
let new_triangles = triangles_to_add_edges
.into_iter()
.filter(|(_, c)| c.eq(&1))
.map(|(e, _)| Triangle2D::new(e.a, e.b, point).unwrap());
self.triangles.extend(new_triangles);
}
fn finalize(&mut self) {
self.triangles.retain(|t| {
!(t.has_point(&self.bounding_triangle.a)
|| t.has_point(&self.bounding_triangle.b)
|| t.has_point(&self.bounding_triangle.c))
});
}
}
fn get_bounding_triangle(points: &[Point2D]) -> Option<Triangle2D> {
let (lower_bound, upper_bound) = points_bounding_box(points)?;
let d = upper_bound - lower_bound;
let d = 3.0 * d.x.max(d.y);
let center = points_average(&[lower_bound, upper_bound])?;
Triangle2D::new(
Point2D::new(center.x - 0.866 * d, center.y - 0.5 * d),
Point2D::new(center.x + 0.866 * d, center.y - 0.5 * d),
Point2D::new(center.x, center.y + d),
)
}
pub fn triangulate(points: &[Point2D]) -> Option<Triangulation2D> {
let points = points_unique(points);
if points.len() < 3 {
return None;
}
let bounding_triangle = get_bounding_triangle(&points)?;
let mut triangulation = Triangulation2D::new(bounding_triangle);
for point in points {
triangulation.add(point);
}
triangulation.finalize();
Some(triangulation)
}
#[cfg(test)]
mod tests {
use super::*;
use geomutil_util::Point2D;
#[test]
fn test_triangulate_rectangle() {
let p1 = Point2D::new(0.0, 0.0);
let p2 = Point2D::new(10.0, 0.0);
let p3 = Point2D::new(10.0, 10.0);
let p4 = Point2D::new(0.0, 10.0);
let points = vec![p1, p2, p3, p4];
let result = triangulate(&points);
assert!(result.is_some(), "Triangulation should not be None");
let triangulation = result.unwrap();
assert_eq!(
triangulation.triangles.len(),
2,
"A rectangle should be triangulated into 2 triangles"
);
let t1 = &triangulation.triangles[0];
let t2 = &triangulation.triangles[1];
let mut all_points_in_triangulation = std::collections::HashSet::new();
all_points_in_triangulation.insert(t1.a);
all_points_in_triangulation.insert(t1.b);
all_points_in_triangulation.insert(t1.c);
all_points_in_triangulation.insert(t2.a);
all_points_in_triangulation.insert(t2.b);
all_points_in_triangulation.insert(t2.c);
assert!(all_points_in_triangulation.contains(&p1));
assert!(all_points_in_triangulation.contains(&p2));
assert!(all_points_in_triangulation.contains(&p3));
assert!(all_points_in_triangulation.contains(&p4));
let mut all_edges = HashMap::new();
for t in &triangulation.triangles {
for edge in t.edges().iter().map(|e| e.canonical()) {
*all_edges.entry(edge).or_insert(0) += 1;
}
}
let boundary_edges_count = all_edges.values().filter(|&&c| c == 1).count();
let internal_edges_count = all_edges.values().filter(|&&c| c == 2).count();
assert_eq!(boundary_edges_count, 4, "Expected 4 boundary edges");
assert_eq!(internal_edges_count, 1, "Expected 1 internal (shared) edge");
println!(
"Triangulation successful! Triangles: {:?}",
triangulation.triangles
);
}
}