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}