avian3d/collision/hooks.rs
1//! Collision hooks for filtering and modifying contacts.
2//!
3//! See the [`CollisionHooks`] trait for more information.
4
5use crate::prelude::*;
6use bevy::{ecs::system::ReadOnlySystemParam, prelude::*};
7
8/// A trait for user-defined hooks that can filter and modify contacts.
9///
10/// This can be useful for advanced contact scenarios, such as:
11///
12/// - One-way platforms
13/// - Conveyor belts
14/// - Non-uniform friction and restitution
15///
16/// Collision hooks are more flexible than built-in filtering options like [`CollisionLayers`],
17/// but can be more complicated to define, and can have slightly more overhead.
18/// It is recommended to use hooks only when existing options are not sufficient.
19///
20/// Only one set of collision hooks can be defined per broad phase and narrow phase.
21///
22/// # Defining Hooks
23///
24/// Collision hooks can be defined by implementing the [`CollisionHooks`] trait for a type
25/// that implements [`SystemParam`]. The system parameter allows the hooks to do things like
26/// access resources, query for components, and perform [spatial queries](crate::spatial_query).
27///
28/// Note that mutable access is not allowed for the system parameter, as hooks may be called
29/// during parallel iteration. However, access to [`Commands`] is provided for deferred changes.
30///
31/// Below is an example of using collision hooks to implement interaction groups and one-way platforms:
32///
33/// ```
34#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
35#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
36/// use bevy::{ecs::system::SystemParam, prelude::*};
37///
38/// /// A component that groups entities for interactions. Only entities in the same group can collide.
39/// #[derive(Component)]
40/// struct InteractionGroup(u32);
41///
42/// /// A component that marks an entity as a one-way platform.
43/// #[derive(Component)]
44/// struct OneWayPlatform;
45///
46/// // Define a `SystemParam` for the collision hooks.
47/// #[derive(SystemParam)]
48/// struct MyHooks<'w, 's> {
49/// interaction_query: Query<'w, 's, &'static InteractionGroup>,
50/// platform_query: Query<'w, 's, &'static Transform, With<OneWayPlatform>>,
51/// }
52///
53/// // Implement the `CollisionHooks` trait.
54/// impl CollisionHooks for MyHooks<'_, '_> {
55/// fn filter_pairs(&self, collider1: Entity, collider2: Entity, _commands: &mut Commands) -> bool {
56/// // Only allow collisions between entities in the same interaction group.
57/// // This could be a basic solution for "multiple physics worlds" that don't interact.
58/// let Ok([group1, group2]) = self.interaction_query.get_many([collider1, collider2]) else {
59/// return true;
60/// };
61/// group1.0 == group2.0
62/// }
63///
64/// fn modify_contacts(&self, contacts: &mut ContactPair, _commands: &mut Commands) -> bool {
65/// // Allow entities to pass through the bottom and sides of one-way platforms.
66/// // See the `one_way_platform_2d` example for a full implementation.
67/// let (entity1, entity2) = (contacts.collider1, contacts.collider2);
68/// !is_hitting_top_of_platform(entity1, entity2, &self.platform_query, &contacts)
69/// }
70/// }
71/// #
72/// # fn is_hitting_top_of_platform(
73/// # entity1: Entity,
74/// # entity2: Entity,
75/// # platform_query: &Query<&Transform, With<OneWayPlatform>>,
76/// # contacts: &ContactPair,
77/// # ) -> bool {
78/// # todo!()
79/// # }
80/// ```
81///
82/// The hooks can then be added to the app using [`PhysicsPlugins::with_collision_hooks`]:
83///
84/// ```no_run
85#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
86#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
87/// # use bevy::{ecs::system::SystemParam, prelude::*};
88/// #
89/// # #[derive(SystemParam)]
90/// # struct MyHooks {}
91/// #
92/// # // No-op hooks for the example.
93/// # impl CollisionHooks for MyHooks {}
94/// #
95/// fn main() {
96/// App::new()
97/// .add_plugins((
98/// DefaultPlugins,
99/// PhysicsPlugins::default().with_collision_hooks::<MyHooks>(),
100/// ))
101/// .run();
102/// }
103/// ```
104///
105/// This is equivalent to manually replacing the default [`BroadPhasePlugin`] and [`NarrowPhasePlugin`]
106/// with instances that have the desired hooks provided using generics.
107///
108/// [`SystemParam`]: bevy::ecs::system::SystemParam
109///
110/// # Activating Hooks
111///
112/// Hooks are *only* called for collisions where at least one entity has the [`ActiveCollisionHooks`] component
113/// with the corresponding flags set. By default, no hooks are called.
114///
115/// ```
116#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
117#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
118/// # use bevy::prelude::*;
119/// #
120/// # fn setup(mut commands: Commands) {
121/// // Spawn a collider with filtering hooks enabled.
122/// commands.spawn((Collider::capsule(0.5, 1.5), ActiveCollisionHooks::FILTER_PAIRS));
123///
124/// // Spawn a collider with both filtering and contact modification hooks enabled.
125/// commands.spawn((
126/// Collider::capsule(0.5, 1.5),
127/// ActiveCollisionHooks::FILTER_PAIRS | ActiveCollisionHooks::MODIFY_CONTACTS
128/// ));
129///
130/// // Alternatively, all hooks can be enabled with `ActiveCollisionHooks::all()`.
131/// commands.spawn((Collider::capsule(0.5, 1.5), ActiveCollisionHooks::all()));
132/// # }
133/// ```
134///
135/// # Caveats
136///
137/// Collision hooks can access the ECS quite freely, but there are a few limitations:
138///
139/// - Only one set of collision hooks can be defined per broad phase and narrow phase.
140/// - Only read-only ECS access is allowed for the hook system parameter. Use the provided [`Commands`] for deferred ECS operations.
141/// - Note that command execution order is unspecified if the `parallel` feature is enabled.
142/// - Access to the [`ContactGraph`] resource is not allowed inside [`CollisionHooks::filter_pairs`].
143/// Trying to access it will result in a panic.
144/// - Access to the [`ContactGraph`] resource is not allowed inside [`CollisionHooks::modify_contacts`].
145/// Trying to access it will result in a panic.
146#[expect(unused_variables)]
147pub trait CollisionHooks: ReadOnlySystemParam + Send + Sync {
148 /// A contact pair filtering hook that determines whether contacts should be computed
149 /// between `collider1` and `collider2`. If `false` is returned, contacts will not be computed.
150 ///
151 /// This is called in the broad phase, before the [`ContactPair`] has been computed.
152 ///
153 /// The provided [`Commands`] can be used for deferred ECS operations that run after
154 /// broad phase pairs have been found.
155 ///
156 /// # Notes
157 ///
158 /// - Only called if at least one entity in the contact pair has [`ActiveCollisionHooks::FILTER_PAIRS`] set.
159 /// - Only called if at least one entity in the contact pair is not [`RigidBody::Static`] and not [`Sleeping`].
160 /// - Access to the [`ContactGraph`] resource is not allowed in this method.
161 /// Trying to access it will result in a panic.
162 fn filter_pairs(&self, collider1: Entity, collider2: Entity, commands: &mut Commands) -> bool {
163 true
164 }
165
166 /// A contact modification hook that allows modifying the contacts for a given contact pair.
167 /// If `false` is returned, the contact pair will be removed.
168 ///
169 /// This is called in the narrow phase, after the [`ContactPair`] has been computed for the pair,
170 /// but before constraints have been generated for the contact solver.
171 ///
172 /// The provided [`Commands`] can be used for deferred ECS operations that run after
173 /// the narrow phase has computed contact pairs and generated constraints.
174 ///
175 /// # Notes
176 ///
177 /// - Only called if at least one entity in the contact pair has [`ActiveCollisionHooks::MODIFY_CONTACTS`] set.
178 /// - Only called if at least one entity in the contact pair is not [`RigidBody::Static`] and not [`Sleeping`].
179 /// - Impulses stored in `contacts` are from the previous physics tick.
180 /// - Command execution order is unspecified if the `parallel` feature is enabled.
181 /// - Access to the [`ContactGraph`] resource is not allowed in this method.
182 /// Trying to access it will result in a panic.
183 fn modify_contacts(&self, contacts: &mut ContactPair, commands: &mut Commands) -> bool {
184 true
185 }
186}
187
188// No-op implementation for `()` to allow default hooks for plugins.
189impl CollisionHooks for () {}
190
191/// A component with flags indicating which [`CollisionHooks`] should be called for collisions with an entity.
192///
193/// Hooks will only be called if either entity in a collision has the corresponding flags set.
194///
195/// Default: [`ActiveCollisionHooks::empty()`]
196///
197/// # Example
198///
199/// ```
200#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
201#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
202/// # use bevy::prelude::*;
203/// #
204/// # fn setup(mut commands: Commands) {
205/// // Spawn a collider with filtering hooks enabled.
206/// commands.spawn((Collider::capsule(0.5, 1.5), ActiveCollisionHooks::FILTER_PAIRS));
207///
208/// // Spawn a collider with both filtering and contact modification hooks enabled.
209/// commands.spawn((
210/// Collider::capsule(0.5, 1.5),
211/// ActiveCollisionHooks::FILTER_PAIRS | ActiveCollisionHooks::MODIFY_CONTACTS
212/// ));
213///
214/// // Alternatively, all hooks can be enabled with `ActiveCollisionHooks::all()`.
215/// commands.spawn((Collider::capsule(0.5, 1.5), ActiveCollisionHooks::all()));
216/// # }
217/// ```
218#[repr(transparent)]
219#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
220#[derive(Component, Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
221#[reflect(opaque, Hash, PartialEq, Debug)]
222pub struct ActiveCollisionHooks(u8);
223
224bitflags::bitflags! {
225 impl ActiveCollisionHooks: u8 {
226 /// Set if [`CollisionHooks::filter_pairs`] should be called for collisions with this entity.
227 const FILTER_PAIRS = 0b0000_0001;
228 /// Set if [`CollisionHooks::modify_contacts`] should be called for collisions with this entity.
229 const MODIFY_CONTACTS = 0b0000_0010;
230 }
231}