use std::collections::{HashMap, HashSet, VecDeque};
use crate::geometry::edge_length;
use crate::ids::HalfEdgeId;
use crate::storage::MeshStorage;
use crate::topology_ops::flip_edge;
use crate::traversal::FaceHalfEdges;
pub fn compute_intrinsic_lengths(mesh: &MeshStorage) -> HashMap<HalfEdgeId, f64> {
let mut lengths = HashMap::new();
for he in mesh.halfedge_ids() {
if let Some(l) = edge_length(mesh, he) {
lengths.insert(he, l);
}
}
lengths
}
fn triangle_area_from_lengths(a: f64, b: f64, c: f64) -> f64 {
let s = (a + b + c) * 0.5;
let val = s * (s - a) * (s - b) * (s - c);
if val <= 0.0 { 0.0 } else { val.sqrt() }
}
pub fn is_intrinsic_delaunay_edge(l_ab: f64, l_bc: f64, l_ca: f64, l_ad: f64, l_bd: f64) -> bool {
let area_abc = triangle_area_from_lengths(l_ab, l_bc, l_ca);
let area_abd = triangle_area_from_lengths(l_ab, l_ad, l_bd);
if area_abc < 1e-14 || area_abd < 1e-14 {
return true;
}
let cot_c = (l_ca * l_ca + l_bc * l_bc - l_ab * l_ab) / (4.0 * area_abc);
let cot_d = (l_ad * l_ad + l_bd * l_bd - l_ab * l_ab) / (4.0 * area_abd);
cot_c + cot_d >= 0.0
}
fn intrinsic_flipped_length(l_ab: f64, l_bc: f64, l_ca: f64, l_ad: f64, l_bd: f64) -> f64 {
let cos_a_abc =
((l_ab * l_ab + l_ca * l_ca - l_bc * l_bc) / (2.0 * l_ab * l_ca)).clamp(-1.0, 1.0);
let cos_a_abd =
((l_ab * l_ab + l_ad * l_ad - l_bd * l_bd) / (2.0 * l_ab * l_ad)).clamp(-1.0, 1.0);
let angle_a_abc = cos_a_abc.acos();
let angle_a_abd = cos_a_abd.acos();
let angle_cad = angle_a_abc + angle_a_abd;
let l_cd_sq = l_ca * l_ca + l_ad * l_ad - 2.0 * l_ca * l_ad * angle_cad.cos();
l_cd_sq.max(0.0).sqrt()
}
pub fn intrinsic_cotan_weight(l_ab: f64, l_bc: f64, l_ca: f64, l_ad: f64, l_bd: f64) -> f64 {
let area_abc = triangle_area_from_lengths(l_ab, l_bc, l_ca);
let area_abd = triangle_area_from_lengths(l_ab, l_ad, l_bd);
let mut weight = 0.0;
if area_abc > 1e-14 {
weight += (l_ca * l_ca + l_bc * l_bc - l_ab * l_ab) / (4.0 * area_abc);
}
if area_abd > 1e-14 {
weight += (l_ad * l_ad + l_bd * l_bd - l_ab * l_ab) / (4.0 * area_abd);
}
weight
}
#[derive(Debug, Clone, Default)]
pub struct IntrinsicDelaunayStats {
pub flips: usize,
pub iterations: usize,
}
pub fn intrinsic_delaunay(
mesh: &mut MeshStorage,
intrinsic_lengths: &mut HashMap<HalfEdgeId, f64>,
) -> IntrinsicDelaunayStats {
let edge_count = mesh.edge_count();
let max_iterations = 10 * edge_count.max(1);
let mut queue: VecDeque<HalfEdgeId> = VecDeque::new();
let mut in_queue: HashSet<HalfEdgeId> = HashSet::new();
for edge in mesh.edge_ids() {
let he = edge.halfedge();
if is_interior_edge(mesh, he) {
queue.push_back(he);
in_queue.insert(he);
}
}
let mut flips = 0;
let mut iterations = 0;
while let Some(he) = queue.pop_front() {
in_queue.remove(&he);
iterations += 1;
if iterations > max_iterations {
break;
}
if !mesh.contains_halfedge(he) || !is_interior_edge(mesh, he) {
continue;
}
let Some(edge_data) = get_edge_quad(mesh, he, intrinsic_lengths) else {
continue;
};
if is_intrinsic_delaunay_edge(
edge_data.l_ab,
edge_data.l_bc,
edge_data.l_ca,
edge_data.l_ad,
edge_data.l_bd,
) {
continue;
}
let l_cd = intrinsic_flipped_length(
edge_data.l_ab,
edge_data.l_bc,
edge_data.l_ca,
edge_data.l_ad,
edge_data.l_bd,
);
let affected = get_affected_halfedges(mesh, he);
if flip_edge(mesh, he).is_err() {
continue;
}
flips += 1;
intrinsic_lengths.insert(he, l_cd);
if let Some(twin) = mesh.get_halfedge(he).and_then(|h| h.twin) {
intrinsic_lengths.insert(twin, l_cd);
}
for &adj_he in &affected {
if !in_queue.contains(&adj_he)
&& mesh.contains_halfedge(adj_he)
&& is_interior_edge(mesh, adj_he)
{
queue.push_back(adj_he);
in_queue.insert(adj_he);
}
}
}
IntrinsicDelaunayStats { flips, iterations }
}
fn is_interior_edge(mesh: &MeshStorage, he: HalfEdgeId) -> bool {
let Some(h) = mesh.get_halfedge(he) else {
return false;
};
if h.face.is_none() {
return false;
}
let Some(twin_id) = h.twin else {
return false;
};
let Some(twin) = mesh.get_halfedge(twin_id) else {
return false;
};
twin.face.is_some()
}
struct EdgeQuadData {
l_ab: f64,
l_bc: f64,
l_ca: f64,
l_ad: f64,
l_bd: f64,
}
fn get_edge_quad(
mesh: &MeshStorage,
he: HalfEdgeId,
lengths: &HashMap<HalfEdgeId, f64>,
) -> Option<EdgeQuadData> {
let h = mesh.get_halfedge(he)?;
let twin_id = h.twin?;
let twin = mesh.get_halfedge(twin_id)?;
let _b = h.vertex; let _a = twin.vertex;
let n1 = h.next?;
let _c = mesh.get_halfedge(n1)?.vertex;
let n2 = twin.next?;
let _d = mesh.get_halfedge(n2)?.vertex;
let l_ab = get_length(mesh, he, lengths)?;
let l_bc = get_length(mesh, n1, lengths)?;
let p1 = h.prev?;
let l_ca = get_length(mesh, p1, lengths)?;
let l_ad = get_length(mesh, n2, lengths)?;
let p2 = twin.prev?;
let l_bd = get_length(mesh, p2, lengths)?;
Some(EdgeQuadData {
l_ab,
l_bc,
l_ca,
l_ad,
l_bd,
})
}
fn get_length(
mesh: &MeshStorage,
he: HalfEdgeId,
lengths: &HashMap<HalfEdgeId, f64>,
) -> Option<f64> {
if let Some(&l) = lengths.get(&he) {
return Some(l);
}
edge_length(mesh, he)
}
fn get_affected_halfedges(mesh: &MeshStorage, he: HalfEdgeId) -> Vec<HalfEdgeId> {
let mut affected = Vec::new();
let Some(h) = mesh.get_halfedge(he) else {
return affected;
};
let Some(twin_id) = h.twin else {
return affected;
};
let Some(twin) = mesh.get_halfedge(twin_id) else {
return affected;
};
let Some(f1) = h.face else {
return affected;
};
let Some(f2) = twin.face else {
return affected;
};
for fhe in FaceHalfEdges::new(mesh, f1) {
if fhe != he {
affected.push(fhe);
if let Some(t) = mesh.get_halfedge(fhe).and_then(|h| h.twin) {
affected.push(t);
}
}
}
for fhe in FaceHalfEdges::new(mesh, f2) {
if fhe != twin_id {
affected.push(fhe);
if let Some(t) = mesh.get_halfedge(fhe).and_then(|h| h.twin) {
affected.push(t);
}
}
}
affected
}
pub fn cotan_from_lengths(a: f64, b: f64, c: f64) -> f64 {
let area = triangle_area_from_lengths(a, b, c);
if area < 1e-14 {
return 0.0;
}
(b * b + c * c - a * a) / (4.0 * area)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::build_grid;
use crate::storage::{MeshStorage, Vertex};
use crate::test_util::build_icosphere;
use crate::topology_ops::add_triangle;
#[test]
fn intrinsic_delaunay_grid() {
let mut mesh = build_grid(1.0, 1.0, 1, 1);
let mut lengths = compute_intrinsic_lengths(&mesh);
let stats = intrinsic_delaunay(&mut mesh, &mut lengths);
for edge in mesh.edge_ids() {
let he = edge.halfedge();
if !is_interior_edge(&mesh, he) {
continue;
}
let Some(data) = get_edge_quad(&mesh, he, &lengths) else {
continue;
};
assert!(
is_intrinsic_delaunay_edge(data.l_ab, data.l_bc, data.l_ca, data.l_ad, data.l_bd),
"翻转后仍存在非 Delaunay 边"
);
}
let _ = stats;
}
#[test]
fn intrinsic_delaunay_icosphere_no_flip() {
let mesh = build_icosphere(1);
let mut lengths = compute_intrinsic_lengths(&mesh);
let mut mesh = mesh;
let stats = intrinsic_delaunay(&mut mesh, &mut lengths);
for edge in mesh.edge_ids() {
let he = edge.halfedge();
if !is_interior_edge(&mesh, he) {
continue;
}
let Some(data) = get_edge_quad(&mesh, he, &lengths) else {
continue;
};
assert!(is_intrinsic_delaunay_edge(
data.l_ab, data.l_bc, data.l_ca, data.l_ad, data.l_bd
),);
}
let _ = stats;
}
#[test]
fn intrinsic_delaunay_flat_quad() {
let mut mesh = MeshStorage::new();
let a = mesh.add_vertex(Vertex::new([0.0, 0.0, 0.0]));
let b = mesh.add_vertex(Vertex::new([2.0, 0.0, 0.0]));
let c = mesh.add_vertex(Vertex::new([1.0, 0.1, 0.0]));
let d = mesh.add_vertex(Vertex::new([1.0, 1.0, 0.0]));
add_triangle(&mut mesh, a, b, c).unwrap();
add_triangle(&mut mesh, a, c, d).unwrap();
let mut lengths = compute_intrinsic_lengths(&mesh);
let stats = intrinsic_delaunay(&mut mesh, &mut lengths);
for edge in mesh.edge_ids() {
let he = edge.halfedge();
if !is_interior_edge(&mesh, he) {
continue;
}
let Some(data) = get_edge_quad(&mesh, he, &lengths) else {
continue;
};
assert!(is_intrinsic_delaunay_edge(
data.l_ab, data.l_bc, data.l_ca, data.l_ad, data.l_bd
),);
}
let _ = stats;
}
#[test]
fn is_delaunay_equilateral() {
let s = 1.0;
assert!(is_intrinsic_delaunay_edge(s, s, s, s, s));
}
#[test]
fn triangle_area_heron() {
let area = triangle_area_from_lengths(3.0, 4.0, 5.0);
assert!((area - 6.0).abs() < 1e-10);
let deg_area = triangle_area_from_lengths(1.0, 2.0, 3.0);
assert_eq!(deg_area, 0.0);
}
#[test]
fn flipped_length_diamond() {
let l_ab = 2.0;
let l_bc = std::f64::consts::SQRT_2;
let l_ca = std::f64::consts::SQRT_2;
let l_ad = std::f64::consts::SQRT_2;
let l_bd = std::f64::consts::SQRT_2;
let l_cd = intrinsic_flipped_length(l_ab, l_bc, l_ca, l_ad, l_bd);
assert!((l_cd - 2.0).abs() < 1e-10, "l_cd = {}, expected 2.0", l_cd);
}
#[test]
fn intrinsic_lengths_match_euclidean() {
let mesh = build_icosphere(1);
let lengths = compute_intrinsic_lengths(&mesh);
for he in mesh.halfedge_ids() {
if let Some(l) = edge_length(&mesh, he) {
let stored = lengths.get(&he);
assert!(stored.is_some(), "半边 {:?} 缺少内蕴长度", he);
assert!(
(stored.unwrap() - l).abs() < 1e-10,
"内蕴长度与欧氏距离不一致"
);
}
}
}
#[test]
fn cotan_weight_nonnegative_after_delaunay() {
let mut mesh = build_grid(1.0, 1.0, 2, 2);
let mut lengths = compute_intrinsic_lengths(&mesh);
intrinsic_delaunay(&mut mesh, &mut lengths);
for edge in mesh.edge_ids() {
let he = edge.halfedge();
if !is_interior_edge(&mesh, he) {
continue;
}
let Some(data) = get_edge_quad(&mesh, he, &lengths) else {
continue;
};
let w = intrinsic_cotan_weight(data.l_ab, data.l_bc, data.l_ca, data.l_ad, data.l_bd);
assert!(w >= -1e-10, "余切权重为负: {}", w);
}
}
#[test]
fn intrinsic_delaunay_empty() {
let mut mesh = MeshStorage::new();
let mut lengths = compute_intrinsic_lengths(&mesh);
let stats = intrinsic_delaunay(&mut mesh, &mut lengths);
assert_eq!(stats.flips, 0);
}
#[test]
fn intrinsic_delaunay_single_triangle() {
let mut mesh = MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([0.0, 0.0, 0.0]));
let v1 = mesh.add_vertex(Vertex::new([1.0, 0.0, 0.0]));
let v2 = mesh.add_vertex(Vertex::new([0.0, 1.0, 0.0]));
add_triangle(&mut mesh, v0, v1, v2).unwrap();
let mut lengths = compute_intrinsic_lengths(&mesh);
let stats = intrinsic_delaunay(&mut mesh, &mut lengths);
assert_eq!(stats.flips, 0);
}
}