use std::collections::HashMap;
use crate::mesh::{Indices, Primitive};
#[derive(Clone, Debug)]
pub struct CurvatureReport {
pub gaussian: Vec<f64>,
pub mean: Vec<f64>,
pub area: Vec<f64>,
pub welded: Primitive,
}
impl CurvatureReport {
pub fn len(&self) -> usize {
self.gaussian.len()
}
pub fn is_empty(&self) -> bool {
self.gaussian.is_empty()
}
pub fn total_angle_defect(&self) -> f64 {
self.gaussian
.iter()
.zip(&self.area)
.map(|(&k, &a)| k * a)
.sum()
}
}
fn cotangent(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
let u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let v = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let dot = u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
let cross = [
u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2],
u[0] * v[1] - u[1] * v[0],
];
let cross_len = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
if cross_len <= 0.0 || !cross_len.is_finite() {
return 0.0;
}
dot / cross_len
}
fn angle_at(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
let u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let v = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let dot = u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
let cross = [
u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2],
u[0] * v[1] - u[1] * v[0],
];
let cross_len = (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt();
if cross_len <= 0.0 || !cross_len.is_finite() {
return 0.0;
}
cross_len.atan2(dot)
}
fn edge_len2(p: [f64; 3], q: [f64; 3]) -> f64 {
let d = [q[0] - p[0], q[1] - p[1], q[2] - p[2]];
d[0] * d[0] + d[1] * d[1] + d[2] * d[2]
}
fn tri_area(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
let u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let v = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let cross = [
u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2],
u[0] * v[1] - u[1] * v[0],
];
0.5 * (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]).sqrt()
}
impl Primitive {
pub fn curvature(&self) -> CurvatureReport {
let welded = self.weld_vertices();
let tris = welded.triangle_indices();
let n = welded.positions.len();
let empty = CurvatureReport {
gaussian: Vec::new(),
mean: Vec::new(),
area: Vec::new(),
welded: {
let mut w = welded.clone();
w.indices = Some(Indices::U32(Vec::new()));
w
},
};
if n == 0 || tris.is_empty() {
return empty;
}
let pos: Vec<[f64; 3]> = welded
.positions
.iter()
.map(|p| [p[0] as f64, p[1] as f64, p[2] as f64])
.collect();
let mut angle_sum = vec![0.0f64; n]; let mut area = vec![0.0f64; n]; let mut lap = vec![[0.0f64; 3]; n];
let mut edge_count: HashMap<(u32, u32), u32> = HashMap::new();
let ekey = |a: u32, b: u32| if a < b { (a, b) } else { (b, a) };
for &[ia, ib, ic] in &tris {
let (a, b, c) = (ia as usize, ib as usize, ic as usize);
if a >= n || b >= n || c >= n {
continue;
}
let (pa, pb, pc) = (pos[a], pos[b], pos[c]);
let area_f = tri_area(pa, pb, pc);
if !area_f.is_finite() || area_f <= 0.0 {
continue; }
for (u, v) in [(ia, ib), (ib, ic), (ic, ia)] {
*edge_count.entry(ekey(u, v)).or_insert(0) += 1;
}
let ang_a = angle_at(pa, pb, pc);
let ang_b = angle_at(pb, pc, pa);
let ang_c = angle_at(pc, pa, pb);
angle_sum[a] += ang_a;
angle_sum[b] += ang_b;
angle_sum[c] += ang_c;
let cot_a = cotangent(pa, pb, pc); let cot_b = cotangent(pb, pc, pa); let cot_c = cotangent(pc, pa, pb);
accumulate_edge(&mut lap, &pos, b, c, cot_a);
accumulate_edge(&mut lap, &pos, c, a, cot_b);
accumulate_edge(&mut lap, &pos, a, b, cot_c);
let obtuse_at = if ang_a > std::f64::consts::FRAC_PI_2 {
Some(a)
} else if ang_b > std::f64::consts::FRAC_PI_2 {
Some(b)
} else if ang_c > std::f64::consts::FRAC_PI_2 {
Some(c)
} else {
None
};
match obtuse_at {
None => {
let l_ab = edge_len2(pa, pb);
let l_bc = edge_len2(pb, pc);
let l_ca = edge_len2(pc, pa);
area[a] += (cot_c * l_ab + cot_b * l_ca) / 8.0;
area[b] += (cot_a * l_bc + cot_c * l_ab) / 8.0;
area[c] += (cot_b * l_ca + cot_a * l_bc) / 8.0;
}
Some(o) => {
for &i in &[a, b, c] {
area[i] += if i == o { area_f / 2.0 } else { area_f / 4.0 };
}
}
}
}
let mut boundary = vec![false; n];
for (&(u, v), &count) in &edge_count {
if count != 2 {
boundary[u as usize] = true;
boundary[v as usize] = true;
}
}
let two_pi = std::f64::consts::TAU;
let pi = std::f64::consts::PI;
let mut gaussian = vec![0.0f64; n];
let mut mean = vec![0.0f64; n];
for i in 0..n {
let reference = if boundary[i] { pi } else { two_pi };
let defect = reference - angle_sum[i];
if area[i] > 0.0 && area[i].is_finite() {
gaussian[i] = defect / area[i];
let l = lap[i];
let mag = (l[0] * l[0] + l[1] * l[1] + l[2] * l[2]).sqrt();
mean[i] = 0.5 * mag / (2.0 * area[i]);
} else {
gaussian[i] = 0.0;
mean[i] = 0.0;
}
}
let mut out_welded = welded.clone();
out_welded.indices = Some(Indices::U32(
tris.iter().flat_map(|t| t.iter().copied()).collect(),
));
CurvatureReport {
gaussian,
mean,
area,
welded: out_welded,
}
}
}
fn accumulate_edge(lap: &mut [[f64; 3]], pos: &[[f64; 3]], i: usize, j: usize, w: f64) {
if !w.is_finite() {
return;
}
let pi = pos[i];
let pj = pos[j];
for k in 0..3 {
lap[i][k] += w * (pj[k] - pi[k]);
lap[j][k] += w * (pi[k] - pj[k]);
}
}