From 3fd6b17207459ce0736dfe929e2333f234eac4fb Mon Sep 17 00:00:00 2001 From: ExoExo Date: Fri, 17 May 2024 15:11:20 +0200 Subject: [PATCH] add `IgnoredCollisions` component --- src/collision/broad_phase.rs | 23 +++++++++++ src/collision/collider/parry/mod.rs | 3 +- src/collision/layers.rs | 2 + src/collision/mod.rs | 6 +++ src/dynamics/rigid_body/mod.rs | 59 ++++++++++++++++++++++++++++- src/lib.rs | 1 + 6 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/collision/broad_phase.rs b/src/collision/broad_phase.rs index 4949435d..e5b75795 100644 --- a/src/collision/broad_phase.rs +++ b/src/collision/broad_phase.rs @@ -182,6 +182,7 @@ fn collect_collision_pairs( intervals: ResMut, mut broad_collision_pairs: ResMut, mut aabb_intersection_query: Query<&mut AabbIntersections>, + ignored_collisions: Query<&IgnoredCollisions>, ) { for mut intersections in &mut aabb_intersection_query { intersections.clear(); @@ -191,6 +192,7 @@ fn collect_collision_pairs( intervals, &mut broad_collision_pairs.0, &mut aabb_intersection_query, + ignored_collisions, ); } @@ -201,6 +203,7 @@ fn sweep_and_prune( mut intervals: ResMut, broad_collision_pairs: &mut Vec<(Entity, Entity)>, aabb_intersection_query: &mut Query<&mut AabbIntersections>, + ignored_collisions: Query<&IgnoredCollisions>, ) { // Sort bodies along the x-axis using insertion sort, a sorting algorithm great for sorting nearly sorted lists. insertion_sort(&mut intervals.0, |a, b| a.2.min.x > b.2.min.x); @@ -212,9 +215,29 @@ fn sweep_and_prune( for (i, (ent1, parent1, aabb1, layers1, store_intersections1, inactive1)) in intervals.0.iter().enumerate() { + let ent1_ignored_collisions = ignored_collisions.get(*ent1).ok(); for (ent2, parent2, aabb2, layers2, store_intersections2, inactive2) in intervals.0.iter().skip(i + 1) { + // Check ignored collisions of `ent1` + if ent1_ignored_collisions + .as_ref() + .map(|i| i.contains(ent2)) + .unwrap_or_default() + { + continue; + } + + // Check ignored collisions of `ent2` + let ent2_ignored_collisions = ignored_collisions.get(*ent2).ok(); + if ent2_ignored_collisions + .as_ref() + .map(|i| i.contains(ent1)) + .unwrap_or_default() + { + continue; + } + // x doesn't intersect; check this first so we can discard as soon as possible if aabb2.min.x > aabb1.max.x { break; diff --git a/src/collision/collider/parry/mod.rs b/src/collision/collider/parry/mod.rs index f208ac5d..11261661 100644 --- a/src/collision/collider/parry/mod.rs +++ b/src/collision/collider/parry/mod.rs @@ -262,7 +262,7 @@ impl From for parry::shape::TriMeshFlags { /// ``` /// /// Colliders can be further configured using various components like [`Friction`], [`Restitution`], -/// [`Sensor`], [`CollisionLayers`], and [`CollisionMargin`]. +/// [`Sensor`], [`IgnoredCollisions`], [`CollisionLayers`], and [`CollisionMargin`]. /// /// In addition, Avian automatically adds some other components for colliders, like the following: /// @@ -344,6 +344,7 @@ impl From for parry::shape::TriMeshFlags { /// - [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/layers.rs b/src/collision/layers.rs index 4e52cf2f..12c8ef44 100644 --- a/src/collision/layers.rs +++ b/src/collision/layers.rs @@ -257,6 +257,8 @@ impl Not for LayerMask { /// /// [bitmasks]: https://en.wikipedia.org/wiki/Mask_(computing) /// +/// See also [`IgnoredCollisions`](crate::components::IgnoredCollisions). +/// /// ## Creation /// /// Collision layers store memberships and filters using [`LayerMask`]s. A [`LayerMask`] can be created using diff --git a/src/collision/mod.rs b/src/collision/mod.rs index d26b1081..feb99791 100644 --- a/src/collision/mod.rs +++ b/src/collision/mod.rs @@ -68,6 +68,12 @@ use indexmap::IndexMap; /// The collisions can be accessed at any time, but modifications to contacts should be performed /// in the [`PostProcessCollisions`] schedule. Otherwise, the physics solver will use the old contact data. /// +/// ### 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. +/// /// ### Filtering and removing collisions /// /// The following methods can be used for filtering or removing existing collisions: diff --git a/src/dynamics/rigid_body/mod.rs b/src/dynamics/rigid_body/mod.rs index c7b45cbc..a8f9f582 100644 --- a/src/dynamics/rigid_body/mod.rs +++ b/src/dynamics/rigid_body/mod.rs @@ -16,7 +16,7 @@ pub(crate) use forces::FloatZero; pub(crate) use forces::Torque; use crate::prelude::*; -use bevy::prelude::*; +use bevy::{prelude::*, utils::HashSet}; use derive_more::From; /// A non-deformable body used for the simulation of most physics objects. @@ -826,6 +826,63 @@ pub struct AngularDamping(pub Scalar); #[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 bevy_xpbd_2d::prelude::*; +/// # #[cfg(feature = "3d")] +/// use bevy_xpbd_3d::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)) + } +} + #[cfg(test)] mod tests { use crate::prelude::*; diff --git a/src/lib.rs b/src/lib.rs index d476ef7f..b6ec1e5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,6 +140,7 @@ //! - [Creation](Collider#creation) //! - [Density](ColliderDensity) //! - [Friction] and [restitution](Restitution) (bounciness) +//! - [Ignoring collisions](IgnoredCollisions) //! - [Collision layers](CollisionLayers) //! - [Sensors](Sensor) #![cfg_attr(