#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct VolumeResult {
pub volume: f32,
pub is_watertight: bool,
}
#[allow(dead_code)]
pub fn mesh_volume_signed(positions: &[[f32; 3]], indices: &[[u32; 3]]) -> f32 {
let mut vol = 0.0f32;
for tri in indices {
let a = positions[tri[0] as usize];
let b = positions[tri[1] as usize];
let c = positions[tri[2] as usize];
vol += a[0] * (b[1] * c[2] - b[2] * c[1])
+ b[0] * (c[1] * a[2] - c[2] * a[1])
+ c[0] * (a[1] * b[2] - a[2] * b[1]);
}
vol / 6.0
}
#[allow(dead_code)]
pub fn mesh_volume_unsigned(positions: &[[f32; 3]], indices: &[[u32; 3]]) -> f32 {
mesh_volume_signed(positions, indices).abs()
}
#[allow(dead_code)]
pub fn is_watertight(indices: &[[u32; 3]]) -> bool {
use std::collections::HashMap;
let mut edge_count: HashMap<(u32, u32), u32> = HashMap::new();
for tri in indices {
for i in 0..3 {
let a = tri[i];
let b = tri[(i + 1) % 3];
let key = if a < b { (a, b) } else { (b, a) };
*edge_count.entry(key).or_insert(0) += 1;
}
}
edge_count.values().all(|&c| c == 2)
}
#[allow(dead_code)]
pub fn volume_center_of_mass(positions: &[[f32; 3]], indices: &[[u32; 3]]) -> [f32; 3] {
let mut cx = 0.0f32;
let mut cy = 0.0f32;
let mut cz = 0.0f32;
let mut total_vol = 0.0f32;
for tri in indices {
let a = positions[tri[0] as usize];
let b = positions[tri[1] as usize];
let c = positions[tri[2] as usize];
let v = (a[0] * (b[1] * c[2] - b[2] * c[1])
+ b[0] * (c[1] * a[2] - c[2] * a[1])
+ c[0] * (a[1] * b[2] - a[2] * b[1])) / 6.0;
let centroid = [
(a[0] + b[0] + c[0]) / 4.0,
(a[1] + b[1] + c[1]) / 4.0,
(a[2] + b[2] + c[2]) / 4.0,
];
cx += v * centroid[0];
cy += v * centroid[1];
cz += v * centroid[2];
total_vol += v;
}
if total_vol.abs() < 1e-12 {
return [0.0; 3];
}
[cx / total_vol, cy / total_vol, cz / total_vol]
}
#[allow(dead_code)]
pub fn volume_inertia_approx(positions: &[[f32; 3]], indices: &[[u32; 3]]) -> f32 {
let vol = mesh_volume_unsigned(positions, indices);
let com = volume_center_of_mass(positions, indices);
let mut max_r2 = 0.0f32;
for p in positions {
let dx = p[0] - com[0];
let dy = p[1] - com[1];
let dz = p[2] - com[2];
let r2 = dx * dx + dy * dy + dz * dz;
if r2 > max_r2 {
max_r2 = r2;
}
}
vol * max_r2
}
#[allow(dead_code)]
pub fn volume_bbox_ratio(positions: &[[f32; 3]], indices: &[[u32; 3]]) -> f32 {
if positions.is_empty() {
return 0.0;
}
let mut min = [f32::MAX; 3];
let mut max = [f32::MIN; 3];
for p in positions {
for i in 0..3 {
if p[i] < min[i] { min[i] = p[i]; }
if p[i] > max[i] { max[i] = p[i]; }
}
}
let bbox_vol = (max[0] - min[0]) * (max[1] - min[1]) * (max[2] - min[2]);
if bbox_vol.abs() < 1e-12 {
return 0.0;
}
mesh_volume_unsigned(positions, indices) / bbox_vol
}
#[allow(dead_code)]
pub fn volume_to_json(positions: &[[f32; 3]], indices: &[[u32; 3]]) -> String {
let vol = mesh_volume_signed(positions, indices);
let wt = is_watertight(indices);
format!("{{\"volume\":{:.6},\"watertight\":{}}}", vol, wt)
}
#[allow(dead_code)]
pub fn volume_is_valid(vol: f32) -> bool {
vol.is_finite()
}
#[cfg(test)]
mod tests {
use super::*;
fn unit_cube() -> (Vec<[f32; 3]>, Vec<[u32; 3]>) {
let p = vec![
[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0],
[0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [1.0, 1.0, 1.0], [0.0, 1.0, 1.0],
];
let i = vec![
[0, 2, 1], [0, 3, 2],
[4, 5, 6], [4, 6, 7],
[0, 1, 5], [0, 5, 4],
[2, 3, 7], [2, 7, 6],
[1, 2, 6], [1, 6, 5],
[0, 4, 7], [0, 7, 3],
];
(p, i)
}
#[test]
fn test_signed_volume_cube() {
let (p, i) = unit_cube();
let vol = mesh_volume_signed(&p, &i);
assert!((vol - 1.0).abs() < 1e-4);
}
#[test]
fn test_unsigned_volume() {
let (p, i) = unit_cube();
assert!((mesh_volume_unsigned(&p, &i) - 1.0).abs() < 1e-4);
}
#[test]
fn test_is_watertight() {
let (_, i) = unit_cube();
assert!(is_watertight(&i));
}
#[test]
fn test_not_watertight() {
let i = vec![[0u32, 1, 2]];
assert!(!is_watertight(&i));
}
#[test]
fn test_center_of_mass() {
let (p, i) = unit_cube();
let com = volume_center_of_mass(&p, &i);
assert!((com[0] - 0.5).abs() < 0.1);
assert!((com[1] - 0.5).abs() < 0.1);
assert!((com[2] - 0.5).abs() < 0.1);
}
#[test]
fn test_volume_inertia_approx() {
let (p, i) = unit_cube();
let inertia = volume_inertia_approx(&p, &i);
assert!(inertia > 0.0);
}
#[test]
fn test_volume_bbox_ratio() {
let (p, i) = unit_cube();
let ratio = volume_bbox_ratio(&p, &i);
assert!((ratio - 1.0).abs() < 0.1);
}
#[test]
fn test_volume_to_json() {
let (p, i) = unit_cube();
let json = volume_to_json(&p, &i);
assert!(json.contains("volume"));
assert!(json.contains("watertight"));
}
#[test]
fn test_volume_is_valid() {
assert!(volume_is_valid(1.0));
assert!(!volume_is_valid(f32::NAN));
assert!(!volume_is_valid(f32::INFINITY));
}
#[test]
fn test_empty_mesh_volume() {
assert!((mesh_volume_signed(&[], &[])).abs() < 1e-6);
}
}