diff --git a/CHANGELOG.md b/CHANGELOG.md index 88bef488..eb272d39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,17 @@ ### Added +- `InteractionTestMode`: Specifies which method should be used to test interactions. Supports `AND` and `OR`. +- `CoefficientCombineRule::Sum` - Adds the two coefficients and does a clamp to have at most 1. - `RigidBodySet` and `ColliderSet` have a new constructor `with_capacity`. ### Modified - `InteractionGroups` default value for `memberships` is now `GROUP_1` (#706) - `ImpulseJointSet::get_mut` has a new parameter `wake_up: bool`, to wake up connected bodies. +- `InteractionGroups` struct now contains `InteractionTestMode`. Continues [rapier/pull/170](https://github.com/dimforge/rapier/pull/170) for [rapier/issues/622](https://github.com/dimforge/rapier/issues/622) +- `InteractionGroups` constructor now requires an `InteractionTestMode` parameter. If you want same behaviour as before, use `InteractionTestMode::AND` (eg. `InteractionGroups::new(Group::GROUP_1, Group::GROUP_1, InteractionTestMode::AND)`) +- `CoefficientCombineRule::Min` - now makes sure it uses a non zero value as result by using `coeff1.min(coeff2).abs()` ## v0.22.0 (20 July 2024) diff --git a/examples2d/collision_groups2.rs b/examples2d/collision_groups2.rs index f84f139a..2784c920 100644 --- a/examples2d/collision_groups2.rs +++ b/examples2d/collision_groups2.rs @@ -24,8 +24,10 @@ pub fn init_world(testbed: &mut Testbed) { /* * Setup groups */ - const GREEN_GROUP: InteractionGroups = InteractionGroups::new(Group::GROUP_1, Group::GROUP_1); - const BLUE_GROUP: InteractionGroups = InteractionGroups::new(Group::GROUP_2, Group::GROUP_2); + const GREEN_GROUP: InteractionGroups = + InteractionGroups::new(Group::GROUP_1, Group::GROUP_1, InteractionTestMode::AND); + const BLUE_GROUP: InteractionGroups = + InteractionGroups::new(Group::GROUP_2, Group::GROUP_2, InteractionTestMode::AND); /* * A green floor that will collide with the GREEN group only. diff --git a/examples3d/collision_groups3.rs b/examples3d/collision_groups3.rs index b9eea792..7c8f3577 100644 --- a/examples3d/collision_groups3.rs +++ b/examples3d/collision_groups3.rs @@ -24,8 +24,10 @@ pub fn init_world(testbed: &mut Testbed) { /* * Setup groups */ - const GREEN_GROUP: InteractionGroups = InteractionGroups::new(Group::GROUP_1, Group::GROUP_1); - const BLUE_GROUP: InteractionGroups = InteractionGroups::new(Group::GROUP_2, Group::GROUP_2); + const GREEN_GROUP: InteractionGroups = + InteractionGroups::new(Group::GROUP_1, Group::GROUP_1, InteractionTestMode::AND); + const BLUE_GROUP: InteractionGroups = + InteractionGroups::new(Group::GROUP_2, Group::GROUP_2, InteractionTestMode::AND); /* * A green floor that will collide with the GREEN group only. diff --git a/examples3d/vehicle_joints3.rs b/examples3d/vehicle_joints3.rs index 517bd9cb..fd2633fa 100644 --- a/examples3d/vehicle_joints3.rs +++ b/examples3d/vehicle_joints3.rs @@ -53,7 +53,11 @@ pub fn init_world(testbed: &mut Testbed) { let body_co = ColliderBuilder::cuboid(0.65, 0.3, 0.9) .density(100.0) - .collision_groups(InteractionGroups::new(CAR_GROUP, !CAR_GROUP)); + .collision_groups(InteractionGroups::new( + CAR_GROUP, + !CAR_GROUP, + InteractionTestMode::AND, + )); let body_rb = RigidBodyBuilder::dynamic() .position(body_position.into()) .build(); @@ -85,7 +89,11 @@ pub fn init_world(testbed: &mut Testbed) { // is mathematically simpler than a cylinder and cheaper to compute for collision-detection. let wheel_co = ColliderBuilder::ball(wheel_radius) .density(100.0) - .collision_groups(InteractionGroups::new(CAR_GROUP, !CAR_GROUP)) + .collision_groups(InteractionGroups::new( + CAR_GROUP, + !CAR_GROUP, + InteractionTestMode::AND, + )) .friction(1.0); let wheel_rb = RigidBodyBuilder::dynamic().position(wheel_center.into()); let wheel_handle = bodies.insert(wheel_rb); diff --git a/src/dynamics/coefficient_combine_rule.rs b/src/dynamics/coefficient_combine_rule.rs index 9f99b7da..6d4a6a1f 100644 --- a/src/dynamics/coefficient_combine_rule.rs +++ b/src/dynamics/coefficient_combine_rule.rs @@ -19,6 +19,8 @@ pub enum CoefficientCombineRule { Multiply, /// The greatest coefficient is chosen. Max, + /// The sum of the two coefficients. + Sum, } impl CoefficientCombineRule { @@ -27,8 +29,10 @@ impl CoefficientCombineRule { match effective_rule { 0 => (coeff1 + coeff2) / 2.0, - 1 => coeff1.min(coeff2), + 1 => coeff1.min(coeff2).abs(), 2 => coeff1 * coeff2, + 4 => (coeff1 + coeff2).clamp(0.0, 1.0), + // 3 is missing as Max is the default one in case of mismatch. _ => coeff1.max(coeff2), } } diff --git a/src/geometry/interaction_groups.rs b/src/geometry/interaction_groups.rs index d8941f75..c7502f9d 100644 --- a/src/geometry/interaction_groups.rs +++ b/src/geometry/interaction_groups.rs @@ -7,13 +7,18 @@ /// - The interaction groups filter. /// /// An interaction is allowed between two filters `a` and `b` when two conditions -/// are met simultaneously: +/// are met simultaneously for [`InteractionTestMode::AND`] or individually for [`InteractionTestMode::OR`]:: /// - The groups membership of `a` has at least one bit set to `1` in common with the groups filter of `b`. /// - The groups membership of `b` has at least one bit set to `1` in common with the groups filter of `a`. /// -/// In other words, interactions are allowed between two filter iff. the following condition is met: +/// In other words, interactions are allowed between two filter iff. the following condition is met +/// for [`InteractionTestMode::AND`]: /// ```ignore -/// (self.memberships & rhs.filter) != 0 && (rhs.memberships & self.filter) != 0 +/// (self.memberships.bits() & rhs.filter.bits()) != 0 && (rhs.memberships.bits() & self.filter.bits()) != 0 +/// ``` +/// or for [`InteractionTestMode::OR`]: +/// ```ignore +/// (self.memberships.bits() & rhs.filter.bits()) != 0 || (rhs.memberships.bits() & self.filter.bits()) != 0 /// ``` #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] @@ -23,25 +28,39 @@ pub struct InteractionGroups { pub memberships: Group, /// Groups filter. pub filter: Group, + /// Interaction test mode + pub test_mode: InteractionTestMode, +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Default)] +/// Specifies which method should be used to test interactions +pub enum InteractionTestMode { + /// Use [`InteractionGroups::test_and`]. + #[default] + And, + /// Use [`InteractionGroups::test_or`]. + Or, } impl InteractionGroups { /// Initializes with the given interaction groups and interaction mask. - pub const fn new(memberships: Group, filter: Group) -> Self { + pub const fn new(memberships: Group, filter: Group, test_mode: InteractionTestMode) -> Self { Self { memberships, filter, + test_mode, } } /// Allow interaction with everything. pub const fn all() -> Self { - Self::new(Group::ALL, Group::ALL) + Self::new(Group::ALL, Group::ALL, InteractionTestMode::AND) } /// Prevent all interactions. pub const fn none() -> Self { - Self::new(Group::NONE, Group::NONE) + Self::new(Group::NONE, Group::NONE, InteractionTestMode::AND) } /// Sets the group this filter is part of. @@ -59,14 +78,33 @@ impl InteractionGroups { /// Check if interactions should be allowed based on the interaction memberships and filter. /// /// An interaction is allowed iff. the memberships of `self` contain at least one bit set to 1 in common - /// with the filter of `rhs`, and vice-versa. + /// with the filter of `rhs`, **and** vice-versa. #[inline] - pub const fn test(self, rhs: Self) -> bool { - // NOTE: since const ops is not stable, we have to convert `Group` into u32 - // to use & operator in const context. + pub const fn test_and(self, rhs: Self) -> bool { (self.memberships.bits() & rhs.filter.bits()) != 0 && (rhs.memberships.bits() & self.filter.bits()) != 0 } + + /// Check if interactions should be allowed based on the interaction memberships and filter. + /// + /// An interaction is allowed iff. the groups of `self` contain at least one bit set to 1 in common + /// with the mask of `rhs`, **or** vice-versa. + #[inline] + pub const fn test_or(self, rhs: Self) -> bool { + (self.memberships.bits() & rhs.filter.bits()) != 0 + || (rhs.memberships.bits() & self.filter.bits()) != 0 + } + + /// Check if interactions should be allowed based on the interaction memberships and filter. + /// + /// See [`InteractionTestMode`] for more info. + #[inline] + pub const fn test(self, rhs: Self) -> bool { + if self.test_mode == InteractionTestMode::And || rhs.test_mode == InteractionTestMode::And { + self.test_and(rhs) + } else { + self.test_or(rhs) + } } impl Default for InteractionGroups { @@ -74,6 +112,7 @@ impl Default for InteractionGroups { Self { memberships: Group::GROUP_1, filter: Group::ALL, + test_mode: InteractionTestMode::AND, } } } diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index ea0c7fbd..d0d5e995 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -11,7 +11,7 @@ pub use self::contact_pair::{ pub use self::interaction_graph::{ ColliderGraphIndex, InteractionGraph, RigidBodyGraphIndex, TemporaryInteractionIndex, }; -pub use self::interaction_groups::{Group, InteractionGroups}; +pub use self::interaction_groups::{Group, InteractionGroups, InteractionTestMode}; pub use self::mesh_converter::{MeshConverter, MeshConverterError}; pub use self::narrow_phase::NarrowPhase;