mod system_param;
use system_param::ContactStatusBits;
pub use system_param::NarrowPhase;
#[cfg(feature = "parallel")]
use system_param::ThreadLocalContactStatusBits;
use core::marker::PhantomData;
use crate::{
dynamics::solver::{
ContactConstraints,
constraint_graph::ConstraintGraph,
islands::{BodyIslandNode, PhysicsIslands},
joint_graph::JointGraph,
},
prelude::*,
};
use bevy::{
ecs::{
entity_disabling::Disabled,
intern::Interned,
schedule::ScheduleLabel,
system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState},
},
prelude::*,
};
use super::{CollisionDiagnostics, contact_types::ContactEdgeFlags};
pub struct NarrowPhasePlugin<C: AnyCollider, H: CollisionHooks = ()> {
schedule: Interned<dyn ScheduleLabel>,
generate_constraints: bool,
_phantom: PhantomData<(C, H)>,
}
impl<C: AnyCollider, H: CollisionHooks> NarrowPhasePlugin<C, H> {
pub fn new(schedule: impl ScheduleLabel, generate_constraints: bool) -> Self {
Self {
schedule: schedule.intern(),
generate_constraints,
_phantom: PhantomData,
}
}
}
impl<C: AnyCollider, H: CollisionHooks> Default for NarrowPhasePlugin<C, H> {
fn default() -> Self {
Self::new(PhysicsSchedule, true)
}
}
#[derive(Resource, Default)]
struct NarrowPhaseInitialized;
impl<C: AnyCollider, H: CollisionHooks + 'static> Plugin for NarrowPhasePlugin<C, H>
where
for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
{
fn build(&self, app: &mut App) {
let already_initialized = app.world().is_resource_added::<NarrowPhaseInitialized>();
app.init_resource::<NarrowPhaseConfig>()
.init_resource::<ContactGraph>()
.init_resource::<ConstraintGraph>()
.init_resource::<JointGraph>()
.init_resource::<ContactStatusBits>()
.init_resource::<DefaultFriction>()
.init_resource::<DefaultRestitution>();
#[cfg(feature = "parallel")]
app.init_resource::<ThreadLocalContactStatusBits>();
app.add_message::<CollisionStart>()
.add_message::<CollisionEnd>();
if self.generate_constraints {
app.init_resource::<ContactConstraints>();
}
app.configure_sets(
self.schedule,
(
NarrowPhaseSystems::First,
NarrowPhaseSystems::Update,
NarrowPhaseSystems::Last,
)
.chain()
.in_set(PhysicsStepSystems::NarrowPhase),
);
app.configure_sets(
self.schedule,
CollisionEventSystems.in_set(PhysicsStepSystems::Finalize),
);
app.add_systems(
self.schedule,
update_narrow_phase::<C, H>
.in_set(NarrowPhaseSystems::Update)
.ambiguous_with_all(),
);
if !already_initialized {
app.add_observer(remove_collider_on::<Add, (Disabled, ColliderDisabled)>);
app.add_observer(remove_collider_on::<Remove, ColliderMarker>);
app.add_observer(on_add_sensor);
app.add_observer(on_remove_sensor);
app.add_observer(on_body_remove_rigid_body_disabled);
app.add_observer(on_disable_body);
app.add_observer(remove_body_on::<Insert, RigidBody>);
app.add_observer(remove_body_on::<Remove, RigidBody>);
app.add_systems(
self.schedule,
trigger_collision_events
.in_set(CollisionEventSystems)
.ambiguous_with(PhysicsStepSystems::Finalize),
);
}
app.init_resource::<NarrowPhaseInitialized>();
}
fn finish(&self, app: &mut App) {
app.register_physics_diagnostics::<CollisionDiagnostics>();
}
}
#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CollisionEventSystems;
#[derive(Resource, Reflect, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Resource, PartialEq)]
pub struct NarrowPhaseConfig {
pub default_speculative_margin: Scalar,
pub contact_tolerance: Scalar,
pub match_contacts: bool,
}
impl Default for NarrowPhaseConfig {
fn default() -> Self {
Self {
default_speculative_margin: Scalar::MAX,
contact_tolerance: 0.005,
match_contacts: true,
}
}
}
#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NarrowPhaseSystems {
First,
Update,
Last,
}
#[deprecated(since = "0.4.0", note = "Renamed to `NarrowPhaseSystems`")]
pub type NarrowPhaseSet = NarrowPhaseSystems;
fn update_narrow_phase<C: AnyCollider, H: CollisionHooks + 'static>(
mut narrow_phase: NarrowPhase<C>,
mut collision_started_writer: MessageWriter<CollisionStart>,
mut collision_ended_writer: MessageWriter<CollisionEnd>,
time: Res<Time>,
hooks: StaticSystemParam<H>,
context: StaticSystemParam<C::Context>,
mut commands: ParallelCommands,
mut diagnostics: ResMut<CollisionDiagnostics>,
) where
for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks,
{
let start = crate::utils::Instant::now();
narrow_phase.update::<H>(
&mut collision_started_writer,
&mut collision_ended_writer,
time.delta_seconds_adjusted(),
&hooks,
&context,
&mut commands,
);
diagnostics.narrow_phase = start.elapsed();
diagnostics.contact_count = narrow_phase.contact_graph.edges.edge_count() as u32;
}
#[derive(SystemParam)]
struct TriggerCollisionEventsContext<'w, 's> {
query: Query<'w, 's, Has<CollisionEventsEnabled>>,
started: MessageReader<'w, 's, CollisionStart>,
ended: MessageReader<'w, 's, CollisionEnd>,
}
fn trigger_collision_events(
world: &mut World,
state: &mut SystemState<TriggerCollisionEventsContext>,
mut started: Local<Vec<CollisionStart>>,
mut ended: Local<Vec<CollisionEnd>>,
) {
let mut state = state.get_mut(world);
for event in state.started.read() {
let Ok([events_enabled1, events_enabled2]) =
state.query.get_many([event.collider1, event.collider2])
else {
continue;
};
if events_enabled1 {
started.push(CollisionStart {
collider1: event.collider1,
collider2: event.collider2,
body1: event.body1,
body2: event.body2,
});
}
if events_enabled2 {
started.push(CollisionStart {
collider1: event.collider2,
collider2: event.collider1,
body1: event.body2,
body2: event.body1,
});
}
}
for event in state.ended.read() {
let Ok([events_enabled1, events_enabled2]) =
state.query.get_many([event.collider1, event.collider2])
else {
continue;
};
if events_enabled1 {
ended.push(CollisionEnd {
collider1: event.collider1,
collider2: event.collider2,
body1: event.body1,
body2: event.body2,
});
}
if events_enabled2 {
ended.push(CollisionEnd {
collider1: event.collider2,
collider2: event.collider1,
body1: event.body2,
body2: event.body1,
});
}
}
started.drain(..).for_each(|event| {
world.trigger(event);
});
ended.drain(..).for_each(|event| {
world.trigger(event);
});
}
fn remove_collider(
entity: Entity,
contact_graph: &mut ContactGraph,
joint_graph: &JointGraph,
constraint_graph: &mut ConstraintGraph,
mut islands: Option<&mut PhysicsIslands>,
body_islands: &mut Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
colliding_entities_query: &mut Query<
&mut CollidingEntities,
Or<(With<Disabled>, Without<Disabled>)>,
>,
message_writer: &mut MessageWriter<CollisionEnd>,
) {
contact_graph.remove_collider_with(entity, |contact_graph, contact_id| {
let contact_edge = contact_graph.edge_weight(contact_id.into()).unwrap();
if !contact_edge.flags.contains(ContactEdgeFlags::TOUCHING) {
return;
}
if contact_edge
.flags
.contains(ContactEdgeFlags::CONTACT_EVENTS)
{
message_writer.write(CollisionEnd {
collider1: contact_edge.collider1,
collider2: contact_edge.collider2,
body1: contact_edge.body1,
body2: contact_edge.body2,
});
}
let other_entity = if contact_edge.collider1 == entity {
contact_edge.collider2
} else {
contact_edge.collider1
};
if let Ok(mut colliding_entities) = colliding_entities_query.get_mut(other_entity) {
colliding_entities.remove(&entity);
}
let has_island = contact_edge.island.is_some();
if let (Some(body1), Some(body2)) = (contact_edge.body1, contact_edge.body2) {
for _ in 0..contact_edge.constraint_handles.len() {
constraint_graph.pop_manifold(contact_graph, contact_id, body1, body2);
}
}
if has_island && let Some(ref mut islands) = islands {
islands.remove_contact(contact_id, body_islands, contact_graph, joint_graph);
}
});
}
fn remove_body_on<E: EntityEvent, B: Bundle>(
trigger: On<E, B>,
body_collider_query: Query<&RigidBodyColliders>,
mut colliding_entities_query: Query<
&mut CollidingEntities,
Or<(With<Disabled>, Without<Disabled>)>,
>,
mut message_writer: MessageWriter<CollisionEnd>,
mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
mut islands: Option<ResMut<PhysicsIslands>>,
mut constraint_graph: ResMut<ConstraintGraph>,
mut contact_graph: ResMut<ContactGraph>,
joint_graph: ResMut<JointGraph>,
mut commands: Commands,
) {
let Ok(colliders) = body_collider_query.get(trigger.event_target()) else {
return;
};
if let Ok(body_island) = body_islands.get_mut(trigger.event_target()) {
commands.queue(WakeIslands(vec![body_island.island_id]));
}
for collider in colliders {
remove_collider(
collider,
&mut contact_graph,
&joint_graph,
&mut constraint_graph,
islands.as_deref_mut(),
&mut body_islands,
&mut colliding_entities_query,
&mut message_writer,
);
}
}
fn remove_collider_on<E: EntityEvent, B: Bundle>(
trigger: On<E, B>,
mut contact_graph: ResMut<ContactGraph>,
joint_graph: ResMut<JointGraph>,
mut constraint_graph: ResMut<ConstraintGraph>,
mut islands: Option<ResMut<PhysicsIslands>>,
mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
mut query: Query<&mut CollidingEntities, Or<(With<Disabled>, Without<Disabled>)>>,
collider_of: Query<&ColliderOf, Or<(With<Disabled>, Without<Disabled>)>>,
mut message_writer: MessageWriter<CollisionEnd>,
mut commands: Commands,
) {
let entity = trigger.event_target();
let body1 = collider_of
.get(entity)
.map(|&ColliderOf { body }| body)
.ok();
if let Some(body) = body1
&& let Ok(body_island) = body_islands.get_mut(body)
{
commands.queue(WakeIslands(vec![body_island.island_id]));
}
remove_collider(
entity,
&mut contact_graph,
&joint_graph,
&mut constraint_graph,
islands.as_deref_mut(),
&mut body_islands,
&mut query,
&mut message_writer,
);
}
fn on_body_remove_rigid_body_disabled(
trigger: On<Add, BodyIslandNode>,
body_collider_query: Query<&RigidBodyColliders>,
mut constraint_graph: ResMut<ConstraintGraph>,
mut contact_graph: ResMut<ContactGraph>,
joint_graph: ResMut<JointGraph>,
mut islands: Option<ResMut<PhysicsIslands>>,
mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
mut colliding_entities_query: Query<
&mut CollidingEntities,
Or<(With<Disabled>, Without<Disabled>)>,
>,
mut message_writer: MessageWriter<CollisionEnd>,
) {
let Ok(colliders) = body_collider_query.get(trigger.entity) else {
return;
};
for collider in colliders {
remove_collider(
collider,
&mut contact_graph,
&joint_graph,
&mut constraint_graph,
islands.as_deref_mut(),
&mut body_islands,
&mut colliding_entities_query,
&mut message_writer,
);
}
}
fn on_disable_body(
trigger: On<Add, (Disabled, RigidBodyDisabled)>,
body_collider_query: Query<&RigidBodyColliders, Or<(With<Disabled>, Without<Disabled>)>>,
mut constraint_graph: ResMut<ConstraintGraph>,
mut contact_graph: ResMut<ContactGraph>,
joint_graph: Res<JointGraph>,
mut islands: Option<ResMut<PhysicsIslands>>,
mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
mut colliding_entities_query: Query<
&mut CollidingEntities,
Or<(With<Disabled>, Without<Disabled>)>,
>,
mut message_writer: MessageWriter<CollisionEnd>,
) {
let Ok(colliders) = body_collider_query.get(trigger.entity) else {
return;
};
for collider in colliders {
remove_collider(
collider,
&mut contact_graph,
&joint_graph,
&mut constraint_graph,
islands.as_deref_mut(),
&mut body_islands,
&mut colliding_entities_query,
&mut message_writer,
);
}
}
fn on_add_sensor(
trigger: On<Add, Sensor>,
mut constraint_graph: ResMut<ConstraintGraph>,
mut contact_graph: ResMut<ContactGraph>,
joint_graph: Res<JointGraph>,
mut islands: Option<ResMut<PhysicsIslands>>,
mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
mut colliding_entities_query: Query<
&mut CollidingEntities,
Or<(With<Disabled>, Without<Disabled>)>,
>,
mut message_writer: MessageWriter<CollisionEnd>,
) {
remove_collider(
trigger.entity,
&mut contact_graph,
&joint_graph,
&mut constraint_graph,
islands.as_deref_mut(),
&mut body_islands,
&mut colliding_entities_query,
&mut message_writer,
);
}
fn on_remove_sensor(
trigger: On<Remove, Sensor>,
mut constraint_graph: ResMut<ConstraintGraph>,
mut contact_graph: ResMut<ContactGraph>,
joint_graph: ResMut<JointGraph>,
mut islands: Option<ResMut<PhysicsIslands>>,
mut body_islands: Query<&mut BodyIslandNode, Or<(With<Disabled>, Without<Disabled>)>>,
mut colliding_entities_query: Query<
&mut CollidingEntities,
Or<(With<Disabled>, Without<Disabled>)>,
>,
mut message_writer: MessageWriter<CollisionEnd>,
) {
remove_collider(
trigger.entity,
&mut contact_graph,
&joint_graph,
&mut constraint_graph,
islands.as_deref_mut(),
&mut body_islands,
&mut colliding_entities_query,
&mut message_writer,
);
}