Skip to main content

bevy_rapier3d/control/
controller_filter.rs

1use bevy::ecs::entity::EntityHashSet;
2use bevy::prelude::{Component, Entity, Reflect, ReflectComponent, ReflectDefault};
3use rapier::dynamics::RigidBodyHandle;
4use rapier::geometry::Collider as RapierCollider;
5use std::fmt;
6use std::sync::Arc;
7
8use crate::plugin::context::{RapierContextColliders, RapierRigidBodySet};
9
10/// Marker component excluding a collider from the environment seen by every controller (the
11/// [`KinematicCharacterController`]s, and the wheels of the `RayCastVehicleController`s in 3D).
12///
13/// Insert it on a collider entity to ignore that collider, or on a rigid-body entity to ignore
14/// all its colliders. Unlike [`ControllerFilterPredicate`], it can be inserted and removed by
15/// systems reading any ECS data (e.g. to let characters walk through the doors they own).
16///
17/// [`KinematicCharacterController`]: crate::control::KinematicCharacterController
18#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Component, Reflect)]
19#[reflect(Component, Default, PartialEq)]
20pub struct ControllerIgnored;
21
22/// A user-defined predicate deciding which colliders are seen by a controller.
23///
24/// The predicate is given the entity and Rapier collider of each candidate collider and must
25/// return `false` to exclude it. Since components cannot hold borrowed closures, the closure is
26/// reference-counted, and it can't read ECS data: see [`ControllerIgnored`] for exclusions
27/// computed by systems.
28#[derive(Clone)]
29#[allow(clippy::type_complexity)]
30pub struct ControllerFilterPredicate(
31    pub Arc<dyn Fn(Entity, &RapierCollider) -> bool + Send + Sync>,
32);
33
34impl ControllerFilterPredicate {
35    /// Wraps the given closure into a predicate usable by the controllers.
36    pub fn new(
37        predicate: impl Fn(Entity, &RapierCollider) -> bool + Send + Sync + 'static,
38    ) -> Self {
39        Self(Arc::new(predicate))
40    }
41
42    /// Evaluates the predicate for the given collider attached to `entity`.
43    pub fn test(&self, entity: Entity, collider: &RapierCollider) -> bool {
44        (self.0)(entity, collider)
45    }
46}
47
48impl fmt::Debug for ControllerFilterPredicate {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        f.write_str("ControllerFilterPredicate(..)")
51    }
52}
53
54/// Entity-based collider exclusions of a controller, resolved against the Rapier sets.
55pub(crate) struct ControllerExclusions<'a> {
56    colliders: &'a EntityHashSet,
57    /// The entities with a [`ControllerIgnored`] component.
58    ignored: &'a EntityHashSet,
59    rigid_bodies: Vec<RigidBodyHandle>,
60    predicate: Option<&'a ControllerFilterPredicate>,
61}
62
63impl<'a> ControllerExclusions<'a> {
64    /// Resolves the excluded rigid-body entities (and the rigid-bodies among the `ignored`
65    /// entities) into handles; entities without a rigid-body are ignored.
66    pub fn new(
67        colliders: &'a EntityHashSet,
68        rigid_bodies: &EntityHashSet,
69        ignored: &'a EntityHashSet,
70        predicate: Option<&'a ControllerFilterPredicate>,
71        rigidbody_set: &RapierRigidBodySet,
72    ) -> Self {
73        let rigid_bodies = rigid_bodies
74            .iter()
75            .chain(ignored.iter())
76            .filter_map(|e| rigidbody_set.entity2body.get(e).copied())
77            .collect();
78        Self {
79            colliders,
80            ignored,
81            rigid_bodies,
82            predicate,
83        }
84    }
85
86    /// Returns `true` if no collider can be excluded by `self`.
87    pub fn is_empty(&self) -> bool {
88        self.colliders.is_empty()
89            && self.ignored.is_empty()
90            && self.rigid_bodies.is_empty()
91            && self.predicate.is_none()
92    }
93
94    /// Returns `true` if the collider must be seen by the controller.
95    pub fn test(&self, collider: &RapierCollider) -> bool {
96        let entity = RapierContextColliders::entity_from_collider(collider);
97        !self.colliders.contains(&entity)
98            && !self.ignored.contains(&entity)
99            && collider
100                .parent()
101                .is_none_or(|parent| !self.rigid_bodies.contains(&parent))
102            && self.predicate.is_none_or(|p| p.test(entity, collider))
103    }
104}