avian3d/collision/
hooks.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
//! Collision hooks for filtering and modifying contacts.
//!
//! See the [`CollisionHooks`] trait for more information.

use crate::prelude::*;
use bevy::{ecs::system::ReadOnlySystemParam, prelude::*};

/// A trait for user-defined hooks that can filter and modify contacts.
///
/// This can be useful for advanced contact scenarios, such as:
///
/// - One-way platforms
/// - Conveyor belts
/// - Non-uniform friction and restitution
///
/// Collision hooks are more flexible than built-in filtering options like [`CollisionLayers`],
/// but can be more complicated to define, and can have slightly more overhead.
/// It is recommended to use hooks only when existing options are not sufficient.
///
/// Only one set of collision hooks can be defined per broad phase and narrow phase.
///
/// # Defining Hooks
///
/// Collision hooks can be defined by implementing the [`CollisionHooks`] trait for a type
/// that implements [`SystemParam`]. The system parameter allows the hooks to do things like
/// access resources, query for components, and perform [spatial queries](crate::spatial_query).
///
/// Note that mutable access is not allowed for the system parameter, as hooks may be called
/// during parallel iteration. However, access to [`Commands`] is provided for deferred changes.
///
/// Below is an example of using collision hooks to implement interaction groups and one-way platforms:
///
/// ```
#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
/// use bevy::{ecs::system::SystemParam, prelude::*};
///
/// /// A component that groups entities for interactions. Only entities in the same group can collide.
/// #[derive(Component)]
/// struct InteractionGroup(u32);
///
/// /// A component that marks an entity as a one-way platform.
/// #[derive(Component)]
/// struct OneWayPlatform;
///
/// // Define a `SystemParam` for the collision hooks.
/// #[derive(SystemParam)]
/// struct MyHooks<'w, 's> {
///     interaction_query: Query<'w, 's, &'static InteractionGroup>,
///     platform_query: Query<'w, 's, &'static Transform, With<OneWayPlatform>>,
/// }
///
/// // Implement the `CollisionHooks` trait.
/// impl CollisionHooks for MyHooks<'_, '_> {
///     fn filter_pairs(&self, collider1: Entity, collider2: Entity, _commands: &mut Commands) -> bool {
///         // Only allow collisions between entities in the same interaction group.
///         // This could be a basic solution for "multiple physics worlds" that don't interact.
///         let Ok([group1, group2]) = self.interaction_query.get_many([collider1, collider2]) else {
///            return true;
///         };
///         group1.0 == group2.0
///     }
///
///     fn modify_contacts(&self, contacts: &mut ContactPair, _commands: &mut Commands) -> bool {
///         // Allow entities to pass through the bottom and sides of one-way platforms.
///         // See the `one_way_platform_2d` example for a full implementation.
///         let (entity1, entity2) = (contacts.collider1, contacts.collider2);
///         !is_hitting_top_of_platform(entity1, entity2, &self.platform_query, &contacts)
///     }
/// }
/// #
/// # fn is_hitting_top_of_platform(
/// #     entity1: Entity,
/// #     entity2: Entity,
/// #     platform_query: &Query<&Transform, With<OneWayPlatform>>,
/// #     contacts: &ContactPair,
/// # ) -> bool {
/// #     todo!()
/// # }
/// ```
///
/// The hooks can then be added to the app using [`PhysicsPlugins::with_collision_hooks`]:
///
/// ```no_run
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
/// # use bevy::{ecs::system::SystemParam, prelude::*};
/// #
/// # #[derive(SystemParam)]
/// # struct MyHooks {}
/// #
/// # // No-op hooks for the example.
/// # impl CollisionHooks for MyHooks {}
/// #
/// fn main() {
///     App::new()
///         .add_plugins((
///             DefaultPlugins,
///             PhysicsPlugins::default().with_collision_hooks::<MyHooks>(),
///         ))
///         .run();
/// }
/// ```
///
/// This is equivalent to manually replacing the default [`BroadPhasePlugin`] and [`NarrowPhasePlugin`]
/// with instances that have the desired hooks provided using generics.
///
/// [`SystemParam`]: bevy::ecs::system::SystemParam
///
/// # Activating Hooks
///
/// Hooks are *only* called for collisions where at least one entity has the [`ActiveCollisionHooks`] component
/// with the corresponding flags set. By default, no hooks are called.
///
/// ```
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
/// # use bevy::prelude::*;
/// #
/// # fn setup(mut commands: Commands) {
/// // Spawn a collider with filtering hooks enabled.
/// commands.spawn((Collider::capsule(0.5, 1.5), ActiveCollisionHooks::FILTER_PAIRS));
///
/// // Spawn a collider with both filtering and contact modification hooks enabled.
/// commands.spawn((
///     Collider::capsule(0.5, 1.5),
///     ActiveCollisionHooks::FILTER_PAIRS | ActiveCollisionHooks::MODIFY_CONTACTS
/// ));
///
/// // Alternatively, all hooks can be enabled with `ActiveCollisionHooks::all()`.
/// commands.spawn((Collider::capsule(0.5, 1.5), ActiveCollisionHooks::all()));
/// # }
/// ```
///
/// # Caveats
///
/// Collision hooks can access the ECS quite freely, but there are a few limitations:
///
/// - Only one set of collision hooks can be defined per broad phase and narrow phase.
/// - Only read-only ECS access is allowed for the hook system parameter. Use the provided [`Commands`] for deferred ECS operations.
///   - Note that command execution order is unspecified if the `parallel` feature is enabled.
/// - Access to the [`ContactGraph`] resource is not allowed inside [`CollisionHooks::filter_pairs`].
///   Trying to access it will result in a panic.
/// - Access to the [`ContactGraph`] resource is not allowed inside [`CollisionHooks::modify_contacts`].
///   Trying to access it will result in a panic.
#[expect(unused_variables)]
pub trait CollisionHooks: ReadOnlySystemParam + Send + Sync {
    /// A contact pair filtering hook that determines whether contacts should be computed
    /// between `collider1` and `collider2`. If `false` is returned, contacts will not be computed.
    ///
    /// This is called in the broad phase, before the [`ContactPair`] has been computed.
    ///
    /// The provided [`Commands`] can be used for deferred ECS operations that run after
    /// broad phase pairs have been found.
    ///
    /// # Notes
    ///
    /// - Only called if at least one entity in the contact pair has [`ActiveCollisionHooks::FILTER_PAIRS`] set.
    /// - Only called if at least one entity in the contact pair is not [`RigidBody::Static`] and not [`Sleeping`].
    /// - Access to the [`ContactGraph`] resource is not allowed in this method.
    ///   Trying to access it will result in a panic.
    fn filter_pairs(&self, collider1: Entity, collider2: Entity, commands: &mut Commands) -> bool {
        true
    }

    /// A contact modification hook that allows modifying the contacts for a given contact pair.
    /// If `false` is returned, the contact pair will be removed.
    ///
    /// This is called in the narrow phase, after the [`ContactPair`] has been computed for the pair,
    /// but before constraints have been generated for the contact solver.
    ///
    /// The provided [`Commands`] can be used for deferred ECS operations that run after
    /// the narrow phase has computed contact pairs and generated constraints.
    ///
    /// # Notes
    ///
    /// - Only called if at least one entity in the contact pair has [`ActiveCollisionHooks::MODIFY_CONTACTS`] set.
    /// - Only called if at least one entity in the contact pair is not [`RigidBody::Static`] and not [`Sleeping`].
    /// - Impulses stored in `contacts` are from the previous physics tick.
    /// - Command execution order is unspecified if the `parallel` feature is enabled.
    /// - Access to the [`ContactGraph`] resource is not allowed in this method.
    ///   Trying to access it will result in a panic.
    fn modify_contacts(&self, contacts: &mut ContactPair, commands: &mut Commands) -> bool {
        true
    }
}

// No-op implementation for `()` to allow default hooks for plugins.
impl CollisionHooks for () {}

/// A component with flags indicating which [`CollisionHooks`] should be called for collisions with an entity.
///
/// Hooks will only be called if either entity in a collision has the corresponding flags set.
///
/// Default: [`ActiveCollisionHooks::empty()`]
///
/// # Example
///
/// ```
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
/// # use bevy::prelude::*;
/// #
/// # fn setup(mut commands: Commands) {
/// // Spawn a collider with filtering hooks enabled.
/// commands.spawn((Collider::capsule(0.5, 1.5), ActiveCollisionHooks::FILTER_PAIRS));
///
/// // Spawn a collider with both filtering and contact modification hooks enabled.
/// commands.spawn((
///     Collider::capsule(0.5, 1.5),
///     ActiveCollisionHooks::FILTER_PAIRS | ActiveCollisionHooks::MODIFY_CONTACTS
/// ));
///
/// // Alternatively, all hooks can be enabled with `ActiveCollisionHooks::all()`.
/// commands.spawn((Collider::capsule(0.5, 1.5), ActiveCollisionHooks::all()));
/// # }
/// ```
#[repr(transparent)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Component, Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
#[reflect(opaque, Hash, PartialEq, Debug)]
pub struct ActiveCollisionHooks(u8);

bitflags::bitflags! {
    impl ActiveCollisionHooks: u8 {
        /// Set if [`CollisionHooks::filter_pairs`] should be called for collisions with this entity.
        const FILTER_PAIRS = 0b0000_0001;
        /// Set if [`CollisionHooks::modify_contacts`] should be called for collisions with this entity.
        const MODIFY_CONTACTS = 0b0000_0010;
    }
}