rapier2d/geometry/
interaction_groups.rs

1#![allow(clippy::bad_bit_mask)] // Clippy will complain about the bitmasks due to Group::NONE being 0.
2
3/// Collision filtering system that controls which colliders can interact with each other.
4///
5/// Think of this as "collision layers" in game engines. Each collider has:
6/// - **Memberships**: What groups does this collider belong to? (up to 32 groups)
7/// - **Filter**: What groups can this collider interact with?
8///
9/// An interaction is allowed between two colliders `a` and `b` when two conditions
10/// are met simultaneously for [`InteractionTestMode::And`] or individually for [`InteractionTestMode::Or`]::
11/// - The groups membership of `a` has at least one bit set to `1` in common with the groups filter of `b`.
12/// - The groups membership of `b` has at least one bit set to `1` in common with the groups filter of `a`.
13///
14/// In other words, interactions are allowed between two colliders iff. the following condition is met
15/// for [`InteractionTestMode::And`]:
16/// ```ignore
17/// (self.memberships.bits() & rhs.filter.bits()) != 0 && (rhs.memberships.bits() & self.filter.bits()) != 0
18/// ```
19/// or for [`InteractionTestMode::Or`]:
20/// ```ignore
21/// (self.memberships.bits() & rhs.filter.bits()) != 0 || (rhs.memberships.bits() & self.filter.bits()) != 0
22/// ```
23/// # Common use cases
24///
25/// - **Player vs. Enemy bullets**: Players in group 1, enemies in group 2. Player bullets
26///   only hit group 2, enemy bullets only hit group 1.
27/// - **Trigger zones**: Sensors that only detect specific object types.
28///
29/// # Example
30///
31/// ```ignore
32/// # use rapier3d::geometry::{InteractionGroups, Group};
33/// // Player collider: in group 1, collides with groups 2 and 3
34/// let player_groups = InteractionGroups::new(
35///     Group::GROUP_1,                    // I am in group 1
36///     Group::GROUP_2, | Group::GROUP_3,  // I collide with groups 2 and 3
37///     InteractionTestMode::And
38/// );
39///
40/// // Enemy collider: in group 2, collides with group 1
41/// let enemy_groups = InteractionGroups::new(
42///     Group::GROUP_2,  // I am in group 2
43///     Group::GROUP_1,  // I collide with group 1
44///     InteractionTestMode::And
45/// );
46///
47/// // These will collide because:
48/// // - Player's membership (GROUP_1) is in enemy's filter (GROUP_1) ✓
49/// // - Enemy's membership (GROUP_2) is in player's filter (GROUP_2) ✓
50/// assert!(player_groups.test(enemy_groups));
51/// ```
52#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
53#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
54#[repr(C)]
55pub struct InteractionGroups {
56    /// Groups memberships.
57    pub memberships: Group,
58    /// Groups filter.
59    pub filter: Group,
60    /// Interaction test mode
61    ///
62    /// In case of different test modes between two [`InteractionGroups`], [`InteractionTestMode::And`] is given priority.
63    pub test_mode: InteractionTestMode,
64}
65
66#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
67#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Default)]
68/// Specifies which method should be used to test interactions.
69///
70/// In case of different test modes between two [`InteractionGroups`], [`InteractionTestMode::And`] is given priority.
71pub enum InteractionTestMode {
72    /// Use [`InteractionGroups::test_and`].
73    #[default]
74    And,
75    /// Use [`InteractionGroups::test_or`], iff. the `rhs` is also [`InteractionTestMode::Or`].
76    ///
77    /// If the `rhs` is not [`InteractionTestMode::Or`], use [`InteractionGroups::test_and`].
78    Or,
79}
80
81impl InteractionGroups {
82    /// Initializes with the given interaction groups and interaction mask.
83    pub const fn new(memberships: Group, filter: Group, test_mode: InteractionTestMode) -> Self {
84        Self {
85            memberships,
86            filter,
87            test_mode,
88        }
89    }
90
91    /// Creates a filter that allows interactions with everything (default behavior).
92    ///
93    /// The collider is in all groups and collides with all groups.
94    pub const fn all() -> Self {
95        Self::new(Group::ALL, Group::ALL, InteractionTestMode::And)
96    }
97
98    /// Creates a filter that prevents all interactions.
99    ///
100    /// The collider won't collide with anything. Useful for temporarily disabled colliders.
101    pub const fn none() -> Self {
102        Self::new(Group::NONE, Group::NONE, InteractionTestMode::And)
103    }
104
105    /// Sets the group this filter is part of.
106    pub const fn with_memberships(mut self, memberships: Group) -> Self {
107        self.memberships = memberships;
108        self
109    }
110
111    /// Sets the interaction mask of this filter.
112    pub const fn with_filter(mut self, filter: Group) -> Self {
113        self.filter = filter;
114        self
115    }
116
117    /// Check if interactions should be allowed based on the interaction memberships and filter.
118    ///
119    /// An interaction is allowed iff. the memberships of `self` contain at least one bit set to 1 in common
120    /// with the filter of `rhs`, **and** vice-versa.
121    #[inline]
122    pub const fn test_and(self, rhs: Self) -> bool {
123        // NOTE: since const ops is not stable, we have to convert `Group` into u32
124        // to use & operator in const context.
125        (self.memberships.bits() & rhs.filter.bits()) != 0
126            && (rhs.memberships.bits() & self.filter.bits()) != 0
127    }
128
129    /// Check if interactions should be allowed based on the interaction memberships and filter.
130    ///
131    /// An interaction is allowed iff. the groups of `self` contain at least one bit set to 1 in common
132    /// with the mask of `rhs`, **or** vice-versa.
133    #[inline]
134    pub const fn test_or(self, rhs: Self) -> bool {
135        // NOTE: since const ops is not stable, we have to convert `Group` into u32
136        // to use & operator in const context.
137        (self.memberships.bits() & rhs.filter.bits()) != 0
138            || (rhs.memberships.bits() & self.filter.bits()) != 0
139    }
140
141    /// Check if interactions should be allowed based on the interaction memberships and filter.
142    ///
143    /// See [`InteractionTestMode`] for more info.
144    #[inline]
145    pub const fn test(self, rhs: Self) -> bool {
146        match (self.test_mode, rhs.test_mode) {
147            (InteractionTestMode::And, _) => self.test_and(rhs),
148            (InteractionTestMode::Or, InteractionTestMode::And) => self.test_and(rhs),
149            (InteractionTestMode::Or, InteractionTestMode::Or) => self.test_or(rhs),
150        }
151    }
152}
153
154impl Default for InteractionGroups {
155    fn default() -> Self {
156        Self {
157            memberships: Group::GROUP_1,
158            filter: Group::ALL,
159            test_mode: InteractionTestMode::And,
160        }
161    }
162}
163
164bitflags::bitflags! {
165    /// A bit mask identifying groups for interaction.
166    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
167    #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
168    pub struct Group: u32 {
169        /// The group n°1.
170        const GROUP_1 = 1 << 0;
171        /// The group n°2.
172        const GROUP_2 = 1 << 1;
173        /// The group n°3.
174        const GROUP_3 = 1 << 2;
175        /// The group n°4.
176        const GROUP_4 = 1 << 3;
177        /// The group n°5.
178        const GROUP_5 = 1 << 4;
179        /// The group n°6.
180        const GROUP_6 = 1 << 5;
181        /// The group n°7.
182        const GROUP_7 = 1 << 6;
183        /// The group n°8.
184        const GROUP_8 = 1 << 7;
185        /// The group n°9.
186        const GROUP_9 = 1 << 8;
187        /// The group n°10.
188        const GROUP_10 = 1 << 9;
189        /// The group n°11.
190        const GROUP_11 = 1 << 10;
191        /// The group n°12.
192        const GROUP_12 = 1 << 11;
193        /// The group n°13.
194        const GROUP_13 = 1 << 12;
195        /// The group n°14.
196        const GROUP_14 = 1 << 13;
197        /// The group n°15.
198        const GROUP_15 = 1 << 14;
199        /// The group n°16.
200        const GROUP_16 = 1 << 15;
201        /// The group n°17.
202        const GROUP_17 = 1 << 16;
203        /// The group n°18.
204        const GROUP_18 = 1 << 17;
205        /// The group n°19.
206        const GROUP_19 = 1 << 18;
207        /// The group n°20.
208        const GROUP_20 = 1 << 19;
209        /// The group n°21.
210        const GROUP_21 = 1 << 20;
211        /// The group n°22.
212        const GROUP_22 = 1 << 21;
213        /// The group n°23.
214        const GROUP_23 = 1 << 22;
215        /// The group n°24.
216        const GROUP_24 = 1 << 23;
217        /// The group n°25.
218        const GROUP_25 = 1 << 24;
219        /// The group n°26.
220        const GROUP_26 = 1 << 25;
221        /// The group n°27.
222        const GROUP_27 = 1 << 26;
223        /// The group n°28.
224        const GROUP_28 = 1 << 27;
225        /// The group n°29.
226        const GROUP_29 = 1 << 28;
227        /// The group n°30.
228        const GROUP_30 = 1 << 29;
229        /// The group n°31.
230        const GROUP_31 = 1 << 30;
231        /// The group n°32.
232        const GROUP_32 = 1 << 31;
233
234        /// All of the groups.
235        const ALL = u32::MAX;
236        /// None of the groups.
237        const NONE = 0;
238    }
239}
240
241impl From<u32> for Group {
242    #[inline]
243    fn from(val: u32) -> Self {
244        Self::from_bits_retain(val)
245    }
246}
247
248impl From<Group> for u32 {
249    #[inline]
250    fn from(val: Group) -> Self {
251        val.bits()
252    }
253}