use crate::data::PolyData;
#[derive(Debug, Clone)]
pub struct MeshMeasurements {
pub surface_area: f64,
pub total_edge_length: f64,
pub avg_edge_length: f64,
pub min_edge_length: f64,
pub max_edge_length: f64,
pub num_edges: usize,
pub diagonal: f64,
}
pub fn measure(pd: &PolyData) -> MeshMeasurements {
let mut surface_area = 0.0;
let mut edges = std::collections::HashSet::new();
let mut total_edge_len = 0.0;
let mut min_edge = f64::INFINITY;
let mut max_edge = 0.0f64;
for cell in pd.polys.iter() {
if cell.len() < 3 { continue; }
let p0 = pd.points.get(cell[0] as usize);
for i in 1..cell.len() - 1 {
let p1 = pd.points.get(cell[i] as usize);
let p2 = pd.points.get(cell[i + 1] as usize);
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]];
let cx = e1[1] * e2[2] - e1[2] * e2[1];
let cy = e1[2] * e2[0] - e1[0] * e2[2];
let cz = e1[0] * e2[1] - e1[1] * e2[0];
surface_area += 0.5 * (cx * cx + cy * cy + cz * cz).sqrt();
}
let n = cell.len();
for i in 0..n {
let a = cell[i] as usize;
let b = cell[(i + 1) % n] as usize;
let key = if a < b { (a, b) } else { (b, a) };
if edges.insert(key) {
let pa = pd.points.get(a);
let pb = pd.points.get(b);
let dx = pb[0] - pa[0];
let dy = pb[1] - pa[1];
let dz = pb[2] - pa[2];
let len = (dx * dx + dy * dy + dz * dz).sqrt();
total_edge_len += len;
min_edge = min_edge.min(len);
max_edge = max_edge.max(len);
}
}
}
let num_edges = edges.len();
let avg = if num_edges > 0 { total_edge_len / num_edges as f64 } else { 0.0 };
let bb = pd.points.bounds();
MeshMeasurements {
surface_area,
total_edge_length: total_edge_len,
avg_edge_length: avg,
min_edge_length: if min_edge.is_finite() { min_edge } else { 0.0 },
max_edge_length: max_edge,
num_edges,
diagonal: bb.diagonal_length(),
}
}
pub fn point_distance(a: [f64; 3], b: [f64; 3]) -> f64 {
let dx = b[0] - a[0];
let dy = b[1] - a[1];
let dz = b[2] - a[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
pub fn angle_at_vertex(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
let ba = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
let bc = [c[0] - b[0], c[1] - b[1], c[2] - b[2]];
let dot = ba[0] * bc[0] + ba[1] * bc[1] + ba[2] * bc[2];
let mag_ba = (ba[0] * ba[0] + ba[1] * ba[1] + ba[2] * ba[2]).sqrt();
let mag_bc = (bc[0] * bc[0] + bc[1] * bc[1] + bc[2] * bc[2]).sqrt();
if mag_ba < 1e-15 || mag_bc < 1e-15 { return 0.0; }
let cos_angle = (dot / (mag_ba * mag_bc)).clamp(-1.0, 1.0);
cos_angle.acos().to_degrees()
}
pub fn triangle_area(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let cx = ab[1] * ac[2] - ab[2] * ac[1];
let cy = ab[2] * ac[0] - ab[0] * ac[2];
let cz = ab[0] * ac[1] - ab[1] * ac[0];
0.5 * (cx * cx + cy * cy + cz * cz).sqrt()
}
impl std::fmt::Display for MeshMeasurements {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "area={:.4}, edges={} (len: {:.4}..{:.4}, avg={:.4}), diag={:.4}",
self.surface_area, self.num_edges,
self.min_edge_length, self.max_edge_length, self.avg_edge_length,
self.diagonal)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unit_triangle_area() {
let a = triangle_area([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!((a - 0.5).abs() < 1e-10);
}
#[test]
fn right_angle() {
let angle = angle_at_vertex([1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
assert!((angle - 90.0).abs() < 1e-6);
}
#[test]
fn point_dist() {
let d = point_distance([0.0, 0.0, 0.0], [3.0, 4.0, 0.0]);
assert!((d - 5.0).abs() < 1e-10);
}
#[test]
fn measure_triangle() {
let pd = PolyData::from_triangles(
vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
vec![[0, 1, 2]],
);
let m = measure(&pd);
assert!((m.surface_area - 0.5).abs() < 1e-10);
assert_eq!(m.num_edges, 3);
assert!(m.min_edge_length > 0.0);
assert!(m.max_edge_length >= m.min_edge_length);
}
#[test]
fn measure_display() {
let pd = PolyData::from_triangles(
vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
vec![[0, 1, 2]],
);
let m = measure(&pd);
let s = format!("{m}");
assert!(s.contains("area="));
assert!(s.contains("edges="));
}
}