use crate::data::{AnyDataArray, DataArray, PolyData};
pub fn curvatures(input: &PolyData) -> PolyData {
let n = input.points.len();
let mut gauss = vec![0.0f64; n];
let mut mean = vec![0.0f64; n];
let mut area = vec![0.0f64; n];
gauss.fill(2.0 * std::f64::consts::PI);
let offsets = input.polys.offsets();
let conn = input.polys.connectivity();
let nc = input.polys.num_cells();
let pts = input.points.as_flat_slice();
for ci in 0..nc {
let start = offsets[ci] as usize;
let end = offsets[ci + 1] as usize;
if end - start != 3 { continue; }
let i0 = conn[start] as usize;
let i1 = conn[start + 1] as usize;
let i2 = conn[start + 2] as usize;
let b0 = i0 * 3; let b1 = i1 * 3; let b2 = i2 * 3;
let p0 = [pts[b0], pts[b0+1], pts[b0+2]];
let p1 = [pts[b1], pts[b1+1], pts[b1+2]];
let p2 = [pts[b2], pts[b2+1], pts[b2+2]];
let e01 = sub(p1, p0);
let e02 = sub(p2, p0);
let e12 = sub(p2, p1);
let e10 = sub(p0, p1);
let e20 = sub(p0, p2);
let e21 = sub(p1, p2);
let a0 = angle_between(e01, e02);
let a1 = angle_between(e10, e12);
let a2 = angle_between(e20, e21);
gauss[i0] -= a0;
gauss[i1] -= a1;
gauss[i2] -= a2;
let cross = cross_3(e01, e02);
let tri_area = 0.5 * length(cross);
let va = tri_area / 3.0;
area[i0] += va;
area[i1] += va;
area[i2] += va;
let cot0 = cot(a0);
let cot1 = cot(a1);
let cot2 = cot(a2);
let len_e12 = length(e12);
let len_e02 = length(e02);
let len_e01 = length(e01);
mean[i0] += cot1 * len_e02 * len_e02 + cot2 * len_e01 * len_e01;
mean[i1] += cot0 * len_e12 * len_e12 + cot2 * len_e01 * len_e01;
mean[i2] += cot0 * len_e12 * len_e12 + cot1 * len_e02 * len_e02;
}
for i in 0..n {
if area[i] > 1e-20 {
gauss[i] /= area[i];
mean[i] /= 4.0 * area[i];
}
}
let mut pd = input.clone();
pd.point_data_mut()
.add_array(AnyDataArray::F64(DataArray::from_vec("GaussCurvature", gauss, 1)));
pd.point_data_mut()
.add_array(AnyDataArray::F64(DataArray::from_vec("MeanCurvature", mean, 1)));
pd
}
fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
fn cross_3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
fn length(v: [f64; 3]) -> f64 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
fn angle_between(a: [f64; 3], b: [f64; 3]) -> f64 {
let la = length(a);
let lb = length(b);
if la < 1e-20 || lb < 1e-20 {
return 0.0;
}
let cos_angle = (dot(a, b) / (la * lb)).clamp(-1.0, 1.0);
cos_angle.acos()
}
fn cot(angle: f64) -> f64 {
let s = angle.sin();
if s.abs() < 1e-20 {
0.0
} else {
angle.cos() / s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn curvatures_on_flat_mesh() {
let pd = PolyData::from_triangles(
vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[2.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0],
[2.0, 1.0, 0.0],
],
vec![[0, 1, 4], [0, 4, 3], [1, 2, 5], [1, 5, 4]],
);
let result = curvatures(&pd);
let gc = result.point_data().get_array("GaussCurvature").unwrap();
let mc = result.point_data().get_array("MeanCurvature").unwrap();
assert_eq!(gc.num_tuples(), 6);
assert_eq!(mc.num_tuples(), 6);
}
#[test]
fn curvatures_arrays_present() {
let pd = PolyData::from_triangles(
vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]],
vec![[0, 1, 2]],
);
let result = curvatures(&pd);
assert!(result.point_data().get_array("GaussCurvature").is_some());
assert!(result.point_data().get_array("MeanCurvature").is_some());
}
}