bevy_picking/mesh_picking/
mod.rs1pub 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#[derive(Debug, Clone, Default, Component, Reflect)]
34#[reflect(Debug, Default, Component)]
35pub struct MeshPickingCamera;
36
37#[derive(Resource, Reflect)]
39#[reflect(Resource, Default)]
40pub struct MeshPickingSettings {
41 pub require_markers: bool,
47
48 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#[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
76pub 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 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}