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#[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 {}