use super::Triangulation;
use crate::{
math::{IndexType, Scalar, Vector, Vector2D},
mesh::{Face, Face3d, FaceBasics, MeshType3D},
};
use itertools::Itertools;
use spade::{ConstrainedDelaunayTriangulation, Point2, Triangulation as _};
use std::collections::HashMap;
pub fn delaunay_triangulation<T: MeshType3D>(
face: &T::Face,
mesh: &T::Mesh,
tri: &mut Triangulation<T::V>,
) {
debug_assert!(face.may_be_curved() || face.is_planar2(mesh));
debug_assert!(!face.has_self_intersections(mesh));
let mut cdt = ConstrainedDelaunayTriangulation::<Point2<_>>::default();
let mut last = None;
let mut first = None;
let mut i2v: Vec<T::V> = Vec::new();
let vec2s = face.vertices_2d(mesh).collect_vec();
for (i2, (vec2, global_index)) in vec2s.iter().enumerate() {
i2v.push(*global_index);
let spade_vertex = cdt
.insert(Point2::new(vec2.x().to_f64(), vec2.y().to_f64()))
.unwrap();
assert!(spade_vertex.index() == i2);
if let Some(j) = last {
assert!(cdt.add_constraint(j, spade_vertex));
} else {
first = Some(spade_vertex);
}
last = Some(spade_vertex);
}
assert!(cdt.add_constraint(last.unwrap(), first.unwrap()));
cdt.inner_faces().for_each(|f| {
let [p0, p1, p2] = f.vertices();
let v0 = i2v[p0.index()];
let v1 = i2v[p1.index()];
let v2 = i2v[p2.index()];
let r = face.triangle_touches_boundary(mesh, v0, v1, v2);
if r.is_none() || r.unwrap() {
if r.is_some() {
tri.insert_triangle(v0, v1, v2);
return;
}
fn wrap_angle<S: Scalar>(x: S) -> S {
if x < S::zero() {
x + S::PI * S::TWO
} else {
x
}
}
fn is_edge_inside<V: IndexType, V2: Vector2D>(
vec2s: &[(V2, V)],
v: usize,
other_v: usize,
) -> bool {
let n = vec2s.len();
let prev = vec2s[(v + n - 1) % n].0;
let next = vec2s[(v + 1) % n].0;
let triangle_angle = wrap_angle(vec2s[v].0.angle_tri(vec2s[other_v].0, prev));
let boundary_angle = wrap_angle(vec2s[v].0.angle_tri(next, prev));
triangle_angle <= boundary_angle
}
{
let is_inside = is_edge_inside(&vec2s, p0.index(), p1.index());
if is_inside {
tri.insert_triangle(v0, v1, v2);
}
for (v, other_v) in &[(p0, p1), (p1, p2), (p2, p0)] {
debug_assert_eq!(
is_edge_inside(&vec2s, v.index(), other_v.index()),
is_inside
);
debug_assert_eq!(
is_edge_inside(&vec2s, other_v.index(), v.index()),
is_inside
);
}
}
}
});
debug_assert!({
let vec2s = face.vec2s(mesh);
let vec_hm: HashMap<T::V, T::Vec2> = vec2s.iter().map(|v| (v.index, v.vec)).collect();
tri.verify_indices(&vec_hm);
tri.verify_all_indices_used(&vec2s);
tri.verify_no_intersections(&vec_hm);
tri.verify_non_degenerate_triangle(&vec_hm);
true
});
}
#[cfg(test)]
#[cfg(feature = "nalgebra")]
mod tests {
use crate::extensions::nalgebra::*;
use crate::prelude::*;
fn verify_triangulation<T: MeshType3D>(mesh: &T::Mesh, f: T::F) {
let face = mesh.face(f);
let vec2s = face.vec2s(mesh);
assert!(
T::Poly::from_iter(vec2s.iter().map(|v| v.vec)).is_ccw(),
"Polygon must be counterclockwise"
);
let mut indices = Vec::new();
let mut tri = Triangulation::new(&mut indices);
delaunay_triangulation::<T>(face, &mesh, &mut tri);
tri.verify_full::<T::Vec2, T::Poly>(&vec2s);
}
#[test]
#[cfg(feature = "fonts")]
fn test_font() {
let mut mesh2d = Mesh2d64Curved::new();
Font::new(include_bytes!("../../assets/Cochineal-Roman.otf"), 1.0)
.layout_text::<2, MeshType2d64PNUCurved>("F", &mut mesh2d);
let mesh3d = mesh2d.to_nd(0.01);
self::verify_triangulation::<MeshType3d64PNU>(&mesh3d, 0);
}
}