Skip to content

Commit 11e64a1

Browse files
morgenthumJondolf
andauthored
Introduce PhysicsPickingFilter (#632)
Co-authored-by: Joona Aalto <[email protected]>
1 parent fa9b898 commit 11e64a1

File tree

3 files changed

+72
-20
lines changed

3 files changed

+72
-20
lines changed

crates/avian3d/examples/picking.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//! By default, the [`PhysicsPickingPlugin`] will test intersections with the pointer against all colliders.
44
//! If you want physics picking to be opt-in, you can set [`PhysicsPickingSettings::require_markers`] to `true`
55
//! and add a [`PhysicsPickable`] component to the desired camera and target entities.
6+
//!
7+
//! Cameras can further filter which entities are pickable with the [`PhysicsPickingFilter`] component.
68
79
use std::f32::consts::PI;
810

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,9 @@ pub mod prelude {
498498
#[cfg(feature = "debug-plugin")]
499499
pub use crate::debug_render::*;
500500
#[cfg(feature = "bevy_picking")]
501-
pub use crate::picking::{PhysicsPickable, PhysicsPickingPlugin, PhysicsPickingSettings};
501+
pub use crate::picking::{
502+
PhysicsPickable, PhysicsPickingFilter, PhysicsPickingPlugin, PhysicsPickingSettings,
503+
};
502504
#[cfg(feature = "default-collider")]
503505
pub(crate) use crate::position::RotationValue;
504506
pub use crate::{

src/picking/mod.rs

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
//!
77
//! To make physics picking entirely opt-in, set [`PhysicsPickingSettings::require_markers`]
88
//! to `true` and add a [`PhysicsPickable`] component to the desired camera and target entities.
9+
//!
10+
//! Cameras can further filter which entities are pickable with the [`PhysicsPickingFilter`] component.
911
#![cfg_attr(
1012
feature = "3d",
1113
doc = "
@@ -15,12 +17,12 @@ Note that in 3D, only the closest intersection will be reported."
1517

1618
use crate::prelude::*;
1719
use bevy::{
20+
ecs::entity::EntityHashSet,
1821
picking::{
1922
backend::{ray::RayMap, HitData, PointerHits},
2023
PickSet,
2124
},
2225
prelude::*,
23-
render::view::RenderLayers,
2426
};
2527

2628
/// Adds the physics picking backend to your app, enabling picking for [colliders](Collider).
@@ -31,15 +33,19 @@ impl Plugin for PhysicsPickingPlugin {
3133
fn build(&self, app: &mut App) {
3234
app.init_resource::<PhysicsPickingSettings>()
3335
.add_systems(PreUpdate, update_hits.in_set(PickSet::Backend))
34-
.register_type::<(PhysicsPickingSettings, PhysicsPickable)>();
36+
.register_type::<(
37+
PhysicsPickingSettings,
38+
PhysicsPickable,
39+
PhysicsPickingFilter,
40+
)>();
3541
}
3642
}
3743

3844
/// Runtime settings for the [`PhysicsPickingPlugin`].
3945
#[derive(Resource, Default, Reflect)]
4046
#[reflect(Resource, Default)]
4147
pub struct PhysicsPickingSettings {
42-
/// When set to `true` picking will only happen between cameras and entities marked with
48+
/// When set to `true`, picking will only happen between cameras and entities marked with
4349
/// [`PhysicsPickable`]. `false` by default.
4450
///
4551
/// This setting is provided to give you fine-grained control over which cameras and entities
@@ -53,48 +59,94 @@ pub struct PhysicsPickingSettings {
5359
#[reflect(Component, Default)]
5460
pub struct PhysicsPickable;
5561

62+
/// An optional component with a [`SpatialQueryFilter`] to determine
63+
/// which physics entities a camera considers for [physics picking](crate::picking).
64+
///
65+
/// If not present on a camera, picking will consider all colliders.
66+
#[derive(Component, Clone, Debug, Default, Reflect)]
67+
#[reflect(Component, Debug, Default)]
68+
pub struct PhysicsPickingFilter(pub SpatialQueryFilter);
69+
70+
impl PhysicsPickingFilter {
71+
/// Creates a new [`PhysicsPickingFilter`] with the given [`LayerMask`] determining
72+
/// which [collision layers] will be pickable.
73+
///
74+
/// [collision layers]: CollisionLayers
75+
/// [spatial query]: crate::spatial_query
76+
pub fn from_mask(mask: impl Into<LayerMask>) -> Self {
77+
Self(SpatialQueryFilter::from_mask(mask))
78+
}
79+
80+
/// Creates a new [`PhysicsPickingFilter`] with the given entities excluded from physics picking.
81+
///
82+
/// [spatial query]: crate::spatial_query
83+
pub fn from_excluded_entities(entities: impl IntoIterator<Item = Entity>) -> Self {
84+
Self(SpatialQueryFilter::from_excluded_entities(entities))
85+
}
86+
87+
/// Sets the [`LayerMask`] of the filter configuration. Only colliders with the corresponding
88+
/// [collision layer memberships] will be pickable.
89+
///
90+
/// [collision layer memberships]: CollisionLayers
91+
/// [spatial query]: crate::spatial_query
92+
pub fn with_mask(mut self, masks: impl Into<LayerMask>) -> Self {
93+
self.0.mask = masks.into();
94+
self
95+
}
96+
97+
/// Excludes the given entities from physics picking.
98+
pub fn with_excluded_entities(mut self, entities: impl IntoIterator<Item = Entity>) -> Self {
99+
self.0.excluded_entities = EntityHashSet::from_iter(entities);
100+
self
101+
}
102+
}
103+
104+
// Store a const reference to the default filter to avoid unnecessary allocations.
105+
const DEFAULT_FILTER_REF: &PhysicsPickingFilter =
106+
&PhysicsPickingFilter(SpatialQueryFilter::DEFAULT);
107+
56108
/// Queries for collider intersections with pointers using [`PhysicsPickingSettings`] and sends [`PointerHits`] events.
57109
pub fn update_hits(
58-
picking_cameras: Query<(&Camera, Option<&PhysicsPickable>, Option<&RenderLayers>)>,
110+
picking_cameras: Query<(
111+
&Camera,
112+
Option<&PhysicsPickingFilter>,
113+
Option<&PhysicsPickable>,
114+
)>,
59115
ray_map: Res<RayMap>,
60116
pickables: Query<&PickingBehavior>,
61117
marked_targets: Query<&PhysicsPickable>,
62-
layers: Query<&RenderLayers>,
63118
backend_settings: Res<PhysicsPickingSettings>,
64119
spatial_query: SpatialQuery,
65120
mut output_events: EventWriter<PointerHits>,
66121
) {
67122
for (&ray_id, &ray) in ray_map.map().iter() {
68-
let Ok((camera, cam_pickable, cam_layers)) = picking_cameras.get(ray_id.camera) else {
123+
let Ok((camera, picking_filter, cam_pickable)) = picking_cameras.get(ray_id.camera) else {
69124
continue;
70125
};
126+
71127
if backend_settings.require_markers && cam_pickable.is_none() || !camera.is_active {
72128
continue;
73129
}
74130

75-
let cam_layers = cam_layers.unwrap_or_default();
131+
let filter = picking_filter.unwrap_or(DEFAULT_FILTER_REF);
76132

77133
#[cfg(feature = "2d")]
78134
{
79135
let mut hits: Vec<(Entity, HitData)> = vec![];
80136

81137
spatial_query.point_intersections_callback(
82138
ray.origin.truncate().adjust_precision(),
83-
&SpatialQueryFilter::default(),
139+
&filter.0,
84140
|entity| {
85141
let marker_requirement =
86142
!backend_settings.require_markers || marked_targets.get(entity).is_ok();
87143

88-
// Other entities missing render layers are on the default layer 0
89-
let entity_layers = layers.get(entity).unwrap_or_default();
90-
let render_layers_match = cam_layers.intersects(entity_layers);
91-
92144
let is_pickable = pickables
93145
.get(entity)
94146
.map(|p| *p != PickingBehavior::IGNORE)
95147
.unwrap_or(true);
96148

97-
if marker_requirement && render_layers_match && is_pickable {
149+
if marker_requirement && is_pickable {
98150
hits.push((
99151
entity,
100152
HitData::new(ray_id.camera, 0.0, Some(ray.origin.f32()), None),
@@ -115,21 +167,17 @@ pub fn update_hits(
115167
ray.direction,
116168
Scalar::MAX,
117169
true,
118-
&SpatialQueryFilter::default(),
170+
&filter.0,
119171
&|entity| {
120172
let marker_requirement =
121173
!backend_settings.require_markers || marked_targets.get(entity).is_ok();
122174

123-
// Other entities missing render layers are on the default layer 0
124-
let entity_layers = layers.get(entity).unwrap_or_default();
125-
let render_layers_match = cam_layers.intersects(entity_layers);
126-
127175
let is_pickable = pickables
128176
.get(entity)
129177
.map(|p| *p != PickingBehavior::IGNORE)
130178
.unwrap_or(true);
131179

132-
marker_requirement && render_layers_match && is_pickable
180+
marker_requirement && is_pickable
133181
},
134182
)
135183
.map(|ray_hit_data| {

0 commit comments

Comments
 (0)