Skip to content

Commit

Permalink
Add ColliderDisabled (#584)
Browse files Browse the repository at this point in the history
# Objective

Closes #436.
Follow-up to #536.

Avian already supports disabling rigid bodies (#536) and joints (#519). The missing piece is disabling colliders!

## Solution

Add a `ColliderDisabled` component for temporarily disabling collision detection for colliders and removing them from spatial queries.

For now, I decided *not* to prevent disabled colliders from contributing to mass properties of rigid bodies, since the primary purpose of the component is to temporarily disable collision detection, and I think it would be unexpected if it affected the behavior of the rigid body in any other way.
  • Loading branch information
Jondolf authored Dec 9, 2024
1 parent 114554b commit 5eb5722
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 31 deletions.
54 changes: 31 additions & 23 deletions src/collision/broad_phase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
use crate::prelude::*;
use bevy::{
ecs::entity::{EntityMapper, MapEntities},
ecs::{
entity::{EntityMapper, MapEntities},
system::lifetimeless::Read,
},
prelude::*,
};

Expand Down Expand Up @@ -109,13 +112,16 @@ impl MapEntities for AabbIntervals {
/// Updates [`AabbIntervals`] to keep them in sync with the [`ColliderAabb`]s.
#[allow(clippy::type_complexity)]
fn update_aabb_intervals(
aabbs: Query<(
&ColliderAabb,
Option<&ColliderParent>,
Option<&CollisionLayers>,
Has<AabbIntersections>,
Has<Sleeping>,
)>,
aabbs: Query<
(
&ColliderAabb,
Option<&ColliderParent>,
Option<&CollisionLayers>,
Has<AabbIntersections>,
Has<Sleeping>,
),
Without<ColliderDisabled>,
>,
rbs: Query<&RigidBody>,
mut intervals: ResMut<AabbIntervals>,
) {
Expand Down Expand Up @@ -145,25 +151,26 @@ fn update_aabb_intervals(
);
}

type AabbIntervalQueryData = (
Entity,
Option<Read<ColliderParent>>,
Read<ColliderAabb>,
Option<Read<RigidBody>>,
Option<Read<CollisionLayers>>,
Has<AabbIntersections>,
);

/// Adds new [`ColliderAabb`]s to [`AabbIntervals`].
#[allow(clippy::type_complexity)]
fn add_new_aabb_intervals(
aabbs: Query<
(
Entity,
Option<&ColliderParent>,
&ColliderAabb,
Option<&RigidBody>,
Option<&CollisionLayers>,
Has<AabbIntersections>,
),
Added<ColliderAabb>,
>,
added_aabbs: Query<AabbIntervalQueryData, (Added<ColliderAabb>, Without<ColliderDisabled>)>,
aabbs: Query<AabbIntervalQueryData>,
mut intervals: ResMut<AabbIntervals>,
mut re_enabled_colliders: RemovedComponents<ColliderDisabled>,
) {
let aabbs = aabbs
.iter()
.map(|(ent, parent, aabb, rb, layers, store_intersections)| {
let re_enabled_aabbs = aabbs.iter_many(re_enabled_colliders.read());
let aabbs = added_aabbs.iter().chain(re_enabled_aabbs).map(
|(ent, parent, aabb, rb, layers, store_intersections)| {
(
ent,
parent.map_or(ColliderParent(ent), |p| *p),
Expand All @@ -173,7 +180,8 @@ fn add_new_aabb_intervals(
rb.map_or(false, |rb| rb.is_static()),
store_intersections,
)
});
},
);
intervals.0.extend(aabbs);
}

Expand Down
45 changes: 45 additions & 0 deletions src/collision/collider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,51 @@ pub trait ScalableCollider: AnyCollider {
}
}

/// A marker component that indicates that a [collider](Collider) is disabled
/// and should not detect collisions or be included in spatial queries.
///
/// This is useful for temporarily disabling a collider without removing it from the world.
/// To re-enable the collider, simply remove the component.
///
/// Note that a disabled collider will still contribute to the mass properties of the rigid body
/// it is attached to. Set the [`Mass`] of the collider to zero to prevent this.
///
/// # Example
///
/// ```
#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
/// # use bevy::prelude::*;
/// #
/// #[derive(Component)]
/// pub struct Character;
///
/// /// Disables colliders for all rigid body characters, for example during cutscenes.
/// fn disable_character_colliders(
/// mut commands: Commands,
/// query: Query<Entity, (With<RigidBody>, With<Character>)>,
/// ) {
/// for entity in &query {
/// commands.entity(entity).insert(ColliderDisabled);
/// }
/// }
///
/// /// Enables colliders for all rigid body characters.
/// fn enable_character_colliders(
/// mut commands: Commands,
/// query: Query<Entity, (With<RigidBody>, With<Character>)>,
/// ) {
/// for entity in &query {
/// commands.entity(entity).remove::<ColliderDisabled>();
/// }
/// }
/// ```
#[derive(Reflect, Clone, Copy, Component, Debug, Default, PartialEq, Eq, From)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component, Default, PartialEq)]
pub struct ColliderDisabled;

/// A component that stores the `Entity` ID of the [`RigidBody`] that a [`Collider`] is attached to.
///
/// If the collider is a child of a rigid body, this points to the body's `Entity` ID.
Expand Down
2 changes: 1 addition & 1 deletion src/collision/narrow_phase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ fn generate_constraints<C: AnyCollider>(
#[derive(SystemParam)]
pub struct NarrowPhase<'w, 's, C: AnyCollider> {
parallel_commands: ParallelCommands<'w, 's>,
collider_query: Query<'w, 's, ColliderQuery<C>>,
collider_query: Query<'w, 's, ColliderQuery<C>, Without<ColliderDisabled>>,
body_query: Query<
'w,
's,
Expand Down
21 changes: 14 additions & 7 deletions src/dynamics/sleeping/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,11 @@ fn wake_on_collision_ended(
Without<Sleeping>,
),
>,
colliders: Query<(&ColliderParent, Ref<ColliderTransform>)>,
colliders: Query<(
&ColliderParent,
Ref<ColliderTransform>,
Has<ColliderDisabled>,
)>,
collisions: Res<Collisions>,
mut sleeping: Query<(Entity, &mut TimeSleeping, Has<Sleeping>)>,
) {
Expand All @@ -303,12 +307,15 @@ fn wake_on_collision_ended(
}
});
if colliding_entities.any(|other_entity| {
colliders.get(other_entity).is_ok_and(|(p, transform)| {
transform.is_changed()
|| bodies
.get(p.get())
.is_ok_and(|(pos, is_disabled)| is_disabled || pos.is_changed())
})
colliders
.get(other_entity)
.is_ok_and(|(p, transform, is_collider_disabled)| {
is_collider_disabled
|| transform.is_changed()
|| bodies
.get(p.get())
.is_ok_and(|(pos, is_rb_disabled)| is_rb_disabled || pos.is_changed())
})
}) {
if is_sleeping {
commands.entity(entity).remove::<Sleeping>();
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
//! - [Collision events](ContactReportingPlugin#collision-events)
//! - [Accessing, filtering and modifying collisions](Collisions)
//! - [Manual contact queries](contact_query)
//! - [Temporarily disabling a collider](ColliderDisabled)
//!
//! See the [`collision`] module for more details about collision detection and colliders in Avian.
//!
Expand Down
1 change: 1 addition & 0 deletions src/spatial_query/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub struct SpatialQuery<'w, 's> {
&'static Collider,
Option<&'static CollisionLayers>,
),
Without<ColliderDisabled>,
>,
pub(crate) added_colliders: Query<'w, 's, Entity, Added<Collider>>,
/// The [`SpatialQueryPipeline`].
Expand Down

0 comments on commit 5eb5722

Please sign in to comment.