use axiolid_core::Point2;
use crate::build::triangulate;
use crate::mesh::{Triangulation, TriangulationError};
use crate::{collinear, Constraint};
const DEGRADATION_TOLERANCE_DEGREES: f64 = 1e-6;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Quality {
pub min_angle_degrees: f64,
pub max_steiner_points: usize,
}
impl Default for Quality {
fn default() -> Self {
Self {
min_angle_degrees: 20.0,
max_steiner_points: 4096,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum RefineOutcome {
Achieved {
inserted: usize,
},
Capped {
inserted: usize,
worst_angle_degrees: f64,
},
}
impl RefineOutcome {
#[must_use]
pub const fn achieved(self) -> bool {
matches!(self, Self::Achieved { .. })
}
}
pub fn triangulate_refined(
points: &[Point2],
constraints: &[Constraint],
quality: Quality,
) -> Result<(Triangulation, RefineOutcome), TriangulationError> {
let mut tri = triangulate(points, constraints)?;
let outcome = refine(&mut tri, quality)?;
Ok((tri, outcome))
}
pub fn refine(
tri: &mut Triangulation,
quality: Quality,
) -> Result<RefineOutcome, TriangulationError> {
let threshold = quality.min_angle_degrees.to_radians().cos();
let mut inserted = 0usize;
let mut skipped: Vec<usize> = Vec::new();
while inserted < quality.max_steiner_points {
let Some(bad) = worst_triangle(tri, threshold, &skipped) else {
break;
};
let Some(centre) = circumcentre(tri, bad) else {
skipped.push(bad);
continue;
};
if !inside_hull(tri, centre) {
skipped.push(bad);
continue;
}
let mut points = tri.points.clone();
points.push(centre);
let constraints = tri.constraints.clone();
let candidate = triangulate(&points, &constraints)?;
let before_worst = worst_angle_degrees(tri);
let after_worst = worst_angle_degrees(&candidate);
if after_worst < before_worst - DEGRADATION_TOLERANCE_DEGREES {
skipped.push(bad);
continue;
}
*tri = candidate;
inserted += 1;
skipped.clear();
}
let worst = worst_angle_degrees(tri);
if worst >= quality.min_angle_degrees {
Ok(RefineOutcome::Achieved { inserted })
} else {
Ok(RefineOutcome::Capped {
inserted,
worst_angle_degrees: worst,
})
}
}
fn worst_triangle(tri: &Triangulation, cos_threshold: f64, skipped: &[usize]) -> Option<usize> {
(0..tri.triangle_count()).find(|t| {
if skipped.contains(t) {
return false;
}
let (a, b, c) = corners(tri, *t);
max_cos(a, b, c) > cos_threshold
})
}
fn max_cos(a: Point2, b: Point2, c: Point2) -> f64 {
let ab = (b.x - a.x, b.y - a.y);
let bc = (c.x - b.x, c.y - b.y);
let ca = (a.x - c.x, a.y - c.y);
let at = angle_cos((-ca.0, -ca.1), ab);
let bt = angle_cos((-ab.0, -ab.1), bc);
let ct = angle_cos((-bc.0, -bc.1), ca);
at.max(bt).max(ct)
}
fn angle_cos(u: (f64, f64), v: (f64, f64)) -> f64 {
let dot = u.0 * v.0 + u.1 * v.1;
let nu = (u.0 * u.0 + u.1 * u.1).sqrt();
let nv = (v.0 * v.0 + v.1 * v.1).sqrt();
if nu == 0.0 || nv == 0.0 {
return 1.0;
}
(dot / (nu * nv)).clamp(-1.0, 1.0)
}
fn worst_angle_degrees(tri: &Triangulation) -> f64 {
let mut worst: f64 = 180.0;
for t in 0..tri.triangle_count() {
let (a, b, c) = corners(tri, t);
let angle = max_cos(a, b, c).clamp(-1.0, 1.0).acos().to_degrees();
worst = worst.min(angle);
}
worst
}
fn corners(tri: &Triangulation, t: usize) -> (Point2, Point2, Point2) {
(
tri.points[tri.triangles[3 * t] as usize],
tri.points[tri.triangles[3 * t + 1] as usize],
tri.points[tri.triangles[3 * t + 2] as usize],
)
}
fn circumcentre(tri: &Triangulation, t: usize) -> Option<Point2> {
let (a, b, c) = corners(tri, t);
if collinear(a, b, c) {
return None;
}
let d = 2.0 * (a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y));
if d == 0.0 || !d.is_finite() {
return None;
}
let a2 = a.x * a.x + a.y * a.y;
let b2 = b.x * b.x + b.y * b.y;
let c2 = c.x * c.x + c.y * c.y;
let ux = (a2 * (b.y - c.y) + b2 * (c.y - a.y) + c2 * (a.y - b.y)) / d;
let uy = (a2 * (c.x - b.x) + b2 * (a.x - c.x) + c2 * (b.x - a.x)) / d;
if ux.is_finite() && uy.is_finite() {
Some(Point2::new(ux, uy))
} else {
None
}
}
fn inside_hull(tri: &Triangulation, p: Point2) -> bool {
(0..tri.triangle_count()).any(|t| {
let (a, b, c) = corners(tri, t);
let ab = crate::turns_left(a, b, p);
let bc = crate::turns_left(b, c, p);
let ca = crate::turns_left(c, a, p);
ab == bc && bc == ca
})
}