phys_geom/
volume.rs

1// Copyright (C) 2020-2025 phys-geom authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Volume computation and mesh processing utilities.
16//!
17//! This module provides functionality for computing volumes of geometric shapes and
18//! processing triangle meshes. It includes both individual shape volume calculations
19//! and mesh-based operations like signed volume computation and bounding box calculation.
20//!
21//! # Volume Computation
22//!
23//! The module provides a [`ComputeVolume`] trait that is implemented for all
24//! supported geometric shapes. This allows for uniform volume calculation across
25//! different shape types.
26//!
27//! # Mesh Processing
28//!
29//! Additional utilities are provided for working with triangle meshes:
30//! - Signed volume computation for watertight meshes
31//! - Axis-aligned bounding box computation
32//!
33//! # Examples
34//!
35//! ```rust
36//! use phys_geom::shape::{Sphere, Cuboid};
37//! use phys_geom::volume::ComputeVolume;
38//!
39//! // Compute volume of individual shapes
40//! let sphere = Sphere::new(1.0);
41//! let sphere_volume = sphere.compute_volume(); // 4π/3 ≈ 4.1888
42//!
43//! let cuboid = Cuboid::new([2.0, 3.0, 1.0]);
44//! let cuboid_volume = cuboid.compute_volume(); // 6.0
45//! ```
46//!
47//! # Mathematical Accuracy
48//!
49//! All volume calculations use mathematically accurate formulas:
50//! - Sphere: V = (4/3)πr³
51//! - Capsule: V = πr²(h + 4r/3) where h is the cylindrical height
52//! - Cuboid: V = l × w × h
53//! - Cylinder: V = πr²h
54//!
55//! The calculations are optimized for performance while maintaining numerical precision.
56
57use crate::math::{Real, PI};
58use crate::shape::{Capsule, Cuboid, Cylinder, InfinitePlane, Sphere, Tetrahedron};
59use crate::Aabb3;
60
61/// Trait for computing the volume of geometric shapes.
62///
63/// This trait provides a uniform interface for volume calculation across different
64/// geometric shape types. All shapes implement this trait, allowing for generic
65/// volume computation algorithms.
66///
67/// # Examples
68///
69/// ```rust
70/// use phys_geom::shape::{Sphere, Cuboid};
71/// use phys_geom::volume::ComputeVolume;
72/// use phys_geom::Real;
73///
74/// fn calculate_total_volume<T: ComputeVolume>(shapes: &[T]) -> Real {
75///     shapes.iter().map(|s| s.compute_volume()).sum()
76/// }
77///
78/// // Works with homogeneous collections
79/// let spheres = vec![Sphere::new(1.0), Sphere::new(2.0)];
80/// let sphere_total = calculate_total_volume(&spheres);
81///
82/// let cuboids = vec![Cuboid::new([1.0, 1.0, 1.0]), Cuboid::new([2.0, 2.0, 2.0])];
83/// let cuboid_total = calculate_total_volume(&cuboids);
84/// ```
85///
86/// # Implementation Notes
87///
88/// Implementations should use mathematically accurate formulas and be optimized
89/// for performance. Volume calculations should be deterministic and return
90/// positive values for valid shapes.
91pub trait ComputeVolume {
92    /// Computes the volume of this shape.
93    ///
94    /// # Returns
95    ///
96    /// The volume as a `Real` (f32 or f64 depending on features).
97    /// Always returns a non-negative value for valid shapes.
98    ///
99    /// # Examples
100    ///
101    /// ```rust
102    /// use phys_geom::shape::Sphere;
103    /// use phys_geom::volume::ComputeVolume;
104    ///
105    /// let sphere = Sphere::new(2.0);
106    /// let volume = sphere.compute_volume();
107    /// assert!(volume > 0.0);
108    /// ```
109    fn compute_volume(&self) -> Real;
110}
111
112impl ComputeVolume for Sphere {
113    #[inline]
114    fn compute_volume(&self) -> Real {
115        const A: Real = 4.0 * PI / 3.0;
116        let r = self.radius();
117        A * r * r * r
118    }
119}
120
121impl ComputeVolume for Capsule {
122    #[inline]
123    fn compute_volume(&self) -> Real {
124        const A: Real = 4.0 / 3.0;
125        let r = self.radius();
126        let h = self.height();
127        PI * r * r * (h + A * r)
128    }
129}
130
131impl ComputeVolume for Cuboid {
132    #[inline]
133    fn compute_volume(&self) -> Real {
134        let half_length = self.half_length();
135        half_length[0] * half_length[1] * half_length[2] * 8.0
136    }
137}
138
139impl ComputeVolume for Cylinder {
140    #[inline]
141    fn compute_volume(&self) -> Real {
142        let r = self.radius();
143        let half_v = PI * r * r * self.half_height();
144        half_v + half_v
145    }
146}
147
148impl ComputeVolume for InfinitePlane {
149    #[inline]
150    fn compute_volume(&self) -> Real {
151        0.0
152    }
153}
154
155/// Computes the signed volume of a triangle mesh using the divergence theorem.
156///
157/// This function calculates the signed volume of a closed, watertight triangle mesh
158/// by summing the signed volumes of tetrahedra formed by each triangle face and the origin.
159/// The sign of the volume depends on the winding order of the triangle vertices.
160///
161/// # Mathematical Background
162///
163/// The signed volume is computed using the formula:
164/// V = Σ(V_tetrahedron) for each triangle, where each tetrahedron is formed
165/// by the origin and the triangle vertices.
166///
167/// # Vertex Ordering
168///
169/// - **Counter-clockwise** (when viewed from outside): Positive volume
170/// - **Clockwise** (when viewed from outside): Negative volume
171///
172/// The vertex ordering should be consistent across all triangles for accurate results.
173///
174/// # Use Cases
175///
176/// - **Mesh validation**: Checking if a mesh is watertight (volume should be consistent)
177/// - **Physics simulations**: Computing mass properties of complex objects
178/// - **3D modeling**: Calculating volumes of imported meshes
179/// - **Computational geometry**: Determining inside/outside relationships
180///
181/// # Arguments
182///
183/// * `vertices` - An iterator over triangle vertices, yielding 3 vertices per triangle
184///
185/// # Returns
186///
187/// The signed volume as a `Real` value.
188///
189/// # Panics
190///
191/// Panics if the number of vertices is not a multiple of 3, indicating malformed
192/// triangle data.
193///
194/// # Examples
195///
196/// ```rust
197/// use phys_geom::volume::triangle_mesh_signed;
198///
199/// // A simple tetrahedron represented as 4 triangles
200/// let vertices = vec![
201///     // Triangle 1
202///     [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0],
203///     // Triangle 2
204///     [0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0],
205///     // ... more triangles
206/// ];
207///
208/// let volume = triangle_mesh_signed(vertices.into_iter());
209/// ```
210///
211/// # Performance
212///
213/// The algorithm processes each triangle exactly once, making it O(n) where n is
214/// the number of triangles. The computation is efficient and suitable for large meshes.
215#[inline]
216pub fn triangle_mesh_signed(mut vertices: impl Iterator<Item = [Real; 3]>) -> Real {
217    let mut volume = 0.0;
218    while let Some(v0) = vertices.next() {
219        let v1 = vertices
220            .next()
221            .expect("The vertices should be a multiple of 3.");
222        let v2 = vertices
223            .next()
224            .expect("The vertices should be a multiple of 3.");
225        let dv = Tetrahedron {
226            a: [0.0, 0.0, 0.0],
227            b: v0,
228            c: v1,
229            d: v2,
230        }
231        .signed_volume();
232        volume += dv;
233    }
234    volume
235}
236
237/// Computes the axis-aligned bounding box (AABB) of a mesh or point cloud.
238///
239/// This function calculates the smallest axis-aligned bounding box that contains
240/// all the provided vertices. It's useful for:
241/// - Quick collision detection broad-phase
242/// - Spatial partitioning and culling
243/// - Mesh visualization and processing
244/// - Bounding volume hierarchy construction
245///
246/// # Algorithm
247///
248/// The function iterates through all vertices, tracking the minimum and maximum
249/// coordinate values along each axis. The resulting AABB is defined by these
250/// extreme points.
251///
252/// # Arguments
253///
254/// * `vertices` - An iterator over 3D points [x, y, z]
255///
256/// # Returns
257///
258/// An [`Aabb3`] that contains all input vertices.
259///
260/// # Special Cases
261///
262/// - **Empty iterator**: Returns an AABB with MAX/MIN values (anti-infinite)
263/// - **Single point**: Returns a zero-volume AABB at that point
264/// - **Coplanar points**: Returns a flat AABB with zero volume in one or more dimensions
265///
266/// # Examples
267///
268/// ```rust
269/// use phys_geom::volume::compute_mesh_bound;
270///
271/// let vertices = vec![
272///     [0.0, 0.0, 0.0],
273///     [1.0, 0.0, 0.0],
274///     [0.5, 1.0, 0.0],
275///     [0.5, 0.5, 1.0],
276/// ];
277///
278/// let aabb = compute_mesh_bound(vertices.into_iter());
279///
280/// // The AABB should contain all points
281/// assert_eq!(aabb.min(), [0.0, 0.0, 0.0].into());
282/// assert_eq!(aabb.max(), [1.0, 1.0, 1.0].into());
283/// ```
284///
285/// # Performance
286///
287/// The algorithm runs in O(n) time where n is the number of vertices, with O(1)
288/// additional memory usage. It processes each vertex exactly once.
289///
290/// # Numerical Considerations
291///
292/// The function uses the floating-point min/max operations directly. For very
293/// large or very small coordinate values, consider the precision limitations
294/// of the underlying `Real` type.
295pub fn compute_mesh_bound(vertices: impl Iterator<Item = [Real; 3]>) -> Aabb3 {
296    let mut min = [Real::MAX; 3];
297    let mut max = [Real::MIN; 3];
298    for point in vertices {
299        min = [
300            min[0].min(point[0]),
301            min[1].min(point[1]),
302            min[2].min(point[2]),
303        ];
304        max = [
305            max[0].max(point[0]),
306            max[1].max(point[1]),
307            max[2].max(point[2]),
308        ];
309    }
310    Aabb3::new_unchecked(min, max)
311}
312
313#[cfg(test)]
314mod tests {
315    use crate::shape::{InfinitePlane, Tetrahedron};
316    use crate::volume::ComputeVolume;
317
318    #[test]
319    fn test_infinite_plane() {
320        let infinite_plane = InfinitePlane::default();
321        assert_eq!(infinite_plane.compute_volume(), 0.0);
322    }
323
324    #[test]
325    fn test_triangle_mesh() {
326        let tet = [
327            [1.0, 0.0, 0.0],
328            [2.0, 0.0, 0.0],
329            [1.0, 1.0, 0.0],
330            [1.0, 0.0, 1.0],
331        ];
332        let target_volume = Tetrahedron {
333            a: tet[0],
334            b: tet[1],
335            c: tet[2],
336            d: tet[3],
337        }
338        .signed_volume();
339        let indices = [0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3];
340
341        let triangle_iter = indices.iter().map(|&i| tet[i]);
342
343        let mesh_volume = crate::volume::triangle_mesh_signed(triangle_iter);
344        assert_eq!(target_volume, mesh_volume);
345        assert_eq!(mesh_volume, 1.0 / 6.0);
346    }
347}