Skip to main content

bevy_picking/mesh_picking/
mod.rs

1//! A [mesh ray casting](ray_cast) backend for [`bevy_picking`](crate).
2//!
3//! By default, all meshes that have [`bevy_asset::RenderAssetUsages::MAIN_WORLD`] are pickable.
4//! Picking can be disabled for individual entities by adding [`Pickable::IGNORE`].
5//!
6//! To make mesh picking entirely opt-in, set [`MeshPickingSettings::require_markers`]
7//! to `true` and add [`MeshPickingCamera`] and [`Pickable`] components to the desired camera and
8//! target entities.
9//!
10//! To manually perform mesh ray casts independent of picking, use the [`MeshRayCast`] system parameter.
11//!
12//! ## Implementation Notes
13//!
14//! - The `position` reported in `HitData` is in world space. The `normal` is a vector pointing
15//!   away from the face, it is not guaranteed to be normalized for scaled meshes.
16
17pub mod ray_cast;
18
19use crate::{
20    backend::{ray::RayMap, HitData, PointerHits},
21    prelude::*,
22    PickingSystems,
23};
24use bevy_app::prelude::*;
25use bevy_camera::{visibility::RenderLayers, Camera};
26use bevy_ecs::prelude::*;
27use bevy_reflect::prelude::*;
28use ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastVisibility};
29
30/// An optional component that marks cameras that should be used in the [`MeshPickingPlugin`].
31///
32/// Only needed if [`MeshPickingSettings::require_markers`] is set to `true`, and ignored otherwise.
33#[derive(Debug, Clone, Default, Component, Reflect)]
34#[reflect(Debug, Default, Component)]
35pub struct MeshPickingCamera;
36
37/// Runtime settings for the [`MeshPickingPlugin`].
38#[derive(Resource, Reflect)]
39#[reflect(Resource, Default)]
40pub struct MeshPickingSettings {
41    /// When set to `true` ray casting will only consider cameras marked with
42    /// [`MeshPickingCamera`] and entities marked with [`Pickable`]. `false` by default.
43    ///
44    /// This setting is provided to give you fine-grained control over which cameras and entities
45    /// should be used by the mesh picking backend at runtime.
46    pub require_markers: bool,
47
48    /// Determines how mesh picking should consider [`Visibility`](bevy_camera::visibility::Visibility).
49    /// When set to [`RayCastVisibility::Any`], ray casts can be performed against both visible and hidden entities.
50    ///
51    /// Defaults to [`RayCastVisibility::VisibleInView`], only performing picking against visible entities
52    /// that are in the view of a camera.
53    pub ray_cast_visibility: RayCastVisibility,
54}
55
56impl Default for MeshPickingSettings {
57    fn default() -> Self {
58        Self {
59            require_markers: false,
60            ray_cast_visibility: RayCastVisibility::VisibleInView,
61        }
62    }
63}
64
65/// Adds the mesh picking backend to your app.
66#[derive(Clone, Default)]
67pub struct MeshPickingPlugin;
68
69impl Plugin for MeshPickingPlugin {
70    fn build(&self, app: &mut App) {
71        app.init_resource::<MeshPickingSettings>()
72            .add_systems(PreUpdate, update_hits.in_set(PickingSystems::Backend));
73    }
74}
75
76/// Casts rays into the scene using [`MeshPickingSettings`] and sends [`PointerHits`] events.
77pub fn update_hits(
78    backend_settings: Res<MeshPickingSettings>,
79    ray_map: Res<RayMap>,
80    picking_cameras: Query<(&Camera, Has<MeshPickingCamera>, Option<&RenderLayers>)>,
81    pickables: Query<&Pickable>,
82    marked_targets: Query<&Pickable>,
83    layers: Query<&RenderLayers>,
84    mut ray_cast: MeshRayCast,
85    mut pointer_hits_writer: MessageWriter<PointerHits>,
86) {
87    for (&ray_id, &ray) in ray_map.iter() {
88        let Ok((camera, cam_can_pick, cam_layers)) = picking_cameras.get(ray_id.camera) else {
89            continue;
90        };
91        if backend_settings.require_markers && !cam_can_pick {
92            continue;
93        }
94
95        let cam_layers = cam_layers.to_owned().unwrap_or_default();
96
97        let settings = MeshRayCastSettings {
98            visibility: backend_settings.ray_cast_visibility,
99            filter: &|entity| {
100                let marker_requirement =
101                    !backend_settings.require_markers || marked_targets.get(entity).is_ok();
102
103                // Other entities missing render layers are on the default layer 0
104                let entity_layers = layers.get(entity).cloned().unwrap_or_default();
105                let render_layers_match = cam_layers.intersects(&entity_layers);
106
107                let is_pickable = pickables.get(entity).ok().is_none_or(|p| p.is_hoverable);
108
109                marker_requirement && render_layers_match && is_pickable
110            },
111            early_exit_test: &|entity_hit| {
112                pickables
113                    .get(entity_hit)
114                    .is_ok_and(|pickable| pickable.should_block_lower)
115            },
116        };
117        let picks = ray_cast
118            .cast_ray(ray, &settings)
119            .iter()
120            .map(|(entity, hit)| {
121                let hit_data = HitData::new(
122                    ray_id.camera,
123                    hit.distance,
124                    Some(hit.point),
125                    Some(hit.normal),
126                );
127                (*entity, hit_data)
128            })
129            .collect::<Vec<_>>();
130        let order = camera.order as f32;
131        if !picks.is_empty() {
132            pointer_hits_writer.write(PointerHits::new(ray_id.pointer, picks, order));
133        }
134    }
135}