From 538eb1c83fdfdb204d5544429a61a66beded7376 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 6072e8a2..1e1213df 100644 --- a/src/collision/broad_phase.rs +++ b/src/collision/broad_phase.rs @@ -190,6 +190,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(); @@ -199,6 +200,7 @@ fn collect_collision_pairs( intervals, &mut broad_collision_pairs.0, &mut aabb_intersection_query, + ignored_collisions, ); } @@ -209,6 +211,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); @@ -220,9 +223,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 3a4a56ff..a1aa5f34 100644 --- a/src/collision/collider/parry/mod.rs +++ b/src/collision/collider/parry/mod.rs @@ -263,7 +263,7 @@ impl From for parry::shape::TriMeshFlags { /// ``` /// /// 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. @@ -337,6 +337,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 b8d56266..31deb657 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::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/mod.rs b/src/collision/mod.rs index c4d32d5e..8d554e3e 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 31c77e06..9094776d 100644 --- a/src/dynamics/rigid_body/mod.rs +++ b/src/dynamics/rigid_body/mod.rs @@ -20,7 +20,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. @@ -684,3 +684,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 bfba69d3..503904df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,6 +142,7 @@ //! - [Creation](Collider#creation) //! - [Density](ColliderDensity) //! - [Friction] and [restitution](Restitution) (bounciness) +//! - [Ignoring collisions](IgnoredCollisions) //! - [Collision layers](CollisionLayers) //! - [Sensors](Sensor) #![cfg_attr(