Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_picking/src/mesh_picking/mod.rs
6598 views
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
17
pub mod ray_cast;
18
19
use crate::{
20
backend::{ray::RayMap, HitData, PointerHits},
21
prelude::*,
22
PickingSystems,
23
};
24
use bevy_app::prelude::*;
25
use bevy_camera::{visibility::RenderLayers, Camera};
26
use bevy_ecs::prelude::*;
27
use bevy_reflect::prelude::*;
28
use 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)]
35
pub struct MeshPickingCamera;
36
37
/// Runtime settings for the [`MeshPickingPlugin`].
38
#[derive(Resource, Reflect)]
39
#[reflect(Resource, Default)]
40
pub 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
56
impl 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)]
67
pub struct MeshPickingPlugin;
68
69
impl 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.
77
pub 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 output: EventWriter<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
output.write(PointerHits::new(ray_id.pointer, picks, order));
133
}
134
}
135
}
136
137