ncollide3d/pipeline/object/
collision_groups.rs

1use crate::pipeline::broad_phase::BroadPhasePairFilter;
2use crate::pipeline::object::{CollisionObjectRef, CollisionObjectSet};
3use na::RealField;
4
5const SELF_COLLISION: u32 = 1 << 31;
6const ALL_GROUPS: u32 = (1 << 30) - 1;
7const NO_GROUP: u32 = 0;
8
9/// Groups of collision used to filter which object interact with which other one.
10///
11/// There are at most 30 groups indexed from 0 to 29 (included). This identifies collidable
12/// entities by combining three attributes:
13///    * A set of group this structure is member of.
14///    * A collision group whitelist.
15///    * A collision group blacklist.
16///
17/// For two entities to interact, they must be member of at least one group part of each-other's
18/// whitelists, and must not be part of any blacklisted group. The blacklist always has priority on
19/// the whitelist.
20///
21/// ### Example
22/// For example if the object A is such that:
23///    * It is part of the groups 1, 3, and 6.
24///    * It whitelists the groups 6 and 7.
25///    * It blacklists the group 1.
26///
27/// Let there be an object B such that:
28///    * It is part of the groups 1, 3, and 7.
29///    * It whitelists the groups 3 and 7.
30///    * It does not blacklist anything.
31///
32/// and an object C such that:
33///    * It is part of the groups 6 and 9.
34///    * It whitelists the groups 3 and 7.
35///    * It does not blacklist anything.
36///
37/// Then we have:
38///    * A and C can interact because A whitelists the group 6 (which C is part of), and,
39///    reciprocally, C whitelists the group 3 (which A is part of).
40///    * A and B will **not** interact because B is part of the group 1 which is blacklisted by A.
41///    * Finally, B and C will **not** interact either because, even if C whitelists the group 3
42///    (which B is part of), B does not whitelists the groups 6 nor 9 (which B is part of).
43#[derive(Clone, Debug, Copy)]
44pub struct CollisionGroups {
45    membership: u32,
46    whitelist: u32,
47    blacklist: u32,
48}
49
50impl CollisionGroups {
51    /// Creates a new `CollisionGroups` that enables interactions with everything except
52    /// self-interaction.
53    #[inline]
54    pub fn new() -> CollisionGroups {
55        CollisionGroups {
56            membership: ALL_GROUPS,
57            whitelist: ALL_GROUPS,
58            blacklist: NO_GROUP,
59        }
60    }
61
62    /// Creates a new `CollisionGroups` that disables interactions with everything.
63    #[inline]
64    pub fn empty() -> CollisionGroups {
65        CollisionGroups {
66            membership: NO_GROUP,
67            whitelist: NO_GROUP,
68            blacklist: NO_GROUP,
69        }
70    }
71
72    /// Returns a copy of this object, updated with a new set of membership groups.
73    ///
74    /// # Examples
75    ///
76    /// ```.ignore
77    /// const GROUP_A: usize = 0;
78    /// const GROUP_B: usize = 29;
79    /// let groups = CollisionGroups::new().with_membership(&[GROUP_A, GROUP_B]);
80    /// assert!(groups.is_member_of(GROUP_A));
81    /// assert!(groups.is_member_of(GROUP_B));
82    /// ```
83    #[inline]
84    pub fn with_membership(mut self, groups: &[usize]) -> CollisionGroups {
85        CollisionGroups::set_mask(&mut self.membership, groups);
86        self
87    }
88
89    /// Returns a copy of this object, updated with a new set of whitelisted groups.
90    ///
91    /// # Examples
92    ///
93    /// ```.ignore
94    /// const GROUP_A: usize = 0;
95    /// const GROUP_B: usize = 29;
96    /// let group_a = CollisionGroups::new().with_whitelist(&[GROUP_B]);
97    /// assert!(!group_a.is_group_whitelisted(GROUP_A));
98    /// assert!(group_a.is_group_whitelisted(GROUP_B));
99    /// ```
100    #[inline]
101    pub fn with_whitelist(mut self, groups: &[usize]) -> CollisionGroups {
102        CollisionGroups::set_mask(&mut self.whitelist, groups);
103        self
104    }
105
106    /// Returns a copy of this object, updated with a new set of blacklisted groups.
107    ///
108    /// # Examples
109    ///
110    /// ```.ignore
111    /// const GROUP_A: usize = 0;
112    /// const GROUP_B: usize = 29;
113    /// let group_a = CollisionGroups::new().with_blacklist(&[GROUP_B]);
114    /// assert!(!group_a.is_group_blacklisted(GROUP_A));
115    /// assert!(group_a.is_group_blacklisted(GROUP_B));
116    /// ```
117    #[inline]
118    pub fn with_blacklist(mut self, groups: &[usize]) -> CollisionGroups {
119        CollisionGroups::set_mask(&mut self.blacklist, groups);
120        self
121    }
122
123    /// The maximum allowed group identifier.
124    #[inline]
125    pub fn max_group_id() -> usize {
126        29
127    }
128
129    #[inline]
130    fn modify_mask(mask: &mut u32, group_id: usize, add: bool) {
131        assert!(
132            group_id < 30,
133            "There are at most 30 groups indexed from 0 to 29 (included)."
134        );
135
136        if add {
137            *mask = *mask | (1 << group_id)
138        } else {
139            *mask = *mask & !(1 << group_id)
140        }
141    }
142
143    #[inline]
144    fn set_mask(mask: &mut u32, groups: &[usize]) {
145        *mask = 0;
146        for g in groups.iter() {
147            CollisionGroups::modify_mask(mask, *g, true);
148        }
149    }
150
151    #[inline]
152    fn add_mask(cur_mask: u32, new_mask: u32) -> u32 {
153        assert!(
154            new_mask <= ALL_GROUPS,
155            "There are at most 30 groups indexed from 0 to 29 (included)."
156        );
157
158        cur_mask | new_mask
159    }
160
161    #[inline]
162    fn remove_mask(cur_mask: u32, new_mask: u32) -> u32 {
163        assert!(
164            new_mask <= ALL_GROUPS,
165            "There are at most 30 groups indexed from 0 to 29 (included)."
166        );
167
168        cur_mask & !new_mask
169    }
170
171    #[inline]
172    /// adds this entity to the given group by a mask of bits where each bit index represent a group
173    pub fn add_membership_by_mask(mut self, group_mask: u32) -> CollisionGroups {
174        self.membership = Self::add_mask(self.membership, group_mask);
175        self
176    }
177
178    #[inline]
179    /// removes this entity from the given group by a mask of bits where each bit index represent a group
180    pub fn remove_membership_by_mask(mut self, group_mask: u32) -> CollisionGroups {
181        self.membership = Self::remove_mask(self.membership, group_mask);
182        self
183    }
184
185    #[inline]
186    /// Replaces the membership with a mask of bits where each bit index represent a group
187    pub fn with_membership_by_mask(mut self, group_mask: u32) -> CollisionGroups {
188        assert!(
189            group_mask <= ALL_GROUPS,
190            "There are at most 30 groups indexed from 0 to 29 (included)."
191        );
192
193        self.membership = group_mask;
194        self
195    }
196
197    #[inline]
198    /// adds this entity to this entity whitelist by a mask of bits where each bit index represent a group
199    pub fn add_whitelist_by_mask(mut self, group_mask: u32) -> CollisionGroups {
200        self.whitelist = Self::add_mask(self.whitelist, group_mask);
201        self
202    }
203
204    #[inline]
205    /// remove this entity from this entity whitelist by a mask of bits where each bit index represent a group
206    pub fn remove_whitelist_by_mask(mut self, group_mask: u32) -> CollisionGroups {
207        self.whitelist = Self::remove_mask(self.whitelist, group_mask);
208        self
209    }
210
211    #[inline]
212    /// Replaces the whitelist with a mask of bits where each bit index represent a group
213    pub fn with_whitelist_by_mask(mut self, group_mask: u32) -> CollisionGroups {
214        assert!(
215            group_mask <= ALL_GROUPS,
216            "There are at most 30 groups indexed from 0 to 29 (included)."
217        );
218
219        self.whitelist = group_mask;
220        self
221    }
222
223    #[inline]
224    /// adds this entity to this entity blacklist by a mask of bits where each bit index represent a group
225    pub fn add_blacklist_by_mask(mut self, group_mask: u32) -> CollisionGroups {
226        self.blacklist = Self::add_mask(self.blacklist, group_mask);
227        self
228    }
229
230    #[inline]
231    /// remove this entity from this entity blacklist by a mask of bits where each bit index represent a group
232    pub fn remove_blacklist_by_mask(mut self, group_mask: u32) -> CollisionGroups {
233        self.blacklist = Self::remove_mask(self.blacklist, group_mask);
234        self
235    }
236
237    #[inline]
238    /// Replaces the blacklist with a mask of bits where each bit index represent a group
239    pub fn with_blacklist_by_mask(mut self, group_mask: u32) -> CollisionGroups {
240        assert!(
241            group_mask <= ALL_GROUPS,
242            "There are at most 30 groups indexed from 0 to 29 (included)."
243        );
244
245        self.blacklist = group_mask;
246        self
247    }
248
249    /// Adds or removes this entity from the given group.
250    #[inline]
251    pub fn modify_membership(&mut self, group_id: usize, add: bool) {
252        CollisionGroups::modify_mask(&mut self.membership, group_id, add);
253    }
254
255    /// Adds or removes the given group from this entity whitelist.
256    #[inline]
257    pub fn modify_whitelist(&mut self, group_id: usize, add: bool) {
258        CollisionGroups::modify_mask(&mut self.whitelist, group_id, add);
259    }
260
261    /// Adds or removes this entity from the given group.
262    #[inline]
263    pub fn modify_blacklist(&mut self, group_id: usize, add: bool) {
264        CollisionGroups::modify_mask(&mut self.blacklist, group_id, add);
265    }
266
267    /// Make this object member of the given groups only.
268    #[inline]
269    pub fn set_membership(&mut self, groups: &[usize]) {
270        CollisionGroups::set_mask(&mut self.membership, groups);
271    }
272
273    /// Whitelists the given groups only (others will be un-whitelisted).
274    #[inline]
275    pub fn set_whitelist(&mut self, groups: &[usize]) {
276        CollisionGroups::set_mask(&mut self.whitelist, groups);
277    }
278
279    /// Blacklists the given groups only (others will be un-blacklisted).
280    #[inline]
281    pub fn set_blacklist(&mut self, groups: &[usize]) {
282        CollisionGroups::set_mask(&mut self.blacklist, groups);
283    }
284
285    /// Copies the membership of another collision groups.
286    #[inline]
287    pub fn copy_membership(&mut self, other: &CollisionGroups) {
288        self.membership = other.membership
289    }
290
291    /// Copies the whitelist of another collision groups.
292    #[inline]
293    pub fn copy_whitelist(&mut self, other: &CollisionGroups) {
294        self.whitelist = other.whitelist
295    }
296
297    /// Copies the blacklist of another collision groups.
298    #[inline]
299    pub fn copy_blacklist(&mut self, other: &CollisionGroups) {
300        self.blacklist = other.blacklist
301    }
302
303    /// Allows the object to interact with itself.
304    #[inline]
305    pub fn enable_self_interaction(&mut self) {
306        self.whitelist = self.whitelist | SELF_COLLISION;
307    }
308
309    /// Prevents the object from interacting with itself.
310    #[inline]
311    pub fn disable_self_interaction(&mut self) {
312        self.whitelist = self.whitelist & !SELF_COLLISION;
313    }
314
315    #[inline]
316    fn is_inside_mask(mask: u32, group_id: usize) -> bool {
317        assert!(
318            group_id < 30,
319            "There are at most 30 groups indexed from 0 to 29 (included)."
320        );
321        mask & (1 << group_id) != 0
322    }
323
324    /// Tests if this entity is part of the given group.
325    #[inline]
326    pub fn is_member_of(&self, group_id: usize) -> bool {
327        CollisionGroups::is_inside_mask(self.membership, group_id)
328    }
329
330    /// Tests if the given group is whitelisted.
331    #[inline]
332    pub fn is_group_whitelisted(&self, group_id: usize) -> bool {
333        CollisionGroups::is_inside_mask(self.whitelist, group_id)
334    }
335
336    /// Tests if the given group is blacklisted.
337    #[inline]
338    pub fn is_group_blacklisted(&self, group_id: usize) -> bool {
339        CollisionGroups::is_inside_mask(self.blacklist, group_id)
340    }
341
342    /// Tests whether interactions with a given group is possible.
343    ///
344    /// Collision is possible if `group_id` is whitelisted but not blacklisted.
345    #[inline]
346    pub fn can_interact_with(&self, group_id: usize) -> bool {
347        !CollisionGroups::is_inside_mask(self.blacklist, group_id)
348            && CollisionGroups::is_inside_mask(self.whitelist, group_id)
349    }
350
351    /// Tests whether two collision groups have at least one group in common.
352    #[inline]
353    pub fn can_interact_with_groups(&self, other: &CollisionGroups) -> bool {
354        // FIXME: is there a more bitwise-y way of doing this?
355        self.membership & other.blacklist == 0
356            && other.membership & self.blacklist == 0
357            && self.membership & other.whitelist != 0
358            && other.membership & self.whitelist != 0
359    }
360
361    /// Tests whether self-interaction is enabled.
362    #[inline]
363    pub fn can_interact_with_self(&self) -> bool {
364        self.whitelist & SELF_COLLISION != 0
365    }
366}
367
368impl Default for CollisionGroups {
369    #[inline]
370    fn default() -> Self {
371        CollisionGroups::new()
372    }
373}
374
375/// A collision filter based collision groups.
376pub struct CollisionGroupsPairFilter;
377
378impl CollisionGroupsPairFilter {
379    /// Creates a new collision filter based collision groups.
380    #[inline]
381    pub fn new() -> CollisionGroupsPairFilter {
382        CollisionGroupsPairFilter
383    }
384}
385
386impl<N: RealField + Copy, Set: CollisionObjectSet<N>> BroadPhasePairFilter<N, Set>
387    for CollisionGroupsPairFilter
388{
389    fn is_pair_valid(
390        &self,
391        h1: Set::CollisionObjectHandle,
392        h2: Set::CollisionObjectHandle,
393        s: &Set,
394    ) -> bool {
395        let co1 = try_ret!(s.collision_object(h1), false);
396        let co2 = try_ret!(s.collision_object(h2), false);
397
398        if h1 == h2 {
399            co1.collision_groups().can_interact_with_self()
400        } else {
401            co1.collision_groups()
402                .can_interact_with_groups(co2.collision_groups())
403        }
404    }
405}