Skip to main content

rapier3d/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#[cfg_attr(feature = "bytemuck", derive(bytemuck::NoUninit))]
54#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
55#[repr(C)]
56pub struct InteractionGroups {
57    /// Groups memberships.
58    pub memberships: Group,
59    /// Groups filter.
60    pub filter: Group,
61    /// Interaction test mode
62    ///
63    /// In case of different test modes between two [`InteractionGroups`], [`InteractionTestMode::And`] is given priority.
64    pub test_mode: InteractionTestMode,
65}
66
67#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
68#[cfg_attr(feature = "bytemuck", derive(bytemuck::NoUninit))]
69#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Default)]
70#[repr(u32)]
71/// Specifies which method should be used to test interactions.
72///
73/// In case of different test modes between two [`InteractionGroups`], [`InteractionTestMode::And`] is given priority.
74pub enum InteractionTestMode {
75    /// Use [`InteractionGroups::test_and`].
76    #[default]
77    And = 0,
78    /// Use [`InteractionGroups::test_or`], iff. the `rhs` is also [`InteractionTestMode::Or`].
79    ///
80    /// If the `rhs` is not [`InteractionTestMode::Or`], use [`InteractionGroups::test_and`].
81    Or = 1,
82}
83
84impl InteractionGroups {
85    /// Initializes with the given interaction groups and interaction mask.
86    pub const fn new(memberships: Group, filter: Group, test_mode: InteractionTestMode) -> Self {
87        Self {
88            memberships,
89            filter,
90            test_mode,
91        }
92    }
93
94    /// Creates a filter that allows interactions with everything (default behavior).
95    ///
96    /// The collider is in all groups and collides with all groups.
97    pub const fn all() -> Self {
98        Self::new(Group::ALL, Group::ALL, InteractionTestMode::And)
99    }
100
101    /// Creates a filter that prevents all interactions.
102    ///
103    /// The collider won't collide with anything. Useful for temporarily disabled colliders.
104    pub const fn none() -> Self {
105        Self::new(Group::NONE, Group::NONE, InteractionTestMode::And)
106    }
107
108    /// Sets the group this filter is part of.
109    pub const fn with_memberships(mut self, memberships: Group) -> Self {
110        self.memberships = memberships;
111        self
112    }
113
114    /// Sets the interaction mask of this filter.
115    pub const fn with_filter(mut self, filter: Group) -> Self {
116        self.filter = filter;
117        self
118    }
119
120    /// Check if interactions should be allowed based on the interaction memberships and filter.
121    ///
122    /// An interaction is allowed iff. the memberships of `self` contain at least one bit set to 1 in common
123    /// with the filter of `rhs`, **and** vice-versa.
124    #[inline]
125    pub const fn test_and(self, rhs: Self) -> bool {
126        // NOTE: since const ops is not stable, we have to convert `Group` into u32
127        // to use & operator in const context.
128        (self.memberships.bits() & rhs.filter.bits()) != 0
129            && (rhs.memberships.bits() & self.filter.bits()) != 0
130    }
131
132    /// Check if interactions should be allowed based on the interaction memberships and filter.
133    ///
134    /// An interaction is allowed iff. the groups of `self` contain at least one bit set to 1 in common
135    /// with the mask of `rhs`, **or** vice-versa.
136    #[inline]
137    pub const fn test_or(self, rhs: Self) -> bool {
138        // NOTE: since const ops is not stable, we have to convert `Group` into u32
139        // to use & operator in const context.
140        (self.memberships.bits() & rhs.filter.bits()) != 0
141            || (rhs.memberships.bits() & self.filter.bits()) != 0
142    }
143
144    /// Check if interactions should be allowed based on the interaction memberships and filter.
145    ///
146    /// See [`InteractionTestMode`] for more info.
147    #[inline]
148    pub const fn test(self, rhs: Self) -> bool {
149        match (self.test_mode, rhs.test_mode) {
150            (InteractionTestMode::And, _) => self.test_and(rhs),
151            (InteractionTestMode::Or, InteractionTestMode::And) => self.test_and(rhs),
152            (InteractionTestMode::Or, InteractionTestMode::Or) => self.test_or(rhs),
153        }
154    }
155}
156
157impl Default for InteractionGroups {
158    fn default() -> Self {
159        Self {
160            memberships: Group::GROUP_1,
161            filter: Group::ALL,
162            test_mode: InteractionTestMode::And,
163        }
164    }
165}
166
167bitflags::bitflags! {
168    /// A bit mask identifying groups for interaction.
169    #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
170    #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
171    pub struct Group: u32 {
172        /// The group n°1.
173        const GROUP_1 = 1 << 0;
174        /// The group n°2.
175        const GROUP_2 = 1 << 1;
176        /// The group n°3.
177        const GROUP_3 = 1 << 2;
178        /// The group n°4.
179        const GROUP_4 = 1 << 3;
180        /// The group n°5.
181        const GROUP_5 = 1 << 4;
182        /// The group n°6.
183        const GROUP_6 = 1 << 5;
184        /// The group n°7.
185        const GROUP_7 = 1 << 6;
186        /// The group n°8.
187        const GROUP_8 = 1 << 7;
188        /// The group n°9.
189        const GROUP_9 = 1 << 8;
190        /// The group n°10.
191        const GROUP_10 = 1 << 9;
192        /// The group n°11.
193        const GROUP_11 = 1 << 10;
194        /// The group n°12.
195        const GROUP_12 = 1 << 11;
196        /// The group n°13.
197        const GROUP_13 = 1 << 12;
198        /// The group n°14.
199        const GROUP_14 = 1 << 13;
200        /// The group n°15.
201        const GROUP_15 = 1 << 14;
202        /// The group n°16.
203        const GROUP_16 = 1 << 15;
204        /// The group n°17.
205        const GROUP_17 = 1 << 16;
206        /// The group n°18.
207        const GROUP_18 = 1 << 17;
208        /// The group n°19.
209        const GROUP_19 = 1 << 18;
210        /// The group n°20.
211        const GROUP_20 = 1 << 19;
212        /// The group n°21.
213        const GROUP_21 = 1 << 20;
214        /// The group n°22.
215        const GROUP_22 = 1 << 21;
216        /// The group n°23.
217        const GROUP_23 = 1 << 22;
218        /// The group n°24.
219        const GROUP_24 = 1 << 23;
220        /// The group n°25.
221        const GROUP_25 = 1 << 24;
222        /// The group n°26.
223        const GROUP_26 = 1 << 25;
224        /// The group n°27.
225        const GROUP_27 = 1 << 26;
226        /// The group n°28.
227        const GROUP_28 = 1 << 27;
228        /// The group n°29.
229        const GROUP_29 = 1 << 28;
230        /// The group n°30.
231        const GROUP_30 = 1 << 29;
232        /// The group n°31.
233        const GROUP_31 = 1 << 30;
234        /// The group n°32.
235        const GROUP_32 = 1 << 31;
236
237        /// All of the groups.
238        const ALL = u32::MAX;
239        /// None of the groups.
240        const NONE = 0;
241    }
242}
243
244impl From<u32> for Group {
245    #[inline]
246    fn from(val: u32) -> Self {
247        Self::from_bits_retain(val)
248    }
249}
250
251impl From<Group> for u32 {
252    #[inline]
253    fn from(val: Group) -> Self {
254        val.bits()
255    }
256}
257
258// `Group` is generated by `bitflags!` as `#[repr(transparent)]` around `u32`, so it is
259// trivially safe to treat as `NoUninit`. The `bitflags!` macro doesn't forward derives
260// to bytemuck, so we add a manual unsafe impl.
261#[cfg(feature = "bytemuck")]
262unsafe impl bytemuck::NoUninit for Group {}