#![allow(dead_code)]
use std::collections::HashMap;
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DihedralEdge {
pub v0: u32,
pub v1: u32,
pub angle_rad: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DihedralResult {
pub edges: Vec<DihedralEdge>,
pub min_angle: f32,
pub max_angle: f32,
pub avg_angle: f32,
}
#[allow(dead_code)]
fn cross(a: [f32; 3], b: [f32; 3]) -> [f32; 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],
]
}
#[allow(dead_code)]
fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[allow(dead_code)]
fn norm(v: [f32; 3]) -> [f32; 3] {
let l = dot(v, v).sqrt();
if l < 1e-12 {
[0.0; 3]
} else {
[v[0] / l, v[1] / l, v[2] / l]
}
}
#[allow(dead_code)]
pub fn face_normal(p0: [f32; 3], p1: [f32; 3], p2: [f32; 3]) -> [f32; 3] {
let e1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
let e2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
norm(cross(e1, e2))
}
#[allow(dead_code)]
pub fn dihedral_angle_from_normals(n1: [f32; 3], n2: [f32; 3]) -> f32 {
let d = dot(n1, n2).clamp(-1.0, 1.0);
d.acos()
}
#[allow(dead_code)]
pub fn compute_dihedral_angles(positions: &[[f32; 3]], indices: &[u32]) -> DihedralResult {
let tri_count = indices.len() / 3;
let mut edge_faces: HashMap<(u32, u32), Vec<usize>> = HashMap::new();
for t in 0..tri_count {
let verts = [indices[t * 3], indices[t * 3 + 1], indices[t * 3 + 2]];
for k in 0..3 {
let a = verts[k];
let b = verts[(k + 1) % 3];
let key = if a < b { (a, b) } else { (b, a) };
edge_faces.entry(key).or_default().push(t);
}
}
let normals: Vec<[f32; 3]> = (0..tri_count)
.map(|t| {
face_normal(
positions[indices[t * 3] as usize],
positions[indices[t * 3 + 1] as usize],
positions[indices[t * 3 + 2] as usize],
)
})
.collect();
let mut edges = Vec::new();
for (&(v0, v1), faces) in &edge_faces {
if faces.len() == 2 {
let angle = dihedral_angle_from_normals(normals[faces[0]], normals[faces[1]]);
edges.push(DihedralEdge {
v0,
v1,
angle_rad: angle,
});
}
}
let min = edges.iter().map(|e| e.angle_rad).fold(f32::MAX, f32::min);
let max = edges.iter().map(|e| e.angle_rad).fold(f32::MIN, f32::max);
let avg = if !edges.is_empty() {
edges.iter().map(|e| e.angle_rad).sum::<f32>() / edges.len() as f32
} else {
0.0
};
DihedralResult {
edges,
min_angle: if min == f32::MAX { 0.0 } else { min },
max_angle: if max == f32::MIN { 0.0 } else { max },
avg_angle: avg,
}
}
#[allow(dead_code)]
pub fn dihedral_edge_count(result: &DihedralResult) -> usize {
result.edges.len()
}
#[allow(dead_code)]
pub fn sharp_edges_by_angle(result: &DihedralResult, threshold: f32) -> Vec<&DihedralEdge> {
result
.edges
.iter()
.filter(|e| e.angle_rad > threshold)
.collect()
}
#[allow(dead_code)]
pub fn dihedral_result_to_json(result: &DihedralResult) -> String {
format!(
"{{\"edge_count\":{},\"min\":{:.6},\"max\":{:.6},\"avg\":{:.6}}}",
result.edges.len(),
result.min_angle,
result.max_angle,
result.avg_angle,
)
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
fn flat_two_tris() -> (Vec<[f32; 3]>, Vec<u32>) {
let pos = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.5, 1.0, 0.0],
[1.5, 1.0, 0.0],
];
let idx = vec![0, 1, 2, 1, 3, 2];
(pos, idx)
}
#[test]
fn test_face_normal() {
let n = face_normal([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!((n[2] - 1.0).abs() < 1e-5);
}
#[test]
fn test_dihedral_coplanar() {
let (pos, idx) = flat_two_tris();
let result = compute_dihedral_angles(&pos, &idx);
for e in &result.edges {
assert!(e.angle_rad < 0.01);
}
}
#[test]
fn test_dihedral_perpendicular() {
let angle = dihedral_angle_from_normals([0.0, 0.0, 1.0], [0.0, 1.0, 0.0]);
assert!((angle - PI / 2.0).abs() < 1e-5);
}
#[test]
fn test_dihedral_same_normal() {
let angle = dihedral_angle_from_normals([0.0, 0.0, 1.0], [0.0, 0.0, 1.0]);
assert!(angle.abs() < 1e-5);
}
#[test]
fn test_sharp_edges_by_angle() {
let (pos, idx) = flat_two_tris();
let result = compute_dihedral_angles(&pos, &idx);
let sharp = sharp_edges_by_angle(&result, PI / 4.0);
assert!(sharp.is_empty()); }
#[test]
fn test_empty_mesh() {
let result = compute_dihedral_angles(&[], &[]);
assert_eq!(dihedral_edge_count(&result), 0);
}
#[test]
fn test_single_tri_no_internal() {
let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]];
let idx = vec![0, 1, 2];
let result = compute_dihedral_angles(&pos, &idx);
assert_eq!(dihedral_edge_count(&result), 0);
}
#[test]
fn test_dihedral_result_to_json() {
let result = DihedralResult {
edges: vec![],
min_angle: 0.0,
max_angle: 0.0,
avg_angle: 0.0,
};
let json = dihedral_result_to_json(&result);
assert!(json.contains("\"edge_count\":0"));
}
#[test]
fn test_edge_count() {
let (pos, idx) = flat_two_tris();
let result = compute_dihedral_angles(&pos, &idx);
assert_eq!(dihedral_edge_count(&result), 1);
}
#[test]
fn test_dihedral_opposite() {
let angle = dihedral_angle_from_normals([0.0, 0.0, 1.0], [0.0, 0.0, -1.0]);
assert!((angle - PI).abs() < 1e-5);
}
}