bevy_picking/
backend.rs

1//! This module provides a simple interface for implementing a picking backend.
2//!
3//! Don't be dissuaded by terminology like "backend"; the idea is dead simple. `bevy_picking`
4//! will tell you where pointers are, all you have to do is send an event if the pointers are
5//! hitting something. That's it. The rest of this documentation explains the requirements in more
6//! detail.
7//!
8//! Because `bevy_picking` is very loosely coupled with its backends, you can mix and match as
9//! many backends as you want. For example, you could use the `rapier` backend to raycast against
10//! physics objects, a picking shader backend to pick non-physics meshes, and the `bevy_ui` backend
11//! for your UI. The [`PointerHits`] instances produced by these various backends will be combined,
12//! sorted, and used as a homogeneous input for the picking systems that consume these events.
13//!
14//! ## Implementation
15//!
16//! - A picking backend only has one job: read [`PointerLocation`](crate::pointer::PointerLocation)
17//!   components and produce [`PointerHits`] events. In plain English, a backend is provided the
18//!   location of pointers, and is asked to provide a list of entities under those pointers.
19//!
20//! - The [`PointerHits`] events produced by a backend do **not** need to be sorted or filtered, all
21//!   that is needed is an unordered list of entities and their [`HitData`].
22//!
23//! - Backends do not need to consider the [`Pickable`](crate::Pickable) component, though they may
24//!   use it for optimization purposes. For example, a backend that traverses a spatial hierarchy
25//!   may want to exit early if it intersects an entity that blocks lower entities from being
26//!   picked.
27//!
28//! ### Raycasting Backends
29//!
30//! Backends that require a ray to cast into the scene should use [`ray::RayMap`]. This
31//! automatically constructs rays in world space for all cameras and pointers, handling details like
32//! viewports and DPI for you.
33
34use bevy_ecs::prelude::*;
35use bevy_math::Vec3;
36use bevy_reflect::Reflect;
37
38/// The picking backend prelude.
39///
40/// This includes the most common types in this module, re-exported for your convenience.
41pub mod prelude {
42    pub use super::{ray::RayMap, HitData, PointerHits};
43    pub use crate::{
44        pointer::{PointerId, PointerLocation},
45        PickSet, Pickable,
46    };
47}
48
49/// An event produced by a picking backend after it has run its hit tests, describing the entities
50/// under a pointer.
51///
52/// Some backends may only support providing the topmost entity; this is a valid limitation. For
53/// example, a picking shader might only have data on the topmost rendered output from its buffer.
54///
55/// Note that systems reading these events in [`PreUpdate`](bevy_app::PreUpdate) will not report ordering
56/// ambiguities with picking backends. Take care to ensure such systems are explicitly ordered
57/// against [`PickSet::Backend`](crate::PickSet::Backend), or better, avoid reading `PointerHits` in `PreUpdate`.
58#[derive(Event, Debug, Clone, Reflect)]
59#[reflect(Debug, Clone)]
60pub struct PointerHits {
61    /// The pointer associated with this hit test.
62    pub pointer: prelude::PointerId,
63    /// An unordered collection of entities and their distance (depth) from the cursor.
64    pub picks: Vec<(Entity, HitData)>,
65    /// Set the order of this group of picks. Normally, this is the
66    /// [`bevy_render::camera::Camera::order`].
67    ///
68    /// Used to allow multiple `PointerHits` submitted for the same pointer to be ordered.
69    /// `PointerHits` with a higher `order` will be checked before those with a lower `order`,
70    /// regardless of the depth of each entity pick.
71    ///
72    /// In other words, when pick data is coalesced across all backends, the data is grouped by
73    /// pointer, then sorted by order, and checked sequentially, sorting each `PointerHits` by
74    /// entity depth. Events with a higher `order` are effectively on top of events with a lower
75    /// order.
76    ///
77    /// ### Why is this an `f32`???
78    ///
79    /// Bevy UI is special in that it can share a camera with other things being rendered. in order
80    /// to properly sort them, we need a way to make `bevy_ui`'s order a tiny bit higher, like adding
81    /// 0.5 to the order. We can't use integers, and we want users to be using camera.order by
82    /// default, so this is the best solution at the moment.
83    pub order: f32,
84}
85
86impl PointerHits {
87    #[expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
88    pub fn new(pointer: prelude::PointerId, picks: Vec<(Entity, HitData)>, order: f32) -> Self {
89        Self {
90            pointer,
91            picks,
92            order,
93        }
94    }
95}
96
97/// Holds data from a successful pointer hit test. See [`HitData::depth`] for important details.
98#[derive(Clone, Debug, PartialEq, Reflect)]
99#[reflect(Clone, PartialEq)]
100pub struct HitData {
101    /// The camera entity used to detect this hit. Useful when you need to find the ray that was
102    /// casted for this hit when using a raycasting backend.
103    pub camera: Entity,
104    /// `depth` only needs to be self-consistent with other [`PointerHits`]s using the same
105    /// [`RenderTarget`](bevy_render::camera::RenderTarget). However, it is recommended to use the
106    /// distance from the pointer to the hit, measured from the near plane of the camera, to the
107    /// point, in world space.
108    pub depth: f32,
109    /// The position reported by the backend, if the data is available. Position data may be in any
110    /// space (e.g. World space, Screen space, Local space), specified by the backend providing it.
111    pub position: Option<Vec3>,
112    /// The normal vector of the hit test, if the data is available from the backend.
113    pub normal: Option<Vec3>,
114}
115
116impl HitData {
117    #[expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
118    pub fn new(camera: Entity, depth: f32, position: Option<Vec3>, normal: Option<Vec3>) -> Self {
119        Self {
120            camera,
121            depth,
122            position,
123            normal,
124        }
125    }
126}
127
128pub mod ray {
129    //! Types and systems for constructing rays from cameras and pointers.
130
131    use crate::backend::prelude::{PointerId, PointerLocation};
132    use bevy_ecs::prelude::*;
133    use bevy_math::Ray3d;
134    use bevy_platform::collections::{hash_map::Iter, HashMap};
135    use bevy_reflect::Reflect;
136    use bevy_render::camera::Camera;
137    use bevy_transform::prelude::GlobalTransform;
138    use bevy_window::PrimaryWindow;
139
140    /// Identifies a ray constructed from some (pointer, camera) combination. A pointer can be over
141    /// multiple cameras, which is why a single pointer may have multiple rays.
142    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Reflect)]
143    #[reflect(Clone, PartialEq, Hash)]
144    pub struct RayId {
145        /// The camera whose projection was used to calculate the ray.
146        pub camera: Entity,
147        /// The pointer whose pixel coordinates were used to calculate the ray.
148        pub pointer: PointerId,
149    }
150
151    impl RayId {
152        /// Construct a [`RayId`].
153        pub fn new(camera: Entity, pointer: PointerId) -> Self {
154            Self { camera, pointer }
155        }
156    }
157
158    /// A map from [`RayId`] to [`Ray3d`].
159    ///
160    /// This map is cleared and re-populated every frame before any backends run. Ray-based picking
161    /// backends should use this when possible, as it automatically handles viewports, DPI, and
162    /// other details of building rays from pointer locations.
163    ///
164    /// ## Usage
165    ///
166    /// Iterate over each [`Ray3d`] and its [`RayId`] with [`RayMap::iter`].
167    ///
168    /// ```
169    /// # use bevy_ecs::prelude::*;
170    /// # use bevy_picking::backend::ray::RayMap;
171    /// # use bevy_picking::backend::PointerHits;
172    /// // My raycasting backend
173    /// pub fn update_hits(ray_map: Res<RayMap>, mut output_events: EventWriter<PointerHits>,) {
174    ///     for (&ray_id, &ray) in ray_map.iter() {
175    ///         // Run a raycast with each ray, returning any `PointerHits` found.
176    ///     }
177    /// }
178    /// ```
179    #[derive(Clone, Debug, Default, Resource)]
180    pub struct RayMap {
181        /// Cartesian product of all pointers and all cameras
182        /// Add your rays here to support picking through indirections,
183        /// e.g. rendered-to-texture cameras
184        pub map: HashMap<RayId, Ray3d>,
185    }
186
187    impl RayMap {
188        /// Iterates over all world space rays for every picking pointer.
189        pub fn iter(&self) -> Iter<'_, RayId, Ray3d> {
190            self.map.iter()
191        }
192
193        /// Clears the [`RayMap`] and re-populates it with one ray for each
194        /// combination of pointer entity and camera entity where the pointer
195        /// intersects the camera's viewport.
196        pub fn repopulate(
197            mut ray_map: ResMut<Self>,
198            primary_window_entity: Query<Entity, With<PrimaryWindow>>,
199            cameras: Query<(Entity, &Camera, &GlobalTransform)>,
200            pointers: Query<(&PointerId, &PointerLocation)>,
201        ) {
202            ray_map.map.clear();
203
204            for (camera_entity, camera, camera_tfm) in &cameras {
205                if !camera.is_active {
206                    continue;
207                }
208
209                for (&pointer_id, pointer_loc) in &pointers {
210                    if let Some(ray) =
211                        make_ray(&primary_window_entity, camera, camera_tfm, pointer_loc)
212                    {
213                        ray_map
214                            .map
215                            .insert(RayId::new(camera_entity, pointer_id), ray);
216                    }
217                }
218            }
219        }
220    }
221
222    fn make_ray(
223        primary_window_entity: &Query<Entity, With<PrimaryWindow>>,
224        camera: &Camera,
225        camera_tfm: &GlobalTransform,
226        pointer_loc: &PointerLocation,
227    ) -> Option<Ray3d> {
228        let pointer_loc = pointer_loc.location()?;
229        if !pointer_loc.is_in_viewport(camera, primary_window_entity) {
230            return None;
231        }
232        let mut viewport_pos = pointer_loc.position;
233        if let Some(viewport) = &camera.viewport {
234            let viewport_logical = camera.to_logical(viewport.physical_position)?;
235            viewport_pos -= viewport_logical;
236        }
237        camera.viewport_to_world(camera_tfm, viewport_pos).ok()
238    }
239}