diff --git a/src/collision/broad_phase/bvh_broad_phase.rs b/src/collision/broad_phase/bvh_broad_phase.rs index 188aab04f..04620df94 100644 --- a/src/collision/broad_phase/bvh_broad_phase.rs +++ b/src/collision/broad_phase/bvh_broad_phase.rs @@ -55,6 +55,7 @@ fn collect_collision_pairs( mut contact_graph: ResMut, joint_graph: Res, mut diagnostics: ResMut, + ignored_collisions: Query<&IgnoredCollisions>, ) where for<'w, 's> SystemParamItem<'w, 's, H>: CollisionHooks, { @@ -96,6 +97,7 @@ fn collect_collision_pairs( &contact_graph, &joint_graph, &mut pairs, + ignored_collisions, ); // Query kinematic tree. @@ -112,6 +114,7 @@ fn collect_collision_pairs( &contact_graph, &joint_graph, &mut pairs, + ignored_collisions, ); // Skip static-static body collisions unless sensors or standalone colliders are involved. @@ -130,6 +133,7 @@ fn collect_collision_pairs( &contact_graph, &joint_graph, &mut pairs, + ignored_collisions, ); } @@ -147,6 +151,7 @@ fn collect_collision_pairs( &contact_graph, &joint_graph, &mut pairs, + ignored_collisions, ); } }); @@ -211,6 +216,7 @@ fn query_tree( contact_graph: &ContactGraph, joint_graph: &JointGraph, pairs: &mut Vec<(ColliderTreeProxyKey, ColliderTreeProxyKey)>, + ignored_collisions: Query<&IgnoredCollisions>, ) { tree.bvh.aabb_traverse(proxy1.aabb, |bvh, node_index| { let node = &bvh.nodes[node_index as usize]; @@ -256,6 +262,26 @@ fn query_tree( let entity1 = proxy1.collider; let entity2 = proxy2.collider; + let ent1_ignored_collisions = ignored_collisions.get(entity1).ok(); + // Check ignored collisions of `ent1` + if ent1_ignored_collisions + .as_ref() + .map(|i| i.contains(&entity2)) + .unwrap_or_default() + { + continue; + } + + // Check ignored collisions of `ent2` + let ent2_ignored_collisions = ignored_collisions.get(entity2).ok(); + if ent2_ignored_collisions + .as_ref() + .map(|i| i.contains(&entity1)) + .unwrap_or_default() + { + continue; + } + // Avoid duplicate pairs. let pair_key = PairKey::new(entity1.index_u32(), entity2.index_u32()); if contact_graph.contains_key(&pair_key) { diff --git a/src/collision/collider/layers.rs b/src/collision/collider/layers.rs index ee06185f2..5970219a4 100644 --- a/src/collision/collider/layers.rs +++ b/src/collision/collider/layers.rs @@ -257,6 +257,8 @@ impl Not for LayerMask { /// /// [bitmasks]: https://en.wikipedia.org/wiki/Mask_(computing) /// +/// See also [`IgnoredCollisions`](crate::dynamics::rigid_body::IgnoredCollisions). +/// /// # Creation /// /// Collision layers store memberships and filters using [`LayerMask`]s. A [`LayerMask`] can be created using diff --git a/src/collision/collider/parry/mod.rs b/src/collision/collider/parry/mod.rs index a55f7bee1..a51dd91ba 100644 --- a/src/collision/collider/parry/mod.rs +++ b/src/collision/collider/parry/mod.rs @@ -269,7 +269,7 @@ pub type TrimeshBuilderError = parry::shape::TriMeshBuilderError; /// ``` /// /// Colliders can be further configured using various components like [`Friction`], [`Restitution`], -/// [`Sensor`], [`CollisionLayers`], [`CollisionMargin`], and [`ColliderDensity`]. +/// [`Sensor`], [`IgnoredCollisions`], [`CollisionLayers`], [`CollisionMargin`], and [`ColliderDensity`]. /// /// If you need to specify the shape of the collider statically, use [`ColliderConstructor`] and build your collider /// with the [`Collider::try_from_constructor`] method. @@ -331,6 +331,7 @@ pub type TrimeshBuilderError = parry::shape::TriMeshBuilderError; /// - [Rigid bodies](RigidBody) /// - [Density](ColliderDensity) /// - [Friction] and [restitution](Restitution) (bounciness) +/// - [Ignoring collisions](IgnoredCollisions) /// - [Collision layers](CollisionLayers) /// - [Sensors](Sensor) /// - [Collision margins for adding extra thickness to colliders](CollisionMargin) diff --git a/src/collision/mod.rs b/src/collision/mod.rs index e0f4ca71a..09987964f 100644 --- a/src/collision/mod.rs +++ b/src/collision/mod.rs @@ -52,6 +52,12 @@ //! [`Event`]: bevy::ecs::event::Event //! [observer]: bevy::ecs::observer::Observer //! +//! ## Ignoring collisions +//! +//! You can attach an [`IgnoredCollisions`] component to an entity with a +//! [`Collider`] to completely avoid collision detection between the entity and +//! the entities contained within the [`IgnoredCollisions`] component. +//! //! # Contact Filtering and Modification //! //! Some advanced contact scenarios may need to filter or modify contacts diff --git a/src/dynamics/rigid_body/mod.rs b/src/dynamics/rigid_body/mod.rs index 1810fb785..28a455c3c 100644 --- a/src/dynamics/rigid_body/mod.rs +++ b/src/dynamics/rigid_body/mod.rs @@ -24,6 +24,7 @@ use crate::{ }; use bevy::{ ecs::{lifecycle::HookContext, world::DeferredWorld}, + platform::collections::HashSet, prelude::*, }; use derive_more::From; @@ -660,3 +661,60 @@ pub struct AngularDamping(pub Scalar); #[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))] #[reflect(Debug, Component, Default, PartialEq)] pub struct Dominance(pub i8); + +/// A component containing a set of entities for which any collisions with the +/// owning entity will be ignored. +/// +/// ## Example +/// +/// ``` +/// use bevy::prelude::*; +/// # #[cfg(feature = "2d")] +/// # use avian2d::prelude::*; +/// # #[cfg(feature = "3d")] +/// use avian3d::prelude::*; +/// +/// fn setup(mut commands: Commands) { +/// // Spawn an entity with a collider +#[cfg_attr( + feature = "2d", + doc = " let ent1 = commands", + doc = " .spawn((RigidBody::Dynamic, Collider::circle(0.5)))", + doc = " .id();" +)] +#[cfg_attr( + feature = "3d", + doc = " let ent1 = commands", + doc = " .spawn((RigidBody::Dynamic, Collider::sphere(0.5)))", + doc = " .id();" +)] +/// +/// // Spawn another entity with a collider and configure it to avoid collisions with the first entity. +#[cfg_attr( + feature = "2d", + doc = " let ent1 = commands.spawn((", + doc = " RigidBody::Dynamic,", + doc = " Collider::circle(0.5),", + doc = " IgnoredCollisions::from_iter([ent1]),", + doc = "));" +)] +#[cfg_attr( + feature = "3d", + doc = " let ent1 = commands.spawn((", + doc = " RigidBody::Dynamic,", + doc = " Collider::sphere(0.5),", + doc = " IgnoredCollisions::from_iter([ent1]),", + doc = " ));" +)] +/// } +/// ``` +/// +/// See also [`CollisionLayers`]. +#[derive(Component, Clone, Debug, Default, Deref, DerefMut)] +pub struct IgnoredCollisions(pub HashSet); + +impl FromIterator for IgnoredCollisions { + fn from_iter>(iter: T) -> Self { + Self(HashSet::from_iter(iter)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 98d37ca81..fbbf90263 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -150,6 +150,7 @@ //! - [Creation](Collider#creation) //! - [Density](ColliderDensity) //! - [Friction] and [restitution](Restitution) (bounciness) +//! - [Ignoring collisions](IgnoredCollisions) //! - [Collision layers](CollisionLayers) //! - [Sensors](Sensor) #![cfg_attr(