use glam::DVec3;
use super::{
EPS_FACE, GIRDLE_NY,
caliper::caliper_extents,
mesh::face_area,
pow2_scale_norm,
types::SolidMetrics,
vertices::{dedup_planes, feasible_vertices},
};
pub(in crate::geometry) fn max_abs_offset(planes: &[(DVec3, f64)]) -> f64 {
planes
.iter()
.map(|&(_, m)| m.abs())
.fold(0.0_f64, |acc, v| {
if acc.is_nan() || v.is_nan() {
f64::NAN
} else {
acc.max(v)
}
})
}
#[must_use]
pub fn measure_solid(planes: &[(DVec3, f64)]) -> Option<SolidMetrics> {
measure_solid_inner(planes).map(|(metrics, _)| metrics)
}
#[must_use]
pub fn measure_solid_with_vertices(planes: &[(DVec3, f64)]) -> Option<(SolidMetrics, Vec<DVec3>)> {
measure_solid_inner(planes)
}
fn measure_solid_inner(planes: &[(DVec3, f64)]) -> Option<(SolidMetrics, Vec<DVec3>)> {
let planes = dedup_planes(planes);
let scale = pow2_scale_norm(max_abs_offset(&planes));
let planes: Vec<(DVec3, f64)> = planes.iter().map(|&(n, m)| (n, m / scale)).collect();
let verts = feasible_vertices(&planes)?;
if verts.len() < 4 {
return None;
}
let volume: f64 = planes
.iter()
.map(|&(n, m)| m * face_area(n, m, &verts) / 3.0)
.sum();
if !(volume.is_finite() && volume > 0.0) {
return None;
}
let y_max = verts
.iter()
.map(|s| s.v.y)
.fold(f64::NEG_INFINITY, f64::max);
let y_min = verts.iter().map(|s| s.v.y).fold(f64::INFINITY, f64::min);
let mut girdle_top = f64::NEG_INFINITY;
let mut girdle_bottom = f64::INFINITY;
for &(n, m) in planes.iter().filter(|(n, _)| n.y.abs() <= GIRDLE_NY) {
for s in &verts {
if (n.dot(s.v) - m).abs() <= EPS_FACE {
girdle_top = girdle_top.max(s.v.y);
girdle_bottom = girdle_bottom.min(s.v.y);
}
}
}
let has_girdle = girdle_top >= girdle_bottom;
let x_max = verts
.iter()
.map(|s| s.v.x)
.fold(f64::NEG_INFINITY, f64::max);
let x_min = verts.iter().map(|s| s.v.x).fold(f64::INFINITY, f64::min);
let z_max = verts
.iter()
.map(|s| s.v.z)
.fold(f64::NEG_INFINITY, f64::max);
let z_min = verts.iter().map(|s| s.v.z).fold(f64::INFINITY, f64::min);
let (dx, dz) = (x_max - x_min, z_max - z_min);
let (width_axis, length_axis) = if dx <= dz { (dx, dz) } else { (dz, dx) };
let outline: Vec<(f64, f64)> = verts.iter().map(|s| (s.v.x, s.v.z)).collect();
let (width_caliper, length_caliper) =
caliper_extents(&outline).unwrap_or((width_axis, length_axis));
let vertex_count = verts.len();
let scaled_verts = verts.into_iter().map(|s| s.v * scale).collect();
Some((
SolidMetrics {
volume: volume * scale * scale * scale,
width_axis: width_axis * scale,
length_axis: length_axis * scale,
width_caliper: width_caliper * scale,
length_caliper: length_caliper * scale,
total_height: (y_max - y_min) * scale,
crown_height: has_girdle.then_some((y_max - girdle_top) * scale),
pavilion_depth: has_girdle.then_some((girdle_bottom - y_min) * scale),
girdle_thickness: has_girdle.then_some((girdle_top - girdle_bottom) * scale),
vertex_count,
},
scaled_verts,
))
}