avian3d/collision/
broad_phase.rs

1//! Finds pairs of entities with overlapping [`ColliderAabb`]s to reduce
2//! the number of potential contacts for the [narrow phase](super::narrow_phase).
3//!
4//! See [`BroadPhasePlugin`].
5
6use core::marker::PhantomData;
7
8use crate::{data_structures::pair_key::PairKey, prelude::*};
9use bevy::{
10    ecs::{
11        entity::{EntityHashSet, EntityMapper, MapEntities},
12        entity_disabling::Disabled,
13        system::{lifetimeless::Read, StaticSystemParam, SystemParamItem},
14    },
15    prelude::*,
16};
17
18use super::CollisionDiagnostics;
19
20/// Finds pairs of entities with overlapping [`ColliderAabb`]s to reduce
21/// the number of potential contacts for the [narrow phase](super::narrow_phase).
22///
23/// A contact pair is created in the [`ContactGraph`] resource for each pair found.
24/// Removing and updating these pairs is left to the [narrow phase](super::narrow_phase).
25///
26/// Currently, the broad phase uses the [sweep and prune](https://en.wikipedia.org/wiki/Sweep_and_prune) algorithm.
27///
28/// The broad phase systems run in [`PhysicsStepSet::BroadPhase`].
29///
30/// [`CollisionHooks`] can be provided with generics to apply custom filtering for collision pairs.
31pub struct BroadPhasePlugin<H: CollisionHooks = ()>(PhantomData<H>);
32
33impl<H: CollisionHooks> Default for BroadPhasePlugin<H> {
34    fn default() -> Self {
35        Self(PhantomData)
36    }
37}
38
39impl<H: CollisionHooks + 'static> Plugin for BroadPhasePlugin<H>
40where
41    for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
42{
43    fn build(&self, app: &mut App) {
44        app.init_resource::<AabbIntervals>();
45
46        app.configure_sets(
47            PhysicsSchedule,
48            (
49                BroadPhaseSet::First,
50                BroadPhaseSet::UpdateStructures,
51                BroadPhaseSet::CollectCollisions,
52                BroadPhaseSet::Last,
53            )
54                .chain()
55                .in_set(PhysicsStepSet::BroadPhase),
56        );
57
58        let physics_schedule = app
59            .get_schedule_mut(PhysicsSchedule)
60            .expect("add PhysicsSchedule first");
61
62        physics_schedule.add_systems(
63            (update_aabb_intervals, add_new_aabb_intervals)
64                .chain()
65                .in_set(BroadPhaseSet::UpdateStructures),
66        );
67
68        physics_schedule
69            .add_systems(collect_collision_pairs::<H>.in_set(BroadPhaseSet::CollectCollisions));
70    }
71
72    fn finish(&self, app: &mut App) {
73        // Register timer and counter diagnostics for collision detection.
74        app.register_physics_diagnostics::<CollisionDiagnostics>();
75    }
76}
77
78/// System sets for systems running in [`PhysicsStepSet::BroadPhase`].
79#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
80pub enum BroadPhaseSet {
81    /// Runs at the start of the broad phase. Empty by default.
82    First,
83    /// Updates acceleration structures and other data needed for broad phase collision detection.
84    UpdateStructures,
85    /// Finds pairs of entities with overlapping [`ColliderAabb`]s
86    /// and creates contact pairs for them in the [`ContactGraph`].
87    CollectCollisions,
88    /// Runs at the end of the broad phase. Empty by default.
89    Last,
90}
91
92/// Entities with [`ColliderAabb`]s sorted along an axis by their extents.
93#[derive(Resource, Default)]
94struct AabbIntervals(
95    Vec<(
96        Entity,
97        ColliderOf,
98        ColliderAabb,
99        CollisionLayers,
100        AabbIntervalFlags,
101    )>,
102);
103
104bitflags::bitflags! {
105    /// Flags for AABB intervals in the broad phase.
106    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
107    pub struct AabbIntervalFlags: u8 {
108        /// Set if the body is sleeping or static.
109        const IS_INACTIVE = 1 << 0;
110        /// Set if the collider is a sensor.
111        const IS_SENSOR = 1 << 1;
112        /// Set if collision events are enabled for this entity.
113        const CONTACT_EVENTS = 1 << 2;
114        /// Set if [`CollisionHooks::filter_pairs`] should be called for this entity.
115        const CUSTOM_FILTER = 1 << 3;
116        /// Set if [`CollisionHooks::modify_contacts`] should be called for this entity.
117        const MODIFY_CONTACTS = 1 << 4;
118    }
119}
120
121impl MapEntities for AabbIntervals {
122    fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
123        for interval in self.0.iter_mut() {
124            interval.0 = entity_mapper.get_mapped(interval.0);
125        }
126    }
127}
128
129/// Updates [`AabbIntervals`] to keep them in sync with the [`ColliderAabb`]s.
130#[allow(clippy::type_complexity)]
131fn update_aabb_intervals(
132    aabbs: Query<
133        (
134            &ColliderAabb,
135            Option<&ColliderOf>,
136            &CollisionLayers,
137            Has<Sensor>,
138            Has<CollisionEventsEnabled>,
139            Option<&ActiveCollisionHooks>,
140            Has<Sleeping>,
141        ),
142        Without<ColliderDisabled>,
143    >,
144    rbs: Query<&RigidBody>,
145    mut intervals: ResMut<AabbIntervals>,
146) {
147    intervals
148        .0
149        .retain_mut(|(collider_entity, collider_of, aabb, layers, flags)| {
150            if let Ok((
151                new_aabb,
152                new_collider_of,
153                new_layers,
154                is_sensor,
155                events_enabled,
156                hooks,
157                is_sleeping,
158            )) = aabbs.get(*collider_entity)
159            {
160                if !new_aabb.min.is_finite() || !new_aabb.max.is_finite() {
161                    return false;
162                }
163
164                *aabb = *new_aabb;
165                *collider_of = new_collider_of.map_or(
166                    ColliderOf {
167                        body: *collider_entity,
168                    },
169                    |p| *p,
170                );
171                *layers = *new_layers;
172
173                let is_static = new_collider_of.is_some_and(|collider_of| {
174                    rbs.get(collider_of.body).is_ok_and(RigidBody::is_static)
175                });
176
177                flags.set(AabbIntervalFlags::IS_INACTIVE, is_static || is_sleeping);
178                flags.set(AabbIntervalFlags::IS_SENSOR, is_sensor);
179                flags.set(AabbIntervalFlags::CONTACT_EVENTS, events_enabled);
180                flags.set(
181                    AabbIntervalFlags::CUSTOM_FILTER,
182                    hooks.is_some_and(|h| h.contains(ActiveCollisionHooks::FILTER_PAIRS)),
183                );
184                flags.set(
185                    AabbIntervalFlags::MODIFY_CONTACTS,
186                    hooks.is_some_and(|h| h.contains(ActiveCollisionHooks::MODIFY_CONTACTS)),
187                );
188
189                true
190            } else {
191                false
192            }
193        });
194}
195
196type AabbIntervalQueryData = (
197    Entity,
198    Option<Read<ColliderOf>>,
199    Read<ColliderAabb>,
200    Option<Read<RigidBody>>,
201    Read<CollisionLayers>,
202    Has<Sensor>,
203    Has<CollisionEventsEnabled>,
204    Option<Read<ActiveCollisionHooks>>,
205);
206
207// TODO: This is pretty gross and inefficient. This should be done with observers or hooks
208//       once we rework the broad phase.
209/// Adds new [`ColliderAabb`]s to [`AabbIntervals`].
210#[allow(clippy::type_complexity)]
211fn add_new_aabb_intervals(
212    added_aabbs: Query<AabbIntervalQueryData, (Added<ColliderAabb>, Without<ColliderDisabled>)>,
213    aabbs: Query<AabbIntervalQueryData, Without<ColliderDisabled>>,
214    mut intervals: ResMut<AabbIntervals>,
215    mut re_enabled_colliders: RemovedComponents<ColliderDisabled>,
216    mut re_enabled_entities: RemovedComponents<Disabled>,
217) {
218    // Collect re-enabled entities without duplicates.
219    let re_enabled = re_enabled_colliders
220        .read()
221        .chain(re_enabled_entities.read())
222        .collect::<EntityHashSet>();
223    let re_enabled_aabbs = aabbs.iter_many(re_enabled);
224
225    let aabbs = added_aabbs.iter().chain(re_enabled_aabbs).map(
226        |(entity, collider_of, aabb, rb, layers, is_sensor, events_enabled, hooks)| {
227            let mut flags = AabbIntervalFlags::empty();
228            flags.set(
229                AabbIntervalFlags::IS_INACTIVE,
230                rb.is_some_and(|rb| rb.is_static()),
231            );
232            flags.set(AabbIntervalFlags::IS_SENSOR, is_sensor);
233            flags.set(AabbIntervalFlags::CONTACT_EVENTS, events_enabled);
234            flags.set(
235                AabbIntervalFlags::CUSTOM_FILTER,
236                hooks.is_some_and(|h| h.contains(ActiveCollisionHooks::FILTER_PAIRS)),
237            );
238            flags.set(
239                AabbIntervalFlags::MODIFY_CONTACTS,
240                hooks.is_some_and(|h| h.contains(ActiveCollisionHooks::MODIFY_CONTACTS)),
241            );
242            (
243                entity,
244                collider_of.map_or(ColliderOf { body: entity }, |p| *p),
245                *aabb,
246                *layers,
247                flags,
248            )
249        },
250    );
251    intervals.0.extend(aabbs);
252}
253
254/// Finds pairs of entities with overlapping [`ColliderAabb`]s
255/// and creates contact pairs for them in the [`ContactGraph`].
256fn collect_collision_pairs<H: CollisionHooks>(
257    intervals: ResMut<AabbIntervals>,
258    mut contact_graph: ResMut<ContactGraph>,
259    hooks: StaticSystemParam<H>,
260    mut commands: Commands,
261    mut diagnostics: ResMut<CollisionDiagnostics>,
262) where
263    for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
264{
265    let start = crate::utils::Instant::now();
266
267    sweep_and_prune::<H>(
268        intervals,
269        &mut contact_graph,
270        &mut hooks.into_inner(),
271        &mut commands,
272    );
273
274    diagnostics.broad_phase = start.elapsed();
275}
276
277/// Sorts the entities by their minimum extents along an axis and collects the entity pairs that have intersecting AABBs.
278///
279/// Sweep and prune exploits temporal coherence, as bodies are unlikely to move significantly between two simulation steps. Insertion sort is used, as it is good at sorting nearly sorted lists efficiently.
280fn sweep_and_prune<H: CollisionHooks>(
281    mut intervals: ResMut<AabbIntervals>,
282    contact_graph: &mut ContactGraph,
283    hooks: &mut H::Item<'_, '_>,
284    commands: &mut Commands,
285) where
286    for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
287{
288    // Sort bodies along the x-axis using insertion sort, a sorting algorithm great for sorting nearly sorted lists.
289    insertion_sort(&mut intervals.0, |a, b| a.2.min.x > b.2.min.x);
290
291    // Find potential collisions by checking for AABB intersections along all axes.
292    // TODO: Find pairs in parallel, but create contact pairs serially for determinism.
293    for (i, (entity1, collider_of1, aabb1, layers1, flags1)) in intervals.0.iter().enumerate() {
294        for (entity2, collider_of2, aabb2, layers2, flags2) in intervals.0.iter().skip(i + 1) {
295            // x doesn't intersect; check this first so we can discard as soon as possible.
296            if aabb2.min.x > aabb1.max.x {
297                break;
298            }
299
300            // y doesn't intersect.
301            if aabb1.min.y > aabb2.max.y || aabb1.max.y < aabb2.min.y {
302                continue;
303            }
304
305            #[cfg(feature = "3d")]
306            // z doesn't intersect.
307            if aabb1.min.z > aabb2.max.z || aabb1.max.z < aabb2.min.z {
308                continue;
309            }
310
311            // No collisions between bodies that haven't moved or colliders with incompatible layers
312            // or colliders attached to the same rigid body.
313            if flags1
314                .intersection(*flags2)
315                .contains(AabbIntervalFlags::IS_INACTIVE)
316                || !layers1.interacts_with(*layers2)
317                || collider_of1 == collider_of2
318            {
319                continue;
320            }
321
322            // Avoid duplicate pairs.
323            let pair_key = PairKey::new(entity1.index(), entity2.index());
324            if contact_graph.contains_key(&pair_key) {
325                continue;
326            }
327
328            // Apply user-defined filter.
329            if flags1
330                .union(*flags2)
331                .contains(AabbIntervalFlags::CUSTOM_FILTER)
332            {
333                let should_collide = hooks.filter_pairs(*entity1, *entity2, commands);
334                if !should_collide {
335                    continue;
336                }
337            }
338
339            // Create a new contact pair as non-touching.
340            // The narrow phase will determine if the entities are touching and compute contact data.
341            let mut contacts = ContactPair::new(*entity1, *entity2);
342
343            // Initialize flags and other data for the contact pair.
344            contacts.body1 = Some(collider_of1.body);
345            contacts.body2 = Some(collider_of2.body);
346            contacts.flags.set(
347                ContactPairFlags::SENSOR,
348                flags1.union(*flags2).contains(AabbIntervalFlags::IS_SENSOR),
349            );
350            contacts.flags.set(
351                ContactPairFlags::CONTACT_EVENTS,
352                flags1
353                    .union(*flags2)
354                    .contains(AabbIntervalFlags::CONTACT_EVENTS),
355            );
356            contacts.flags.set(
357                ContactPairFlags::MODIFY_CONTACTS,
358                flags1
359                    .union(*flags2)
360                    .contains(AabbIntervalFlags::MODIFY_CONTACTS),
361            );
362
363            // Add the contact pair to the contact graph.
364            contact_graph.add_pair_with_key(contacts, pair_key);
365        }
366    }
367}
368
369/// Sorts a list iteratively using comparisons. In an ascending sort order, when a smaller value is encountered, it is moved lower in the list until it is larger than the item before it.
370///
371/// This is relatively slow for large lists, but very efficient in cases where the list is already mostly sorted.
372fn insertion_sort<T>(items: &mut [T], comparison: fn(&T, &T) -> bool) {
373    for i in 1..items.len() {
374        let mut j = i;
375        while j > 0 && comparison(&items[j - 1], &items[j]) {
376            items.swap(j - 1, j);
377            j -= 1;
378        }
379    }
380}