1use core::marker::PhantomData;
7
8use crate::{
9 data_structures::pair_key::PairKey, dynamics::solver::joint_graph::JointGraph, prelude::*,
10};
11use bevy::{
12 ecs::{
13 entity::{EntityMapper, MapEntities},
14 entity_disabling::Disabled,
15 system::{StaticSystemParam, SystemParamItem, lifetimeless::Read},
16 },
17 prelude::*,
18};
19
20use super::{
21 CollisionDiagnostics,
22 contact_types::{ContactEdge, ContactEdgeFlags},
23};
24
25pub struct BroadPhasePlugin<H: CollisionHooks = ()>(PhantomData<H>);
37
38impl<H: CollisionHooks> Default for BroadPhasePlugin<H> {
39 fn default() -> Self {
40 Self(PhantomData)
41 }
42}
43
44impl<H: CollisionHooks + 'static> Plugin for BroadPhasePlugin<H>
45where
46 for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
47{
48 fn build(&self, app: &mut App) {
49 app.init_resource::<AabbIntervals>();
50
51 app.configure_sets(
52 PhysicsSchedule,
53 (
54 BroadPhaseSystems::First,
55 BroadPhaseSystems::UpdateStructures,
56 BroadPhaseSystems::CollectCollisions,
57 BroadPhaseSystems::Last,
58 )
59 .chain()
60 .in_set(PhysicsStepSystems::BroadPhase),
61 );
62
63 let physics_schedule = app
64 .get_schedule_mut(PhysicsSchedule)
65 .expect("add PhysicsSchedule first");
66
67 physics_schedule.add_systems(
68 (update_aabb_intervals, add_new_aabb_intervals)
69 .chain()
70 .in_set(BroadPhaseSystems::UpdateStructures),
71 );
72
73 physics_schedule
74 .add_systems(collect_collision_pairs::<H>.in_set(BroadPhaseSystems::CollectCollisions));
75
76 app.add_observer(
79 |trigger: On<Remove, Disabled>,
80 query: Query<
82 AabbIntervalQueryData,
83 (
84 Without<ColliderDisabled>,
85 Or<(With<Disabled>, Without<Disabled>)>,
86 ),
87 >,
88 rbs: Query<(&RigidBody, Has<RigidBodyDisabled>)>,
89 mut intervals: ResMut<AabbIntervals>| {
90 let entity = trigger.entity;
91
92 if let Ok((entity, collider_of, aabb, layers, is_sensor, events_enabled, hooks)) =
94 query.get(entity)
95 {
96 let flags = init_aabb_interval_flags(
97 collider_of,
98 &rbs,
99 is_sensor,
100 events_enabled,
101 hooks,
102 );
103 let interval = (
104 entity,
105 collider_of.map_or(ColliderOf { body: entity }, |p| *p),
106 *aabb,
107 *layers,
108 flags,
109 );
110
111 intervals.0.push(interval);
113 }
114 },
115 );
116
117 app.add_observer(
119 |trigger: On<Remove, ColliderDisabled>,
120 query: Query<AabbIntervalQueryData>,
121 rbs: Query<(&RigidBody, Has<RigidBodyDisabled>)>,
122 mut intervals: ResMut<AabbIntervals>| {
123 let entity = trigger.entity;
124
125 if let Ok((entity, collider_of, aabb, layers, is_sensor, events_enabled, hooks)) =
127 query.get(entity)
128 {
129 let flags = init_aabb_interval_flags(
130 collider_of,
131 &rbs,
132 is_sensor,
133 events_enabled,
134 hooks,
135 );
136 let interval = (
137 entity,
138 collider_of.map_or(ColliderOf { body: entity }, |p| *p),
139 *aabb,
140 *layers,
141 flags,
142 );
143
144 intervals.0.push(interval);
146 }
147 },
148 );
149 }
150
151 fn finish(&self, app: &mut App) {
152 app.register_physics_diagnostics::<CollisionDiagnostics>();
154 }
155}
156
157#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
159pub enum BroadPhaseSystems {
160 First,
162 UpdateStructures,
164 CollectCollisions,
167 Last,
169}
170
171#[deprecated(since = "0.4.0", note = "Renamed to `BroadPhaseSystems`")]
173pub type BroadPhaseSet = BroadPhaseSystems;
174
175#[derive(Resource, Default)]
177struct AabbIntervals(Vec<AabbInterval>);
178
179type AabbInterval = (
180 Entity,
181 ColliderOf,
182 ColliderAabb,
183 CollisionLayers,
184 AabbIntervalFlags,
185);
186
187bitflags::bitflags! {
188 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
190 pub struct AabbIntervalFlags: u8 {
191 const IS_INACTIVE = 1 << 0;
193 const CONTACT_EVENTS = 1 << 1;
195 const GENERATE_CONSTRAINTS = 1 << 2;
197 const CUSTOM_FILTER = 1 << 3;
199 const MODIFY_CONTACTS = 1 << 4;
201 }
202}
203
204impl MapEntities for AabbIntervals {
205 fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
206 for interval in self.0.iter_mut() {
207 interval.0 = entity_mapper.get_mapped(interval.0);
208 }
209 }
210}
211
212#[allow(clippy::type_complexity)]
214fn update_aabb_intervals(
215 aabbs: Query<
216 (
217 &ColliderAabb,
218 Option<&ColliderOf>,
219 &CollisionLayers,
220 Has<Sensor>,
221 Has<CollisionEventsEnabled>,
222 Option<&ActiveCollisionHooks>,
223 Has<Sleeping>,
224 ),
225 Without<ColliderDisabled>,
226 >,
227 rbs: Query<(&RigidBody, Has<RigidBodyDisabled>)>,
228 mut intervals: ResMut<AabbIntervals>,
229) {
230 intervals
231 .0
232 .retain_mut(|(collider_entity, collider_of, aabb, layers, flags)| {
233 if let Ok((
234 new_aabb,
235 new_collider_of,
236 new_layers,
237 is_sensor,
238 events_enabled,
239 hooks,
240 is_sleeping,
241 )) = aabbs.get(*collider_entity)
242 {
243 if !new_aabb.min.is_finite() || !new_aabb.max.is_finite() {
244 return false;
245 }
246
247 *aabb = *new_aabb;
248 *collider_of = new_collider_of.map_or(
249 ColliderOf {
250 body: *collider_entity,
251 },
252 |p| *p,
253 );
254 *layers = *new_layers;
255
256 let rb = new_collider_of.and_then(|collider_of| rbs.get(collider_of.body).ok());
257 let is_static = rb.is_some_and(|(body, _)| body.is_static());
258 let is_disabled = rb.is_some_and(|(_, is_disabled)| is_disabled);
259
260 flags.set(AabbIntervalFlags::IS_INACTIVE, is_static || is_sleeping);
261 flags.set(AabbIntervalFlags::CONTACT_EVENTS, events_enabled);
262 flags.set(
263 AabbIntervalFlags::GENERATE_CONSTRAINTS,
264 !is_sensor && !is_disabled,
265 );
266 flags.set(
267 AabbIntervalFlags::CUSTOM_FILTER,
268 hooks.is_some_and(|h| h.contains(ActiveCollisionHooks::FILTER_PAIRS)),
269 );
270 flags.set(
271 AabbIntervalFlags::MODIFY_CONTACTS,
272 hooks.is_some_and(|h| h.contains(ActiveCollisionHooks::MODIFY_CONTACTS)),
273 );
274
275 true
276 } else {
277 false
278 }
279 });
280}
281
282type AabbIntervalQueryData = (
283 Entity,
284 Option<Read<ColliderOf>>,
285 Read<ColliderAabb>,
286 Read<CollisionLayers>,
287 Has<Sensor>,
288 Has<CollisionEventsEnabled>,
289 Option<Read<ActiveCollisionHooks>>,
290);
291
292#[allow(clippy::type_complexity)]
296fn add_new_aabb_intervals(
297 added_aabbs: Query<AabbIntervalQueryData, (Added<ColliderAabb>, Without<ColliderDisabled>)>,
298 rbs: Query<(&RigidBody, Has<RigidBodyDisabled>)>,
299 mut intervals: ResMut<AabbIntervals>,
300) {
301 let aabbs = added_aabbs.iter().map(
302 |(entity, collider_of, aabb, layers, is_sensor, events_enabled, hooks)| {
303 let flags =
304 init_aabb_interval_flags(collider_of, &rbs, is_sensor, events_enabled, hooks);
305 (
306 entity,
307 collider_of.map_or(ColliderOf { body: entity }, |p| *p),
308 *aabb,
309 *layers,
310 flags,
311 )
312 },
313 );
314 intervals.0.extend(aabbs);
315}
316
317fn init_aabb_interval_flags(
318 collider_of: Option<&ColliderOf>,
319 rbs: &Query<(&RigidBody, Has<RigidBodyDisabled>)>,
320 is_sensor: bool,
321 events_enabled: bool,
322 hooks: Option<&ActiveCollisionHooks>,
323) -> AabbIntervalFlags {
324 let mut flags = AabbIntervalFlags::empty();
325 let rb = collider_of.and_then(|collider_of| rbs.get(collider_of.body).ok());
326 let is_static = rb.is_some_and(|(body, _)| body.is_static());
327 let is_body_disabled = rb.is_some_and(|(_, is_disabled)| is_disabled);
328 flags.set(AabbIntervalFlags::IS_INACTIVE, is_static);
329 flags.set(AabbIntervalFlags::CONTACT_EVENTS, events_enabled);
330 flags.set(
331 AabbIntervalFlags::GENERATE_CONSTRAINTS,
332 !is_sensor && !is_body_disabled,
333 );
334 flags.set(
335 AabbIntervalFlags::CUSTOM_FILTER,
336 hooks.is_some_and(|h| h.contains(ActiveCollisionHooks::FILTER_PAIRS)),
337 );
338 flags.set(
339 AabbIntervalFlags::MODIFY_CONTACTS,
340 hooks.is_some_and(|h| h.contains(ActiveCollisionHooks::MODIFY_CONTACTS)),
341 );
342 flags
343}
344
345fn collect_collision_pairs<H: CollisionHooks>(
348 intervals: ResMut<AabbIntervals>,
349 mut contact_graph: ResMut<ContactGraph>,
350 joint_graph: Res<JointGraph>,
351 hooks: StaticSystemParam<H>,
352 mut commands: Commands,
353 mut diagnostics: ResMut<CollisionDiagnostics>,
354) where
355 for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
356{
357 let start = crate::utils::Instant::now();
358
359 sweep_and_prune::<H>(
360 intervals,
361 &mut contact_graph,
362 &joint_graph,
363 &mut hooks.into_inner(),
364 &mut commands,
365 );
366
367 diagnostics.broad_phase = start.elapsed();
368}
369
370fn sweep_and_prune<H: CollisionHooks>(
374 mut intervals: ResMut<AabbIntervals>,
375 contact_graph: &mut ContactGraph,
376 joint_graph: &JointGraph,
377 hooks: &mut H::Item<'_, '_>,
378 commands: &mut Commands,
379) where
380 for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
381{
382 insertion_sort(&mut intervals.0, |a, b| a.2.min.x > b.2.min.x);
384
385 for (i, (entity1, collider_of1, aabb1, layers1, flags1)) in intervals.0.iter().enumerate() {
388 for (entity2, collider_of2, aabb2, layers2, flags2) in intervals.0.iter().skip(i + 1) {
389 if aabb2.min.x > aabb1.max.x {
391 break;
392 }
393
394 if aabb1.min.y > aabb2.max.y || aabb1.max.y < aabb2.min.y {
396 continue;
397 }
398
399 #[cfg(feature = "3d")]
400 if aabb1.min.z > aabb2.max.z || aabb1.max.z < aabb2.min.z {
402 continue;
403 }
404
405 if flags1
408 .intersection(*flags2)
409 .contains(AabbIntervalFlags::IS_INACTIVE)
410 || !layers1.interacts_with(*layers2)
411 || collider_of1 == collider_of2
412 {
413 continue;
414 }
415
416 let pair_key = PairKey::new(entity1.index(), entity2.index());
418 if contact_graph.contains_key(&pair_key) {
419 continue;
420 }
421
422 if joint_graph
424 .joints_between(collider_of1.body, collider_of2.body)
425 .any(|edge| edge.collision_disabled)
426 {
427 continue;
428 }
429
430 if flags1
432 .union(*flags2)
433 .contains(AabbIntervalFlags::CUSTOM_FILTER)
434 {
435 let should_collide = hooks.filter_pairs(*entity1, *entity2, commands);
436 if !should_collide {
437 continue;
438 }
439 }
440
441 let mut contact_edge = ContactEdge::new(*entity1, *entity2);
444 contact_edge.body1 = Some(collider_of1.body);
445 contact_edge.body2 = Some(collider_of2.body);
446 contact_edge.flags.set(
447 ContactEdgeFlags::CONTACT_EVENTS,
448 flags1
449 .union(*flags2)
450 .contains(AabbIntervalFlags::CONTACT_EVENTS),
451 );
452 contact_graph
453 .add_edge_and_key_with(contact_edge, pair_key, |contact_pair| {
454 contact_pair.body1 = Some(collider_of1.body);
455 contact_pair.body2 = Some(collider_of2.body);
456 contact_pair.flags.set(
457 ContactPairFlags::MODIFY_CONTACTS,
458 flags1
459 .union(*flags2)
460 .contains(AabbIntervalFlags::MODIFY_CONTACTS),
461 );
462 contact_pair.flags.set(
463 ContactPairFlags::GENERATE_CONSTRAINTS,
464 flags1
465 .union(*flags2)
466 .contains(AabbIntervalFlags::GENERATE_CONSTRAINTS),
467 );
468 })
469 .unwrap_or_else(|| {
470 panic!("Pair key already exists in contact graph: {pair_key:?}")
471 });
472 }
473 }
474}
475
476fn insertion_sort<T>(items: &mut [T], comparison: fn(&T, &T) -> bool) {
480 for i in 1..items.len() {
481 let mut j = i;
482 while j > 0 && comparison(&items[j - 1], &items[j]) {
483 items.swap(j - 1, j);
484 j -= 1;
485 }
486 }
487}