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/// Two colliders interact only if:
10/// 1. Collider A's memberships overlap with Collider B's filter, AND
11/// 2. Collider B's memberships overlap with Collider A's filter
12///
13/// # Common use cases
14///
15/// - **Player vs. Enemy bullets**: Players in group 1, enemies in group 2. Player bullets
16/// only hit group 2, enemy bullets only hit group 1.
17/// - **Trigger zones**: Sensors that only detect specific object types.
18///
19/// # Example
20///
21/// ```
22/// # use rapier3d::geometry::{InteractionGroups, Group};
23/// // Player collider: in group 1, collides with groups 2 and 3
24/// let player_groups = InteractionGroups::new(
25/// Group::GROUP_1, // I am in group 1
26/// Group::GROUP_2 | Group::GROUP_3 // I collide with groups 2 and 3
27/// );
28///
29/// // Enemy collider: in group 2, collides with group 1
30/// let enemy_groups = InteractionGroups::new(
31/// Group::GROUP_2, // I am in group 2
32/// Group::GROUP_1 // I collide with group 1
33/// );
34///
35/// // These will collide because:
36/// // - Player's membership (GROUP_1) is in enemy's filter (GROUP_1) ✓
37/// // - Enemy's membership (GROUP_2) is in player's filter (GROUP_2) ✓
38/// assert!(player_groups.test(enemy_groups));
39/// ```
40#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
41#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
42#[repr(C)]
43pub struct InteractionGroups {
44 /// Groups memberships.
45 pub memberships: Group,
46 /// Groups filter.
47 pub filter: Group,
48}
49
50impl InteractionGroups {
51 /// Initializes with the given interaction groups and interaction mask.
52 pub const fn new(memberships: Group, filter: Group) -> Self {
53 Self {
54 memberships,
55 filter,
56 }
57 }
58
59 /// Creates a filter that allows interactions with everything (default behavior).
60 ///
61 /// The collider is in all groups and collides with all groups.
62 pub const fn all() -> Self {
63 Self::new(Group::ALL, Group::ALL)
64 }
65
66 /// Creates a filter that prevents all interactions.
67 ///
68 /// The collider won't collide with anything. Useful for temporarily disabled colliders.
69 pub const fn none() -> Self {
70 Self::new(Group::NONE, Group::NONE)
71 }
72
73 /// Sets the group this filter is part of.
74 pub const fn with_memberships(mut self, memberships: Group) -> Self {
75 self.memberships = memberships;
76 self
77 }
78
79 /// Sets the interaction mask of this filter.
80 pub const fn with_filter(mut self, filter: Group) -> Self {
81 self.filter = filter;
82 self
83 }
84
85 /// Check if interactions should be allowed based on the interaction memberships and filter.
86 ///
87 /// An interaction is allowed iff. the memberships of `self` contain at least one bit set to 1 in common
88 /// with the filter of `rhs`, and vice-versa.
89 #[inline]
90 pub const fn test(self, rhs: Self) -> bool {
91 // NOTE: since const ops is not stable, we have to convert `Group` into u32
92 // to use & operator in const context.
93 (self.memberships.bits() & rhs.filter.bits()) != 0
94 && (rhs.memberships.bits() & self.filter.bits()) != 0
95 }
96}
97
98impl Default for InteractionGroups {
99 fn default() -> Self {
100 Self {
101 memberships: Group::GROUP_1,
102 filter: Group::ALL,
103 }
104 }
105}
106
107bitflags::bitflags! {
108 /// A bit mask identifying groups for interaction.
109 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
110 #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
111 pub struct Group: u32 {
112 /// The group n°1.
113 const GROUP_1 = 1 << 0;
114 /// The group n°2.
115 const GROUP_2 = 1 << 1;
116 /// The group n°3.
117 const GROUP_3 = 1 << 2;
118 /// The group n°4.
119 const GROUP_4 = 1 << 3;
120 /// The group n°5.
121 const GROUP_5 = 1 << 4;
122 /// The group n°6.
123 const GROUP_6 = 1 << 5;
124 /// The group n°7.
125 const GROUP_7 = 1 << 6;
126 /// The group n°8.
127 const GROUP_8 = 1 << 7;
128 /// The group n°9.
129 const GROUP_9 = 1 << 8;
130 /// The group n°10.
131 const GROUP_10 = 1 << 9;
132 /// The group n°11.
133 const GROUP_11 = 1 << 10;
134 /// The group n°12.
135 const GROUP_12 = 1 << 11;
136 /// The group n°13.
137 const GROUP_13 = 1 << 12;
138 /// The group n°14.
139 const GROUP_14 = 1 << 13;
140 /// The group n°15.
141 const GROUP_15 = 1 << 14;
142 /// The group n°16.
143 const GROUP_16 = 1 << 15;
144 /// The group n°17.
145 const GROUP_17 = 1 << 16;
146 /// The group n°18.
147 const GROUP_18 = 1 << 17;
148 /// The group n°19.
149 const GROUP_19 = 1 << 18;
150 /// The group n°20.
151 const GROUP_20 = 1 << 19;
152 /// The group n°21.
153 const GROUP_21 = 1 << 20;
154 /// The group n°22.
155 const GROUP_22 = 1 << 21;
156 /// The group n°23.
157 const GROUP_23 = 1 << 22;
158 /// The group n°24.
159 const GROUP_24 = 1 << 23;
160 /// The group n°25.
161 const GROUP_25 = 1 << 24;
162 /// The group n°26.
163 const GROUP_26 = 1 << 25;
164 /// The group n°27.
165 const GROUP_27 = 1 << 26;
166 /// The group n°28.
167 const GROUP_28 = 1 << 27;
168 /// The group n°29.
169 const GROUP_29 = 1 << 28;
170 /// The group n°30.
171 const GROUP_30 = 1 << 29;
172 /// The group n°31.
173 const GROUP_31 = 1 << 30;
174 /// The group n°32.
175 const GROUP_32 = 1 << 31;
176
177 /// All of the groups.
178 const ALL = u32::MAX;
179 /// None of the groups.
180 const NONE = 0;
181 }
182}
183
184impl From<u32> for Group {
185 #[inline]
186 fn from(val: u32) -> Self {
187 Self::from_bits_retain(val)
188 }
189}
190
191impl From<Group> for u32 {
192 #[inline]
193 fn from(val: Group) -> Self {
194 val.bits()
195 }
196}