use crate::prelude::*;
use bevy::ecs::query::Has;
#[cfg(feature = "parallel")]
use bevy::tasks::{ComputeTaskPool, ParallelSlice};
pub struct NarrowPhasePlugin;
impl Plugin for NarrowPhasePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<NarrowPhaseConfig>()
.init_resource::<Collisions>()
.register_type::<NarrowPhaseConfig>();
app.get_schedule_mut(PhysicsSchedule)
.expect("add PhysicsSchedule first")
.add_systems(
(
reset_collision_states
.after(PhysicsStepSet::BroadPhase)
.before(PhysicsStepSet::Substeps),
((|mut collisions: ResMut<Collisions>| {
collisions.retain(|contacts| contacts.during_current_frame)
}),)
.chain()
.after(PhysicsStepSet::ReportContacts)
.before(PhysicsStepSet::Sleeping),
)
.chain(),
);
app.get_schedule_mut(SubstepSchedule)
.expect("add SubstepSchedule first")
.add_systems(
(reset_substep_collision_states, collect_collisions)
.chain()
.in_set(SubstepSet::NarrowPhase),
);
}
}
#[derive(Resource, Reflect, Clone, Debug, PartialEq)]
#[reflect(Resource)]
pub struct NarrowPhaseConfig {
pub prediction_distance: Scalar,
}
impl Default for NarrowPhaseConfig {
fn default() -> Self {
Self {
#[cfg(feature = "2d")]
prediction_distance: 5.0,
#[cfg(feature = "3d")]
prediction_distance: 0.005,
}
}
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub fn collect_collisions(
bodies: Query<(
&Position,
Option<&AccumulatedTranslation>,
&Rotation,
&Collider,
)>,
broad_collision_pairs: Res<BroadCollisionPairs>,
mut collisions: ResMut<Collisions>,
narrow_phase_config: Res<NarrowPhaseConfig>,
) {
#[cfg(feature = "parallel")]
{
let pool = ComputeTaskPool::get();
let new_collisions = broad_collision_pairs
.0
.par_splat_map(pool, None, |chunks| {
let mut new_collisions: Vec<Contacts> = vec![];
for (entity1, entity2) in chunks {
if let Ok([bundle1, bundle2]) = bodies.get_many([*entity1, *entity2]) {
let (position1, accumulated_translation1, rotation1, collider1) = bundle1;
let (position2, accumulated_translation2, rotation2, collider2) = bundle2;
let position1 =
position1.0 + accumulated_translation1.copied().unwrap_or_default().0;
let position2 =
position2.0 + accumulated_translation2.copied().unwrap_or_default().0;
let previous_contact = collisions.get_internal().get(&(*entity1, *entity2));
let contacts = Contacts {
entity1: *entity1,
entity2: *entity2,
during_current_frame: true,
during_current_substep: true,
during_previous_frame: previous_contact
.map_or(false, |c| c.during_previous_frame),
manifolds: contact_query::contact_manifolds(
collider1,
position1,
*rotation1,
collider2,
position2,
*rotation2,
narrow_phase_config.prediction_distance,
),
};
if !contacts.manifolds.is_empty() {
new_collisions.push(contacts);
}
}
}
new_collisions
})
.into_iter()
.flatten();
collisions.extend(new_collisions);
}
#[cfg(not(feature = "parallel"))]
{
for (entity1, entity2) in broad_collision_pairs.0.iter() {
if let Ok([bundle1, bundle2]) = bodies.get_many([*entity1, *entity2]) {
let (position1, accumulated_translation1, rotation1, collider1) = bundle1;
let (position2, accumulated_translation2, rotation2, collider2) = bundle2;
let position1 =
position1.0 + accumulated_translation1.copied().unwrap_or_default().0;
let position2 =
position2.0 + accumulated_translation2.copied().unwrap_or_default().0;
let previous_contact = collisions.get_internal().get(&(*entity1, *entity2));
let contacts = Contacts {
entity1: *entity1,
entity2: *entity2,
during_current_frame: true,
during_current_substep: true,
during_previous_frame: previous_contact
.map_or(false, |c| c.during_previous_frame),
manifolds: contact_query::contact_manifolds(
collider1,
position1,
*rotation1,
collider2,
position2,
*rotation2,
narrow_phase_config.prediction_distance,
),
};
if !contacts.manifolds.is_empty() {
collisions.insert_collision_pair(contacts);
}
}
}
}
}
pub fn reset_collision_states(
mut collisions: ResMut<Collisions>,
query: Query<(Option<&RigidBody>, Has<Sleeping>)>,
) {
for contacts in collisions.get_internal_mut().values_mut() {
if let Ok([(rb1, sleeping1), (rb2, sleeping2)]) =
query.get_many([contacts.entity1, contacts.entity2])
{
let active1 = !rb1.map_or(false, |rb| rb.is_static()) && !sleeping1;
let active2 = !rb2.map_or(false, |rb| rb.is_static()) && !sleeping2;
if active1 || active2 {
contacts.during_previous_frame = true;
contacts.during_current_frame = false;
contacts.during_current_substep = false;
} else {
contacts.during_previous_frame = true;
contacts.during_current_frame = true;
}
} else {
contacts.during_current_frame = false;
}
}
}
pub fn reset_substep_collision_states(mut collisions: ResMut<Collisions>) {
for contacts in collisions.get_internal_mut().values_mut() {
contacts.during_current_substep = false;
}
}