use crate::prelude::*;
use bevy::prelude::*;
pub struct BroadPhasePlugin;
impl Plugin for BroadPhasePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<AabbIntervals>();
let physics_schedule = app
.get_schedule_mut(PhysicsSchedule)
.expect("add PhysicsSchedule first");
physics_schedule.add_systems(
(
update_aabb,
update_aabb_intervals,
add_new_aabb_intervals,
collect_collision_pairs,
)
.chain()
.in_set(PhysicsStepSet::BroadPhase),
);
}
}
#[derive(Reflect, Resource, Default, Debug)]
#[reflect(Resource)]
pub struct BroadCollisionPairs(pub Vec<(Entity, Entity)>);
type AABBChanged = Or<(
Changed<Position>,
Changed<Rotation>,
Changed<LinearVelocity>,
Changed<AngularVelocity>,
Changed<Collider>,
)>;
#[allow(clippy::type_complexity)]
fn update_aabb(
mut colliders: Query<
(
&Collider,
&mut ColliderAabb,
&Position,
&Rotation,
Option<&ColliderParent>,
Option<&LinearVelocity>,
Option<&AngularVelocity>,
),
AABBChanged,
>,
parent_velocity: Query<
(&Position, Option<&LinearVelocity>, Option<&AngularVelocity>),
With<Children>,
>,
dt: Res<Time>,
) {
let safety_margin_factor = 2.0 * dt.delta_seconds_adjusted();
for (collider, mut aabb, pos, rot, collider_parent, lin_vel, ang_vel) in &mut colliders {
let (lin_vel, ang_vel) = if let (Some(lin_vel), Some(ang_vel)) = (lin_vel, ang_vel) {
(*lin_vel, *ang_vel)
} else if let Some(Ok((parent_pos, Some(lin_vel), Some(ang_vel)))) =
collider_parent.map(|p| parent_velocity.get(p.get()))
{
let offset = pos.0 - parent_pos.0;
#[cfg(feature = "2d")]
let vel_at_offset =
lin_vel.0 + Vector::new(-ang_vel.0 * offset.y, ang_vel.0 * offset.x) * 1.0;
#[cfg(feature = "3d")]
let vel_at_offset = lin_vel.0 + ang_vel.cross(offset);
(LinearVelocity(vel_at_offset), *ang_vel)
} else {
(LinearVelocity::ZERO, AngularVelocity::ZERO)
};
let start_iso = utils::make_isometry(*pos, *rot);
let end_iso = {
#[cfg(feature = "2d")]
{
utils::make_isometry(
pos.0 + lin_vel.0 * safety_margin_factor,
*rot + Rotation::from_radians(safety_margin_factor * ang_vel.0),
)
}
#[cfg(feature = "3d")]
{
let q = Quaternion::from_vec4(ang_vel.0.extend(0.0)) * rot.0;
let (x, y, z, w) = (
rot.x + safety_margin_factor * 0.5 * q.x,
rot.y + safety_margin_factor * 0.5 * q.y,
rot.z + safety_margin_factor * 0.5 * q.z,
rot.w + safety_margin_factor * 0.5 * q.w,
);
utils::make_isometry(
pos.0 + lin_vel.0 * safety_margin_factor,
Quaternion::from_xyzw(x, y, z, w).normalize(),
)
}
};
aabb.0 = collider
.shape_scaled()
.compute_swept_aabb(&start_iso, &end_iso);
}
}
type IsBodyInactive = bool;
#[derive(Resource, Default)]
struct AabbIntervals(Vec<(Entity, ColliderAabb, CollisionLayers, IsBodyInactive)>);
#[allow(clippy::type_complexity)]
fn update_aabb_intervals(
aabbs: Query<(
&ColliderAabb,
Option<&CollisionLayers>,
Ref<Position>,
Ref<Rotation>,
)>,
mut intervals: ResMut<AabbIntervals>,
) {
intervals
.0
.retain_mut(|(entity, aabb, layers, is_inactive)| {
if let Ok((new_aabb, new_layers, position, rotation)) = aabbs.get(*entity) {
*aabb = *new_aabb;
*layers = new_layers.map_or(CollisionLayers::default(), |layers| *layers);
*is_inactive = !position.is_changed() && !rotation.is_changed();
true
} else {
false
}
});
}
#[allow(clippy::type_complexity)]
fn add_new_aabb_intervals(
aabbs: Query<
(
Entity,
&ColliderAabb,
Option<&RigidBody>,
Option<&CollisionLayers>,
),
Added<ColliderAabb>,
>,
mut intervals: ResMut<AabbIntervals>,
) {
let aabbs = aabbs.iter().map(|(ent, aabb, rb, layers)| {
(
ent,
*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.1.mins.x > b.1.mins.x);
broad_collision_pairs.clear();
for (i, (ent1, aabb1, layers1, inactive1)) in intervals.0.iter().enumerate() {
for (ent2, aabb2, layers2, inactive2) in intervals.0.iter().skip(i + 1) {
if aabb2.mins.x > aabb1.maxs.x {
break;
}
if (*inactive1 && *inactive2) || !layers1.interacts_with(*layers2) {
continue;
}
if aabb1.mins.y > aabb2.maxs.y || aabb1.maxs.y < aabb2.mins.y {
continue;
}
#[cfg(feature = "3d")]
if aabb1.mins.z > aabb2.maxs.z || aabb1.maxs.z < aabb2.mins.z {
continue;
}
broad_collision_pairs.push((*ent1, *ent2));
}
}
}
fn insertion_sort<T>(items: &mut Vec<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;
}
}
}