axiolid_measure/mesh_measure.rs
1//! The reference [`Measure`] provider for triangle meshes.
2//!
3//! # Why this exists
4//!
5//! `Measure<T>` was a trait with no implementor, and `MassProperties` a struct
6//! nothing produced. The parts were all present -- area, volume and centroid
7//! were computed by free functions in [`crate::mesh`] -- but nothing assembled
8//! them into the declared contract, and `second_moment_diagonal` was never
9//! computed at all.
10//!
11//! # Refusal over a plausible number
12//!
13//! Mass properties are only defined for a closed oriented solid. An open shell
14//! has a surface area but no enclosed volume, so this provider refuses it
15//! rather than reporting the divergence sum of an open surface, which is a
16//! finite number with no meaning.
17
18use axiolid_core::Tolerance;
19use axiolid_mesh::TriMesh;
20
21use crate::{
22 mesh::{second_moments, surface_properties, volume_properties},
23 MassProperties, Measure, MeshMeasureError,
24};
25
26/// Reference mass-property provider for triangle meshes.
27///
28/// Deterministic: the sums run in triangle order, so the result is
29/// reproducible for a given mesh.
30#[derive(Debug, Default, Clone, Copy)]
31pub struct MeshMeasure;
32
33impl Measure<TriMesh> for MeshMeasure {
34 type Error = MeshMeasureError;
35
36 /// Area, signed volume, volume centroid and second moments in one pass.
37 ///
38 /// The centroid reported is the VOLUME centroid, not the area centroid:
39 /// for mass properties of a solid that is the meaningful one, and
40 /// [`surface_properties`] still exposes the area centroid separately for
41 /// callers measuring a shell.
42 fn measure(
43 &self,
44 value: &TriMesh,
45 tolerance: Tolerance,
46 ) -> Result<MassProperties, Self::Error> {
47 // Volume first: it is the strictest admission (closed two-manifold),
48 // so an unusable mesh is refused before any further work.
49 let volume = volume_properties(value, tolerance)?;
50 let surface = surface_properties(value, tolerance)?;
51 let second_moment_diagonal = second_moments(value, tolerance)?;
52 Ok(MassProperties {
53 area: surface.area,
54 signed_volume: volume.signed_volume,
55 centroid: volume.centroid,
56 second_moment_diagonal,
57 })
58 }
59}