Files
OneCAD/crates/kernel/src/measure.rs
T
2026-10-07 19:22:27 +00:00

76 lines
2.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use serde::Serialize;
use solvecraft_geom::{Aabb3, Mesh, Vec3};
use crate::Result;
use crate::body::Body;
use crate::topo::{TopoCounts, merged_topology};
/// Measured properties of a body.
#[derive(Clone, Debug, Serialize)]
pub struct BodyMeasure {
pub volume: f64,
pub area: f64,
pub centroid: Vec3,
pub bbox: Aabb3,
/// Raw kernel topology counts (closed curves and surfaces may be split in pieces).
pub faces: usize,
pub edges: usize,
pub vertices: usize,
pub shells: usize,
/// Counts after merging split pieces of the same analytic surface/curve (comparable with
/// systems that use periodic faces and edges).
pub merged: TopoCounts,
}
/// Per-face sums: area, 6 × signed volume and 24 × first moments (divergence theorem, origin 0).
fn face_sums(m: &Mesh, nf: usize) -> Vec<(f64, f64, Vec3)> {
let mut out = vec![(0.0, 0.0, Vec3::ZERO); nf];
for (t, f) in m.triangles.iter().zip(&m.tri_face) {
let (Some([a, b, c]), Some(slot)) = (m.tri(t), out.get_mut(*f as usize)) else { continue };
let d = a.dot(b.cross(c));
slot.0 += (b - a).cross(c - a).len() * 0.5;
slot.1 += d;
slot.2 += (a + b + c) * d;
}
out
}
/// Measure a body from a fine tessellation (chord tolerance 5e-5 of the body size). Faces whose
/// fine tessellation disagrees with a coarse one (a meshing failure) use the coarse result.
pub fn measure(b: &Body) -> Result<BodyMeasure> {
let size = b.size();
let fine = b.tessellate((size * 5e-5).max(1e-4))?;
let medium = b.tessellate((size * 2.5e-4).max(2e-4))?;
let coarse = b.tessellate((size * 1e-3).max(1e-3))?;
let nf = b.face_count();
let (sf, sm, sc) = (face_sums(&fine, nf), face_sums(&medium, nf), face_sums(&coarse, nf));
let close = |x: f64, y: f64| (x - y).abs() <= 0.01 * x.abs().max(y.abs()) + 1e-9;
let (mut area, mut v6, mut mom) = (0.0, 0.0, Vec3::ZERO);
for ((f, m), c) in sf.iter().zip(&sm).zip(&sc) {
// Trust the finest tessellation that agrees with the next coarser one.
let pick = if close(f.0, m.0) {
f
} else if close(m.0, c.0) {
m
} else {
f
};
area += pick.0;
v6 += pick.1;
mom += pick.2;
}
let volume = v6 / 6.0;
let centroid = if v6.abs() > 1e-12 { mom / (4.0 * v6) } else { fine.bounds().center() };
Ok(BodyMeasure {
volume,
area,
centroid,
bbox: coarse.bounds().union(&fine.bounds()),
faces: b.face_count(),
edges: b.edge_count(),
vertices: b.vertex_count(),
shells: b.shell_count(),
merged: merged_topology(b, &coarse)?,
})
}