use crate::prelude::*;
use bevy::{
ecs::entity::{EntityMapper, MapEntities},
prelude::*,
};
pub struct BroadPhasePlugin;
impl Plugin for BroadPhasePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<AabbIntervals>();
app.configure_sets(
PhysicsSchedule,
(
BroadPhaseSet::First,
BroadPhaseSet::UpdateStructures,
BroadPhaseSet::CollectCollisions,
BroadPhaseSet::Last,
)
.chain()
.in_set(PhysicsStepSet::BroadPhase),
);
let physics_schedule = app
.get_schedule_mut(PhysicsSchedule)
.expect("add PhysicsSchedule first");
physics_schedule.add_systems(
(update_aabb_intervals, add_new_aabb_intervals)
.chain()
.in_set(BroadPhaseSet::UpdateStructures),
);
physics_schedule
.add_systems(collect_collision_pairs.in_set(BroadPhaseSet::CollectCollisions));
}
}
#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BroadPhaseSet {
First,
UpdateStructures,
CollectCollisions,
Last,
}
#[derive(Reflect, Resource, Default, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[reflect(Resource)]
pub struct BroadCollisionPairs(pub Vec<(Entity, Entity)>);
type IsBodyInactive = bool;
#[derive(Resource, Default)]
struct AabbIntervals(
Vec<(
Entity,
ColliderParent,
ColliderAabb,
CollisionLayers,
IsBodyInactive,
)>,
);
impl MapEntities for AabbIntervals {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
for interval in self.0.iter_mut() {
interval.0 = entity_mapper.map_entity(interval.0);
}
}
}
#[allow(clippy::type_complexity)]
fn update_aabb_intervals(
aabbs: Query<(
&ColliderAabb,
Option<&ColliderParent>,
Option<&CollisionLayers>,
Ref<Position>,
Ref<Rotation>,
)>,
rbs: Query<&RigidBody>,
mut intervals: ResMut<AabbIntervals>,
) {
intervals.0.retain_mut(
|(collider_entity, collider_parent, aabb, layers, is_inactive)| {
if let Ok((new_aabb, new_parent, new_layers, position, rotation)) =
aabbs.get(*collider_entity)
{
*aabb = *new_aabb;
*collider_parent = new_parent.map_or(ColliderParent(*collider_entity), |p| *p);
*layers = new_layers.map_or(CollisionLayers::default(), |layers| *layers);
let is_static =
new_parent.is_some_and(|p| rbs.get(p.get()).is_ok_and(RigidBody::is_static));
*is_inactive = is_static || (!position.is_changed() && !rotation.is_changed());
true
} else {
false
}
},
);
}
#[allow(clippy::type_complexity)]
fn add_new_aabb_intervals(
aabbs: Query<
(
Entity,
Option<&ColliderParent>,
&ColliderAabb,
Option<&RigidBody>,
Option<&CollisionLayers>,
),
Added<ColliderAabb>,
>,
mut intervals: ResMut<AabbIntervals>,
) {
let aabbs = aabbs.iter().map(|(ent, parent, aabb, rb, layers)| {
(
ent,
parent.map_or(ColliderParent(ent), |p| *p),
*aabb,
layers.map_or(CollisionLayers::default(), |layers| *layers),
rb.map_or(false, |rb| rb.is_static()),
)
});
intervals.0.extend(aabbs);
}
fn collect_collision_pairs(
intervals: ResMut<AabbIntervals>,
mut broad_collision_pairs: ResMut<BroadCollisionPairs>,
) {
sweep_and_prune(intervals, &mut broad_collision_pairs.0);
}
fn sweep_and_prune(
mut intervals: ResMut<AabbIntervals>,
broad_collision_pairs: &mut Vec<(Entity, Entity)>,
) {
insertion_sort(&mut intervals.0, |a, b| a.2.min.x > b.2.min.x);
broad_collision_pairs.clear();
for (i, (ent1, parent1, aabb1, layers1, inactive1)) in intervals.0.iter().enumerate() {
for (ent2, parent2, aabb2, layers2, inactive2) in intervals.0.iter().skip(i + 1) {
if aabb2.min.x > aabb1.max.x {
break;
}
if (*inactive1 && *inactive2) || !layers1.interacts_with(*layers2) || parent1 == parent2
{
continue;
}
if aabb1.min.y > aabb2.max.y || aabb1.max.y < aabb2.min.y {
continue;
}
#[cfg(feature = "3d")]
if aabb1.min.z > aabb2.max.z || aabb1.max.z < aabb2.min.z {
continue;
}
broad_collision_pairs.push((*ent1, *ent2));
}
}
}
fn insertion_sort<T>(items: &mut [T], comparison: fn(&T, &T) -> bool) {
for i in 1..items.len() {
let mut j = i;
while j > 0 && comparison(&items[j - 1], &items[j]) {
items.swap(j - 1, j);
j -= 1;
}
}
}