1use 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
20pub 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 app.register_physics_diagnostics::<CollisionDiagnostics>();
75 }
76}
77
78#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
80pub enum BroadPhaseSet {
81 First,
83 UpdateStructures,
85 CollectCollisions,
88 Last,
90}
91
92#[derive(Resource, Default)]
94struct AabbIntervals(
95 Vec<(
96 Entity,
97 ColliderOf,
98 ColliderAabb,
99 CollisionLayers,
100 AabbIntervalFlags,
101 )>,
102);
103
104bitflags::bitflags! {
105 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
107 pub struct AabbIntervalFlags: u8 {
108 const IS_INACTIVE = 1 << 0;
110 const IS_SENSOR = 1 << 1;
112 const CONTACT_EVENTS = 1 << 2;
114 const CUSTOM_FILTER = 1 << 3;
116 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#[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#[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 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
254fn 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
277fn 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 insertion_sort(&mut intervals.0, |a, b| a.2.min.x > b.2.min.x);
290
291 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 if aabb2.min.x > aabb1.max.x {
297 break;
298 }
299
300 if aabb1.min.y > aabb2.max.y || aabb1.max.y < aabb2.min.y {
302 continue;
303 }
304
305 #[cfg(feature = "3d")]
306 if aabb1.min.z > aabb2.max.z || aabb1.max.z < aabb2.min.z {
308 continue;
309 }
310
311 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 let pair_key = PairKey::new(entity1.index(), entity2.index());
324 if contact_graph.contains_key(&pair_key) {
325 continue;
326 }
327
328 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 let mut contacts = ContactPair::new(*entity1, *entity2);
342
343 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 contact_graph.add_pair_with_key(contacts, pair_key);
365 }
366 }
367}
368
369fn 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}