use crate::mesh::Mesh;
use cartan_core::Manifold;
pub fn angle_at_vertex<M: Manifold, const K: usize, const B: usize>(
mesh: &Mesh<M, K, B>,
manifold: &M,
s: usize,
v_idx: usize,
) -> f64 {
let simplex = &mesh.simplices[s];
let local = simplex
.iter()
.position(|&v| v == v_idx)
.expect("v_idx not in simplex");
let other: Vec<usize> = simplex
.iter()
.enumerate()
.filter(|&(i, _)| i != local)
.map(|(_, &v)| v)
.collect();
let v0 = &mesh.vertices[v_idx];
let u = manifold
.log(v0, &mesh.vertices[other[0]])
.unwrap_or_else(|_| manifold.zero_tangent(v0));
let v = manifold
.log(v0, &mesh.vertices[other[1]])
.unwrap_or_else(|_| manifold.zero_tangent(v0));
let uu = manifold.inner(v0, &u, &u);
let vv = manifold.inner(v0, &v, &v);
let uv = manifold.inner(v0, &u, &v);
let cos_angle = uv / (uu.sqrt() * vv.sqrt());
cos_angle.clamp(-1.0, 1.0).acos()
}
pub fn is_delaunay<M: Manifold, const K: usize, const B: usize>(
mesh: &Mesh<M, K, B>,
manifold: &M,
) -> bool {
assert_eq!(K, 3, "is_delaunay requires triangle meshes (K=3)");
for e in 0..mesh.n_boundaries() {
let cofaces = &mesh.boundary_simplices[e];
if cofaces.len() != 2 {
continue;
}
let edge = &mesh.boundaries[e];
let alpha = opposite_angle(mesh, manifold, cofaces[0], edge);
let beta = opposite_angle(mesh, manifold, cofaces[1], edge);
if alpha + beta > std::f64::consts::PI + 1e-10 {
return false;
}
}
true
}
fn opposite_angle<M: Manifold, const K: usize, const B: usize>(
mesh: &Mesh<M, K, B>,
manifold: &M,
s: usize,
edge: &[usize; B],
) -> f64 {
let simplex = &mesh.simplices[s];
let opp = simplex
.iter()
.find(|&&v| !edge.contains(&v))
.expect("simplex must have a vertex not in edge");
angle_at_vertex(mesh, manifold, s, *opp)
}
pub fn is_well_centered<M: Manifold>(mesh: &Mesh<M, 3, 2>, manifold: &M) -> bool {
mesh.check_well_centered(manifold).is_ok()
}
#[derive(Debug, Clone)]
pub struct MeshQualityReport {
pub min_angle: f64,
pub max_angle: f64,
pub non_delaunay_edges: usize,
pub non_well_centered_simplices: usize,
pub n_vertices: usize,
pub n_edges: usize,
pub n_simplices: usize,
pub is_delaunay: bool,
pub is_well_centered: bool,
}
pub fn quality_report<M: Manifold, const K: usize, const B: usize>(
mesh: &Mesh<M, K, B>,
manifold: &M,
) -> MeshQualityReport {
assert_eq!(K, 3, "quality_report requires triangle meshes (K=3)");
let mut min_angle = f64::MAX;
let mut max_angle = 0.0_f64;
for s in 0..mesh.n_simplices() {
for &v in &mesh.simplices[s] {
let a = angle_at_vertex(mesh, manifold, s, v);
min_angle = min_angle.min(a);
max_angle = max_angle.max(a);
}
}
let mut non_delaunay = 0;
for e in 0..mesh.n_boundaries() {
let cofaces = &mesh.boundary_simplices[e];
if cofaces.len() != 2 {
continue;
}
let edge = &mesh.boundaries[e];
let alpha = opposite_angle(mesh, manifold, cofaces[0], edge);
let beta = opposite_angle(mesh, manifold, cofaces[1], edge);
if alpha + beta > std::f64::consts::PI + 1e-10 {
non_delaunay += 1;
}
}
let mut non_wc = 0;
for s in 0..mesh.n_simplices() {
let all_acute = mesh.simplices[s]
.iter()
.all(|&v| angle_at_vertex(mesh, manifold, s, v) < std::f64::consts::FRAC_PI_2 + 1e-10);
if !all_acute {
non_wc += 1;
}
}
MeshQualityReport {
min_angle,
max_angle,
non_delaunay_edges: non_delaunay,
non_well_centered_simplices: non_wc,
n_vertices: mesh.n_vertices(),
n_edges: mesh.n_boundaries(),
n_simplices: mesh.n_simplices(),
is_delaunay: non_delaunay == 0,
is_well_centered: non_wc == 0,
}
}
fn edge_flip<M: Manifold>(mesh: &mut Mesh<M, 3, 2>, e: usize) {
let cofaces = mesh.boundary_simplices[e].clone();
assert_eq!(cofaces.len(), 2, "cannot flip a boundary edge");
let t0 = cofaces[0];
let t1 = cofaces[1];
let edge = mesh.boundaries[e];
let opp0 = mesh.simplices[t0]
.iter()
.find(|&&v| !edge.contains(&v))
.copied()
.expect("opposite vertex in t0");
let opp1 = mesh.simplices[t1]
.iter()
.find(|&&v| !edge.contains(&v))
.copied()
.expect("opposite vertex in t1");
mesh.simplices[t0] = [edge[0], opp1, opp0];
mesh.simplices[t1] = [opp1, edge[1], opp0];
mesh.rebuild_topology();
}
pub fn make_delaunay<M: Manifold>(mut mesh: Mesh<M, 3, 2>, manifold: &M) -> Mesh<M, 3, 2> {
let max_iterations = mesh.n_boundaries() * mesh.n_boundaries();
for _ in 0..max_iterations {
let mut flipped = false;
for e in 0..mesh.n_boundaries() {
let cofaces = &mesh.boundary_simplices[e];
if cofaces.len() != 2 {
continue;
}
let edge = &mesh.boundaries[e];
let alpha = opposite_angle(&mesh, manifold, cofaces[0], edge);
let beta = opposite_angle(&mesh, manifold, cofaces[1], edge);
if alpha + beta > std::f64::consts::PI + 1e-10 {
edge_flip(&mut mesh, e);
flipped = true;
break; }
}
if !flipped {
break;
}
}
mesh
}
pub fn make_well_centered<M: Manifold>(
mut mesh: Mesh<M, 3, 2>,
manifold: &M,
max_iterations: usize,
tolerance: f64,
) -> Mesh<M, 3, 2> {
for _iter in 0..max_iterations {
let mut max_displacement = 0.0_f64;
let nv = mesh.n_vertices();
let mut displacements: Vec<Option<M::Tangent>> = (0..nv).map(|_| None).collect();
let mut weights: Vec<f64> = vec![0.0; nv];
for s in 0..mesh.n_simplices() {
let simplex = &mesh.simplices[s];
let area = mesh.simplex_volume(manifold, s);
for &v in simplex {
let pv = &mesh.vertices[v];
let mut centroid_tan = manifold.zero_tangent(pv);
for &other in simplex {
if other != v {
let log = manifold
.log(pv, &mesh.vertices[other])
.unwrap_or_else(|_| manifold.zero_tangent(pv));
centroid_tan = centroid_tan + log * (1.0 / 3.0);
}
}
let weighted = centroid_tan * area;
displacements[v] = Some(match displacements[v].take() {
Some(existing) => existing + weighted,
None => weighted,
});
weights[v] += area;
}
}
for v in 0..nv {
let is_boundary = mesh.vertex_boundaries[v]
.iter()
.any(|&e| mesh.boundary_simplices[e].len() < 2);
if is_boundary {
continue;
}
if let Some(ref tan) = displacements[v] {
let w = weights[v];
if w < 1e-30 {
continue;
}
let disp = tan.clone() * (1.0 / w);
let disp_norm = manifold.inner(&mesh.vertices[v], &disp, &disp).sqrt();
max_displacement = max_displacement.max(disp_norm);
mesh.vertices[v] = manifold.exp(&mesh.vertices[v], &disp);
}
}
if max_displacement < tolerance {
break;
}
mesh = make_delaunay(mesh, manifold);
}
mesh
}