diff --git a/src/components/collider.rs b/src/components/collider.rs index b8cf799d..42401c4c 100644 --- a/src/components/collider.rs +++ b/src/components/collider.rs @@ -335,6 +335,11 @@ impl Collider { ) } + /// Computes the collider's mass properties based on its shape and a given density. + pub fn mass_properties(&self, density: Scalar) -> ColliderMassProperties { + ColliderMassProperties::new(self, density) + } + /// Creates a collider with a compound shape defined by a given vector of colliders with a position and a rotation. /// /// Especially for dynamic rigid bodies, compound shape colliders should be preferred over triangle meshes and polylines, diff --git a/src/components/mass_properties.rs b/src/components/mass_properties.rs index 700f8c05..3921d35d 100644 --- a/src/components/mass_properties.rs +++ b/src/components/mass_properties.rs @@ -214,7 +214,7 @@ pub struct MassPropertiesBundle { } impl MassPropertiesBundle { - /// Computes the mass properties from a given [`Collider`] and density. + /// Computes the mass properties for a [`Collider`] based on its shape and a given density. pub fn new_computed(collider: &Collider, density: Scalar) -> Self { let ColliderMassProperties { mass, @@ -223,7 +223,7 @@ impl MassPropertiesBundle { inverse_inertia, center_of_mass, .. - } = ColliderMassProperties::new_computed(collider, density); + } = collider.mass_properties(density); Self { mass, @@ -234,27 +234,61 @@ impl MassPropertiesBundle { } } } - -/// The mass properties derived from a given collider shape and density. +/// The density of a [`Collider`], 1.0 by default. This is used for computing +/// the [`ColliderMassProperties`] for each collider. +/// +/// ## Example /// -/// These will be added to the body's actual [`Mass`], [`InverseMass`], [`Inertia`], [`InverseInertia`] and [`CenterOfMass`] components. +/// ``` +/// use bevy::prelude::*; +/// # #[cfg(feature = "2d")] +/// # use bevy_xpbd_2d::prelude::*; +/// # #[cfg(feature = "3d")] +/// # use bevy_xpbd_3d::prelude::*; +/// +/// // Spawn a body with a collider that has a density of 2.5 +/// fn setup(mut commands: Commands) { +/// commands.spawn(( +/// RigidBody::Dynamic, +/// Collider::ball(0.5), +/// ColliderDensity(2.5), +/// )); +/// } +/// ``` +#[derive(Reflect, Clone, Copy, Component, Debug, Deref, DerefMut, PartialEq, PartialOrd)] +#[reflect(Component)] +pub struct ColliderDensity(pub Scalar); + +impl ColliderDensity { + /// The density of the [`Collider`] is zero. It has no mass. + pub const ZERO: Self = Self(0.0); +} + +impl Default for ColliderDensity { + fn default() -> Self { + Self(1.0) + } +} + +/// An automatically added component that contains the read-only mass properties of a [`Collider`]. +/// The density used for computing the mass properties can be configured using the [`ColliderDensity`] +/// component. /// -/// You should generally not create or modify this directly. Instead, you can generate this automatically using a given collider shape and density with the associated `from_shape_and_density` method. +/// These mass properties will be added to the [rigid body's](RigidBody) actual [`Mass`], +/// [`InverseMass`], [`Inertia`], [`InverseInertia`] and [`CenterOfMass`] components. #[derive(Reflect, Clone, Copy, Component, Debug, PartialEq)] #[reflect(Component)] pub struct ColliderMassProperties { /// Mass given by collider. - pub mass: Mass, + pub(crate) mass: Mass, /// Inverse mass given by collider. - pub inverse_mass: InverseMass, + pub(crate) inverse_mass: InverseMass, /// Inertia given by collider. - pub inertia: Inertia, + pub(crate) inertia: Inertia, /// Inverse inertia given by collider. - pub inverse_inertia: InverseInertia, + pub(crate) inverse_inertia: InverseInertia, /// Local center of mass given by collider. - pub center_of_mass: CenterOfMass, - /// Density used for calculating other mass properties. - pub density: Scalar, + pub(crate) center_of_mass: CenterOfMass, } impl ColliderMassProperties { @@ -265,13 +299,13 @@ impl ColliderMassProperties { inertia: Inertia::ZERO, inverse_inertia: InverseInertia::ZERO, center_of_mass: CenterOfMass::ZERO, - density: 0.0, }; -} -impl ColliderMassProperties { /// Computes mass properties from a given [`Collider`] and density. - pub fn new_computed(collider: &Collider, density: Scalar) -> Self { + /// + /// Because [`ColliderMassProperties`] is read-only, adding this as a component manually + /// has no effect. The mass properties will be recomputed using the [`ColliderDensity`]. + pub fn new(collider: &Collider, density: Scalar) -> Self { let props = collider.shape_scaled().mass_properties(density); Self { @@ -289,10 +323,47 @@ impl ColliderMassProperties { inverse_inertia: InverseInertia(props.reconstruct_inverse_inertia_matrix().into()), center_of_mass: CenterOfMass(props.local_com.into()), - - density, } } + + /// Get the [mass](Mass) of the [`Collider`]. + pub fn mass(&self) -> Scalar { + self.mass.0 + } + + /// Get the [inverse mass](InverseMass) of the [`Collider`]. + pub fn inverse_mass(&self) -> Scalar { + self.inverse_mass.0 + } + + /// Get the [inerta](Inertia) of the [`Collider`]. + #[cfg(feature = "2d")] + pub fn inertia(&self) -> Scalar { + self.inertia.0 + } + + /// Get the [inertia tensor](InverseInertia) of the [`Collider`]. + #[cfg(feature = "3d")] + pub fn inertia(&self) -> Matrix3 { + self.inertia.0 + } + + /// Get the [inverse inertia](InverseInertia) of the [`Collider`]. + #[cfg(feature = "2d")] + pub fn inverse_inertia(&self) -> Scalar { + self.inverse_inertia.0 + } + + /// Get the [inverse inertia](InverseInertia) of the [`Collider`]. + #[cfg(feature = "3d")] + pub fn inverse_inertia(&self) -> Matrix3 { + self.inverse_inertia.0 + } + + /// Get the [local center of mass](CenterOfMass) of the [`Collider`]. + pub fn center_of_mass(&self) -> Vector { + self.center_of_mass.0 + } } impl Default for ColliderMassProperties { diff --git a/src/components/mod.rs b/src/components/mod.rs index 101a9041..9d634347 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -65,26 +65,54 @@ use derive_more::From; /// /// ## Adding mass properties /// -/// You should always give dynamic rigid bodies mass properties. The easiest way to do this is to [add a collider](Collider), since colliders -/// by default have [their own mass properties](ColliderMassProperties) that are added to the body's own mass properties. +/// You should always give dynamic rigid bodies mass properties so that forces are applied to them correctly. +/// +/// The easiest way to add mass properties is to simply [add a collider](Collider): /// /// ```ignore -/// // The mass properties will be computed from a ball shape with a radius of 0.5 and a density of 1. /// commands.spawn((RigidBody::Dynamic, Collider::ball(0.5))); /// ``` /// -/// If you don't want to add a collider, you can instead add a [`MassPropertiesBundle`] with the mass properties computed from a collider -/// shape using the [`MassPropertiesBundle::new_computed`](MassPropertiesBundle#method.new_computed) method. +/// This will automatically compute the [collider's mass properties](ColliderMassProperties) +/// and add them to the body's own mass properties like [`Mass`], [`Inertia`] and so on. +/// +/// By default, each collider has a density of `1.0`. This can be configured with +/// the [`ColliderDensity`] component: +/// +/// ```ignore +/// commands.spawn(( +/// RigidBody::Dynamic, +/// Collider::ball(0.5), +/// ColliderDensity(2.5), +/// )); +/// ``` +/// +/// If you don't want to add a collider, you can instead add a [`MassPropertiesBundle`] +/// with the mass properties computed from a collider shape using the +/// [`MassPropertiesBundle::new_computed`](MassPropertiesBundle#method.new_computed) method. /// /// ```ignore /// // This is equivalent to the earlier approach, but no collider will be added. -/// commands.spawn((RigidBody::Dynamic, MassPropertiesBundle::new_computed(&Collider::ball(0.5), 1.0))); +/// commands.spawn(( +/// RigidBody::Dynamic, +/// MassPropertiesBundle::new_computed(&Collider::ball(0.5), 2.5), +/// )); /// ``` /// -/// If you want, you can also define the mass properties explicitly by adding the components manually. -/// Note that the mass properties of colliders are added on top of the existing mass properties, so if you -/// want to define the body's mass properties explicitly, you might want to add -/// [`ColliderMassProperties::ZERO`](ColliderMassProperties#associatedconstant.ZERO) to the colliders. +/// You can also specify the exact values of the mass properties by adding the components manually. +/// To avoid the collider mass properties from being added to the body's own mass properties, +/// you can simply set the collider's density to zero. +/// +/// ```ignore +/// // Create a rigid body with a mass of 5.0 and a collider with no mass +/// commands.spawn(( +/// RigidBody::Dynamic, +/// Collider::ball(0.5), +/// ColliderDensity(0.0), +/// Mass(5.0), +/// // ...the rest of the mass properties +/// )); +/// ``` #[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq, Eq)] #[reflect(Component)] pub enum RigidBody { diff --git a/src/components/world_queries.rs b/src/components/world_queries.rs index 127734da..5f71c71b 100644 --- a/src/components/world_queries.rs +++ b/src/components/world_queries.rs @@ -257,8 +257,7 @@ mod tests { app.world.spawn(original_mass_props.clone()); // Create collider mass properties - let collider_mass_props = - ColliderMassProperties::new_computed(&Collider::capsule(7.4, 2.1), 14.3); + let collider_mass_props = Collider::capsule(7.4, 2.1).mass_properties(14.3); // Get the mass properties and then add and subtract the collider mass properties let mut query = app.world.query::(); diff --git a/src/plugins/prepare.rs b/src/plugins/prepare.rs index c7d0d846..994659be 100644 --- a/src/plugins/prepare.rs +++ b/src/plugins/prepare.rs @@ -66,6 +66,7 @@ impl Plugin for PreparePlugin { .chain() .run_if(any_new_physics_entities), update_mass_properties, + clamp_collider_density, clamp_restitution, // all the components we added above must exist before we can simulate the bodies apply_deferred, @@ -373,16 +374,21 @@ fn init_colliders( Entity, &Collider, Option<&ColliderAabb>, + Option<&ColliderDensity>, Option<&ColliderMassProperties>, Option<&PreviousColliderMassProperties>, ), Added, >, ) { - for (entity, collider, aabb, mass_properties, previous_mass_properties) in &mut colliders { + for (entity, collider, aabb, density, mass_properties, previous_mass_properties) in + &mut colliders + { + let density = *density.unwrap_or(&ColliderDensity::default()); commands.entity(entity).insert(( *aabb.unwrap_or(&ColliderAabb::from_shape(collider.shape_scaled())), - *mass_properties.unwrap_or(&ColliderMassProperties::new_computed(collider, 1.0)), + density, + *mass_properties.unwrap_or(&collider.mass_properties(density.0)), *previous_mass_properties.unwrap_or(&PreviousColliderMassProperties( ColliderMassProperties::ZERO, )), @@ -504,12 +510,14 @@ fn update_mass_properties( &mut PreviousColliderTransform, &ColliderParent, &Collider, + &ColliderDensity, &mut ColliderMassProperties, &mut PreviousColliderMassProperties, ), Or<( Changed, Changed, + Changed, Changed, )>, >, @@ -521,6 +529,7 @@ fn update_mass_properties( mut previous_collider_transform, collider_parent, collider, + density, mut collider_mass_properties, mut previous_collider_mass_properties, ) in &mut colliders @@ -539,8 +548,7 @@ fn update_mass_properties( // Update previous and current collider mass props previous_collider_mass_properties.0 = *collider_mass_properties; - *collider_mass_properties = - ColliderMassProperties::new_computed(collider, collider_mass_properties.density); + *collider_mass_properties = collider.mass_properties(density.max(Scalar::EPSILON)); // Add new collider mass props to the body's mass props mass_properties += ColliderMassProperties { @@ -602,3 +610,10 @@ fn clamp_restitution(mut query: Query<&mut Restitution, Changed>) { restitution.coefficient = restitution.coefficient.clamp(0.0, 1.0); } } + +/// Clamps [`ColliderDensity`] to be above 0.0. +fn clamp_collider_density(mut query: Query<&mut ColliderDensity, Changed>) { + for mut density in &mut query { + density.0 = density.max(Scalar::EPSILON); + } +} diff --git a/src/plugins/setup.rs b/src/plugins/setup.rs index 5ff57242..9bdaeaad 100644 --- a/src/plugins/setup.rs +++ b/src/plugins/setup.rs @@ -103,6 +103,7 @@ impl Plugin for PhysicsSetupPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::()