use crate::mesh::MeshBuffers;
#[derive(Debug, Clone, PartialEq)]
pub struct Aabb {
pub min: [f32; 3],
pub max: [f32; 3],
}
impl Aabb {
pub fn height(&self) -> f32 {
self.max[1] - self.min[1]
}
pub fn width(&self) -> f32 {
self.max[0] - self.min[0]
}
pub fn depth(&self) -> f32 {
self.max[2] - self.min[2]
}
pub fn center(&self) -> [f32; 3] {
[
(self.min[0] + self.max[0]) * 0.5,
(self.min[1] + self.max[1]) * 0.5,
(self.min[2] + self.max[2]) * 0.5,
]
}
}
pub fn compute_aabb(buf: &MeshBuffers) -> Option<Aabb> {
if buf.positions.is_empty() {
return None;
}
let mut min = buf.positions[0];
let mut max = buf.positions[0];
for p in &buf.positions {
for i in 0..3 {
if p[i] < min[i] {
min[i] = p[i];
}
if p[i] > max[i] {
max[i] = p[i];
}
}
}
Some(Aabb { min, max })
}
#[derive(Debug, Clone)]
pub struct BodyMeasurements {
pub total_height: f32,
pub max_width: f32,
pub max_depth: f32,
pub torso_height: f32,
pub shoulder_width: f32,
pub waist_width: f32,
pub hip_width: f32,
}
pub fn compute_measurements(buf: &MeshBuffers) -> Option<BodyMeasurements> {
let aabb = compute_aabb(buf)?;
let total_height = aabb.height();
if total_height < 1e-6 {
return None;
}
let y_min = aabb.min[1];
let shoulder_w = width_at_height_fraction(buf, y_min, total_height, 0.75);
let waist_w = width_at_height_fraction(buf, y_min, total_height, 0.55);
let hip_w = width_at_height_fraction(buf, y_min, total_height, 0.35);
Some(BodyMeasurements {
total_height,
max_width: aabb.width(),
max_depth: aabb.depth(),
torso_height: total_height * 0.30, shoulder_width: shoulder_w,
waist_width: waist_w,
hip_width: hip_w,
})
}
fn width_at_height_fraction(
buf: &MeshBuffers,
y_min: f32,
total_height: f32,
fraction: f32,
) -> f32 {
let target_y = y_min + total_height * fraction;
let tolerance = total_height * 0.05;
let mut x_min = f32::INFINITY;
let mut x_max = f32::NEG_INFINITY;
let mut found = false;
for p in &buf.positions {
if (p[1] - target_y).abs() <= tolerance {
if p[0] < x_min {
x_min = p[0];
}
if p[0] > x_max {
x_max = p[0];
}
found = true;
}
}
if found {
x_max - x_min
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mesh::MeshBuffers as MyMesh;
use oxihuman_morph::engine::MeshBuffers as MB;
fn unit_cube_verts() -> MyMesh {
let positions = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 0.0, 1.0],
[0.0, 1.0, 1.0],
[1.0, 1.0, 1.0],
];
MyMesh::from_morph(MB {
positions,
normals: vec![[0.0, 1.0, 0.0]; 8],
uvs: vec![[0.0, 0.0]; 8],
indices: vec![0, 1, 2],
has_suit: false,
})
}
#[test]
fn aabb_unit_cube() {
let m = unit_cube_verts();
let aabb = compute_aabb(&m).expect("should succeed");
assert!((aabb.height() - 1.0).abs() < 1e-6);
assert!((aabb.width() - 1.0).abs() < 1e-6);
assert!((aabb.depth() - 1.0).abs() < 1e-6);
}
#[test]
fn aabb_center() {
let m = unit_cube_verts();
let aabb = compute_aabb(&m).expect("should succeed");
let c = aabb.center();
assert!((c[0] - 0.5).abs() < 1e-6);
assert!((c[1] - 0.5).abs() < 1e-6);
assert!((c[2] - 0.5).abs() < 1e-6);
}
#[test]
fn empty_mesh_returns_none() {
let m = MyMesh::from_morph(MB {
positions: vec![],
normals: vec![],
uvs: vec![],
indices: vec![],
has_suit: false,
});
assert!(compute_aabb(&m).is_none());
assert!(compute_measurements(&m).is_none());
}
#[test]
fn measurements_non_negative() {
let m = unit_cube_verts();
let meas = compute_measurements(&m).expect("should succeed");
assert!(meas.total_height > 0.0);
assert!(meas.max_width > 0.0);
assert!(meas.shoulder_width >= 0.0);
assert!(meas.waist_width >= 0.0);
assert!(meas.hip_width >= 0.0);
}
#[test]
fn real_base_mesh_measurements() {
use oxihuman_core::parser::obj::parse_obj;
let path = oxihuman_test_utils::base_obj();
if let Ok(src) = std::fs::read_to_string(&path) {
if let Ok(obj) = parse_obj(&src) {
let morph_buf = MB {
positions: obj.positions,
normals: obj.normals,
uvs: obj.uvs,
indices: obj.indices,
has_suit: false,
};
let mesh = MyMesh::from_morph(morph_buf);
let meas = compute_measurements(&mesh).expect("should succeed");
assert!(
meas.total_height > 1.0,
"height too small: {}",
meas.total_height
);
assert!(meas.shoulder_width > 0.0);
}
}
}
}