parry3d/mass_properties/
mass_properties_cuboid.rs

1use crate::mass_properties::MassProperties;
2use crate::math::{Point, PrincipalAngularInertia, Real, Vector};
3
4impl MassProperties {
5    pub(crate) fn cuboid_volume_unit_inertia(
6        half_extents: Vector<Real>,
7    ) -> (Real, PrincipalAngularInertia<Real>) {
8        #[cfg(feature = "dim2")]
9        {
10            let volume = half_extents.x * half_extents.y * 4.0;
11            let ix = (half_extents.x * half_extents.x) / 3.0;
12            let iy = (half_extents.y * half_extents.y) / 3.0;
13
14            (volume, ix + iy)
15        }
16
17        #[cfg(feature = "dim3")]
18        {
19            let volume = half_extents.x * half_extents.y * half_extents.z * 8.0;
20            let ix = (half_extents.x * half_extents.x) / 3.0;
21            let iy = (half_extents.y * half_extents.y) / 3.0;
22            let iz = (half_extents.z * half_extents.z) / 3.0;
23
24            (volume, Vector::new(iy + iz, ix + iz, ix + iy))
25        }
26    }
27
28    /// Computes the mass properties of a cuboid (box in 3D, rectangle in 2D).
29    ///
30    /// A cuboid is a box-shaped object with three dimensions (or two in 2D), where each
31    /// dimension can have a different size. The cuboid is centered at the origin, and
32    /// `half_extents` define the distance from the center to each face.
33    ///
34    /// # Arguments
35    ///
36    /// * `density` - The material density (mass per unit volume/area). Higher values make heavier objects.
37    ///   - In 3D: units are typically kg/m³ (e.g., wood = 500-900, concrete = 2400)
38    ///   - In 2D: units are typically kg/m² (mass per unit area)
39    /// * `half_extents` - Half the size along each axis (center to face distance).
40    ///   - In 3D: `Vector3::new(hx, hy, hz)` creates a box with dimensions 2hx × 2hy × 2hz
41    ///   - In 2D: `Vector2::new(hx, hy)` creates a rectangle with dimensions 2hx × 2hy
42    ///
43    /// # Returns
44    ///
45    /// A `MassProperties` struct containing:
46    /// - **mass**: Total mass calculated from volume and density
47    /// - **local_com**: Center of mass at the origin (cuboids are symmetric)
48    /// - **inv_principal_inertia**: Inverse angular inertia along each axis
49    ///
50    /// # Physics Background
51    ///
52    /// Cuboids have axis-aligned mass distribution:
53    /// - Center of mass is at the geometric center (origin)
54    /// - Angular inertia varies per axis based on dimensions
55    /// - Longer dimensions increase inertia around perpendicular axes
56    /// - In 3D, each axis has different inertia: I_x depends on y and z extents, etc.
57    ///
58    /// # Example (3D) - Wooden Crate
59    ///
60    /// ```
61    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
62    /// use parry3d::mass_properties::MassProperties;
63    /// use nalgebra::{Point3, Vector3};
64    ///
65    /// // Create a wooden crate: 2m × 1m × 1m (half_extents = 1.0, 0.5, 0.5)
66    /// // Wood density: approximately 600 kg/m³
67    /// let half_extents = Vector3::new(1.0, 0.5, 0.5);
68    /// let density = 600.0;
69    /// let crate_props = MassProperties::from_cuboid(density, half_extents);
70    ///
71    /// // Volume = (2 * 1.0) × (2 * 0.5) × (2 * 0.5) = 2 m³
72    /// // Mass = volume × density = 1200 kg
73    /// let mass = crate_props.mass();
74    /// assert!((mass - 1200.0).abs() < 0.1);
75    ///
76    /// // Longer dimension (x-axis) means higher inertia around y and z axes
77    /// let inertia = crate_props.principal_inertia();
78    /// println!("Inertia around x-axis: {:.2}", inertia.x); // Lowest (easier to spin around length)
79    /// println!("Inertia around y-axis: {:.2}", inertia.y); // Higher
80    /// println!("Inertia around z-axis: {:.2}", inertia.z); // Higher
81    ///
82    /// // Center of mass is at the origin
83    /// assert_eq!(crate_props.local_com, Point3::origin());
84    /// # }
85    /// ```
86    ///
87    /// # Example (3D) - Cube
88    ///
89    /// ```
90    /// # #[cfg(all(feature = "dim3", feature = "f32"))] {
91    /// use parry3d::mass_properties::MassProperties;
92    /// use nalgebra::Vector3;
93    ///
94    /// // Create a 1m × 1m × 1m cube (half_extents = 0.5 on all axes)
95    /// let half_extents = Vector3::new(0.5, 0.5, 0.5);
96    /// let density = 1000.0; // Water density
97    /// let cube_props = MassProperties::from_cuboid(density, half_extents);
98    ///
99    /// // Volume = 1 m³, Mass = 1000 kg
100    /// assert!((cube_props.mass() - 1000.0).abs() < 0.1);
101    ///
102    /// // For a cube, all axes have equal inertia (symmetric)
103    /// let inertia = cube_props.principal_inertia();
104    /// assert!((inertia.x - inertia.y).abs() < 0.01);
105    /// assert!((inertia.y - inertia.z).abs() < 0.01);
106    /// # }
107    /// ```
108    ///
109    /// # Example (2D) - Rectangular Platform
110    ///
111    /// ```
112    /// # #[cfg(all(feature = "dim2", feature = "f32"))] {
113    /// use parry2d::mass_properties::MassProperties;
114    /// use nalgebra::Vector2;
115    ///
116    /// // Create a 4m × 2m rectangular platform (half_extents = 2.0, 1.0)
117    /// let half_extents = Vector2::new(2.0, 1.0);
118    /// let density = 500.0; // kg/m²
119    /// let platform_props = MassProperties::from_cuboid(density, half_extents);
120    ///
121    /// // Area = (2 * 2.0) × (2 * 1.0) = 8 m²
122    /// // Mass = area × density = 4000 kg
123    /// let mass = platform_props.mass();
124    /// assert!((mass - 4000.0).abs() < 0.1);
125    ///
126    /// println!("Platform mass: {:.2} kg", mass);
127    /// println!("Moment of inertia: {:.2}", platform_props.principal_inertia());
128    /// # }
129    /// ```
130    ///
131    /// # Use Cases
132    ///
133    /// - **Boxes and crates**: Storage containers, shipping boxes
134    /// - **Building blocks**: Walls, floors, platforms
135    /// - **Vehicles**: Simplified car or truck bodies
136    /// - **Furniture**: Tables, chairs, cabinets
137    /// - **Terrain**: Rectangular ground segments
138    ///
139    /// # Common Mistakes
140    ///
141    /// - **Wrong dimensions**: Remember that `half_extents` are HALF the total size.
142    ///   For a 2m × 2m × 2m box, use `Vector3::new(1.0, 1.0, 1.0)`, not `(2.0, 2.0, 2.0)`
143    /// - **Unit confusion**: Ensure density units match your distance units
144    ///   (kg/m³ with meters, kg/cm³ with centimeters, etc.)
145    ///
146    /// # Performance Note
147    ///
148    /// This is a very fast computation (constant time). Cuboids are the second simplest
149    /// shape after balls and are highly efficient for collision detection.
150    pub fn from_cuboid(density: Real, half_extents: Vector<Real>) -> Self {
151        let (vol, unit_i) = Self::cuboid_volume_unit_inertia(half_extents);
152        let mass = vol * density;
153        Self::new(Point::origin(), mass, unit_i * mass)
154    }
155}