use crate::error::{AlgorithmError, Result};
use oxigdal_core::vector::{Coordinate, LineString, Point, Polygon};
#[derive(Debug, Clone)]
pub struct DelaunayOptions {
pub compute_quality: bool,
pub min_angle: f64,
}
impl Default for DelaunayOptions {
fn default() -> Self {
Self {
compute_quality: false,
min_angle: 20.0,
}
}
}
#[derive(Debug, Clone)]
pub struct Triangle {
pub vertices: [usize; 3],
pub polygon: Polygon,
pub quality: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct DelaunayTriangulation {
pub points: Vec<Point>,
pub triangles: Vec<Triangle>,
pub num_triangles: usize,
}
pub fn delaunay_triangulation(
points: &[Point],
options: &DelaunayOptions,
) -> Result<DelaunayTriangulation> {
if points.len() < 3 {
return Err(AlgorithmError::InvalidInput(
"Need at least 3 points for triangulation".to_string(),
));
}
let delaunator_points: Vec<delaunator::Point> = points
.iter()
.map(|p| delaunator::Point {
x: p.coord.x,
y: p.coord.y,
})
.collect();
let delaunay = delaunator::triangulate(&delaunator_points);
let mut triangles = Vec::new();
for tri_idx in 0..(delaunay.triangles.len() / 3) {
let a = delaunay.triangles[tri_idx * 3];
let b = delaunay.triangles[tri_idx * 3 + 1];
let c = delaunay.triangles[tri_idx * 3 + 2];
let pa = &points[a];
let pb = &points[b];
let pc = &points[c];
let coords_tri = vec![
Coordinate::new_2d(pa.coord.x, pa.coord.y),
Coordinate::new_2d(pb.coord.x, pb.coord.y),
Coordinate::new_2d(pc.coord.x, pc.coord.y),
Coordinate::new_2d(pa.coord.x, pa.coord.y), ];
let exterior = LineString::new(coords_tri)
.map_err(|e| AlgorithmError::InvalidGeometry(format!("Invalid triangle: {}", e)))?;
let polygon = Polygon::new(exterior, vec![]).map_err(|e| {
AlgorithmError::InvalidGeometry(format!("Invalid triangle polygon: {}", e))
})?;
let quality = if options.compute_quality {
Some(compute_triangle_quality(pa, pb, pc))
} else {
None
};
triangles.push(Triangle {
vertices: [a, b, c],
polygon,
quality,
});
}
let num_triangles = triangles.len();
Ok(DelaunayTriangulation {
points: points.to_vec(),
triangles,
num_triangles,
})
}
fn compute_triangle_quality(pa: &Point, pb: &Point, pc: &Point) -> f64 {
let a = distance(pb, pc);
let b = distance(pc, pa);
let c = distance(pa, pb);
let s = (a + b + c) / 2.0;
let area = (s * (s - a) * (s - b) * (s - c)).sqrt();
let inradius = area / s;
let circumradius = (a * b * c) / (4.0 * area);
if circumradius > 0.0 {
2.0 * inradius / circumradius
} else {
0.0
}
}
fn distance(p1: &Point, p2: &Point) -> f64 {
let dx = p1.coord.x - p2.coord.x;
let dy = p1.coord.y - p2.coord.y;
(dx * dx + dy * dy).sqrt()
}
pub fn in_circumcircle(pa: &Point, pb: &Point, pc: &Point, pd: &Point) -> bool {
let ax = pa.coord.x - pd.coord.x;
let ay = pa.coord.y - pd.coord.y;
let bx = pb.coord.x - pd.coord.x;
let by = pb.coord.y - pd.coord.y;
let cx = pc.coord.x - pd.coord.x;
let cy = pc.coord.y - pd.coord.y;
let det = (ax * ax + ay * ay) * (bx * cy - cx * by) - (bx * bx + by * by) * (ax * cy - cx * ay)
+ (cx * cx + cy * cy) * (ax * by - bx * ay);
det > 0.0
}
pub fn constrained_delaunay(
points: &[Point],
constraints: &[(usize, usize)],
options: &DelaunayOptions,
) -> Result<DelaunayTriangulation> {
let mut triangulation = delaunay_triangulation(points, options)?;
triangulation
.triangles
.retain(|triangle| !violates_constraints(triangle, constraints, points));
triangulation.num_triangles = triangulation.triangles.len();
Ok(triangulation)
}
fn violates_constraints(
triangle: &Triangle,
constraints: &[(usize, usize)],
_points: &[Point],
) -> bool {
for &(c1, c2) in constraints {
let [a, b, c] = triangle.vertices;
if (c1 == a && c2 == b)
|| (c1 == b && c2 == a)
|| (c1 == b && c2 == c)
|| (c1 == c && c2 == b)
|| (c1 == c && c2 == a)
|| (c1 == a && c2 == c)
{
continue;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_delaunay_simple() {
let points = vec![
Point::new(0.0, 0.0),
Point::new(1.0, 0.0),
Point::new(0.5, 1.0),
Point::new(0.5, 0.5),
];
let options = DelaunayOptions::default();
let result = delaunay_triangulation(&points, &options);
assert!(result.is_ok());
let triangulation = result.expect("Triangulation failed");
assert!(triangulation.num_triangles >= 2);
}
#[test]
fn test_triangle_quality() {
let pa = Point::new(0.0, 0.0);
let pb = Point::new(1.0, 0.0);
let pc = Point::new(0.5, 0.866);
let quality = compute_triangle_quality(&pa, &pb, &pc);
assert!(quality > 0.9); }
#[test]
fn test_in_circumcircle() {
let pa = Point::new(0.0, 0.0);
let pb = Point::new(1.0, 0.0);
let pc = Point::new(0.0, 1.0);
let pd = Point::new(0.25, 0.25);
assert!(in_circumcircle(&pa, &pb, &pc, &pd));
}
#[test]
fn test_constrained_delaunay() {
let points = vec![
Point::new(0.0, 0.0),
Point::new(1.0, 0.0),
Point::new(0.5, 1.0),
Point::new(0.5, 0.5),
];
let constraints = vec![(0, 2)];
let options = DelaunayOptions::default();
let result = constrained_delaunay(&points, &constraints, &options);
assert!(result.is_ok());
}
}