|
| 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`]s produced by these various backends will be combined, sorted, |
| 12 | +//! 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 early exit 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 | +
|
| 34 | +use bevy_ecs::prelude::*; |
| 35 | +use bevy_math::Vec3; |
| 36 | +use bevy_reflect::Reflect; |
| 37 | + |
| 38 | +/// Common imports for implementing a picking backend. |
| 39 | +pub mod prelude { |
| 40 | + pub use super::{ray::RayMap, HitData, PointerHits}; |
| 41 | + pub use crate::{ |
| 42 | + pointer::{PointerId, PointerLocation}, |
| 43 | + PickSet, Pickable, |
| 44 | + }; |
| 45 | +} |
| 46 | + |
| 47 | +/// An event produced by a picking backend after it has run its hit tests, describing the entities |
| 48 | +/// under a pointer. |
| 49 | +/// |
| 50 | +/// Some backends may only support providing the topmost entity; this is a valid limitation of some |
| 51 | +/// backends. For example, a picking shader might only have data on the topmost rendered output from |
| 52 | +/// its buffer. |
| 53 | +#[derive(Event, Debug, Clone)] |
| 54 | +pub struct PointerHits { |
| 55 | + /// The pointer associated with this hit test. |
| 56 | + pub pointer: prelude::PointerId, |
| 57 | + /// An unordered collection of entities and their distance (depth) from the cursor. |
| 58 | + pub picks: Vec<(Entity, HitData)>, |
| 59 | + /// Set the order of this group of picks. Normally, this is the |
| 60 | + /// [`bevy_render::camera::Camera::order`]. |
| 61 | + /// |
| 62 | + /// Used to allow multiple `PointerHits` submitted for the same pointer to be ordered. |
| 63 | + /// `PointerHits` with a higher `order` will be checked before those with a lower `order`, |
| 64 | + /// regardless of the depth of each entity pick. |
| 65 | + /// |
| 66 | + /// In other words, when pick data is coalesced across all backends, the data is grouped by |
| 67 | + /// pointer, then sorted by order, and checked sequentially, sorting each `PointerHits` by |
| 68 | + /// entity depth. Events with a higher `order` are effectively on top of events with a lower |
| 69 | + /// order. |
| 70 | + /// |
| 71 | + /// ### Why is this an `f32`??? |
| 72 | + /// |
| 73 | + /// Bevy UI is special in that it can share a camera with other things being rendered. in order |
| 74 | + /// to properly sort them, we need a way to make `bevy_ui`'s order a tiny bit higher, like adding |
| 75 | + /// 0.5 to the order. We can't use integers, and we want users to be using camera.order by |
| 76 | + /// default, so this is the best solution at the moment. |
| 77 | + pub order: f32, |
| 78 | +} |
| 79 | + |
| 80 | +impl PointerHits { |
| 81 | + #[allow(missing_docs)] |
| 82 | + pub fn new(pointer: prelude::PointerId, picks: Vec<(Entity, HitData)>, order: f32) -> Self { |
| 83 | + Self { |
| 84 | + pointer, |
| 85 | + picks, |
| 86 | + order, |
| 87 | + } |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +/// Holds data from a successful pointer hit test. See [`HitData::depth`] for important details. |
| 92 | +#[derive(Clone, Debug, PartialEq, Reflect)] |
| 93 | +pub struct HitData { |
| 94 | + /// The camera entity used to detect this hit. Useful when you need to find the ray that was |
| 95 | + /// casted for this hit when using a raycasting backend. |
| 96 | + pub camera: Entity, |
| 97 | + /// `depth` only needs to be self-consistent with other [`PointerHits`]s using the same |
| 98 | + /// [`RenderTarget`](bevy_render::camera::RenderTarget). However, it is recommended to use the |
| 99 | + /// distance from the pointer to the hit, measured from the near plane of the camera, to the |
| 100 | + /// point, in world space. |
| 101 | + pub depth: f32, |
| 102 | + /// The position of the intersection in the world, if the data is available from the backend. |
| 103 | + pub position: Option<Vec3>, |
| 104 | + /// The normal vector of the hit test, if the data is available from the backend. |
| 105 | + pub normal: Option<Vec3>, |
| 106 | +} |
| 107 | + |
| 108 | +impl HitData { |
| 109 | + #[allow(missing_docs)] |
| 110 | + pub fn new(camera: Entity, depth: f32, position: Option<Vec3>, normal: Option<Vec3>) -> Self { |
| 111 | + Self { |
| 112 | + camera, |
| 113 | + depth, |
| 114 | + position, |
| 115 | + normal, |
| 116 | + } |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +pub mod ray { |
| 121 | + //! Types and systems for constructing rays from cameras and pointers. |
| 122 | +
|
| 123 | + use crate::backend::prelude::{PointerId, PointerLocation}; |
| 124 | + use bevy_ecs::prelude::*; |
| 125 | + use bevy_math::Ray3d; |
| 126 | + use bevy_reflect::Reflect; |
| 127 | + use bevy_render::camera::Camera; |
| 128 | + use bevy_transform::prelude::GlobalTransform; |
| 129 | + use bevy_utils::{hashbrown::hash_map::Iter, HashMap}; |
| 130 | + use bevy_window::PrimaryWindow; |
| 131 | + |
| 132 | + /// Identifies a ray constructed from some (pointer, camera) combination. A pointer can be over |
| 133 | + /// multiple cameras, which is why a single pointer may have multiple rays. |
| 134 | + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Reflect)] |
| 135 | + pub struct RayId { |
| 136 | + /// The camera whose projection was used to calculate the ray. |
| 137 | + pub camera: Entity, |
| 138 | + /// The pointer whose pixel coordinates were used to calculate the ray. |
| 139 | + pub pointer: PointerId, |
| 140 | + } |
| 141 | + |
| 142 | + impl RayId { |
| 143 | + /// Construct a [`RayId`]. |
| 144 | + pub fn new(camera: Entity, pointer: PointerId) -> Self { |
| 145 | + Self { camera, pointer } |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + /// A map from [`RayId`] to [`Ray3d`]. |
| 150 | + /// |
| 151 | + /// This map is cleared and re-populated every frame before any backends run. Ray-based picking |
| 152 | + /// backends should use this when possible, as it automatically handles viewports, DPI, and |
| 153 | + /// other details of building rays from pointer locations. |
| 154 | + /// |
| 155 | + /// ## Usage |
| 156 | + /// |
| 157 | + /// Iterate over each [`Ray3d`] and its [`RayId`] with [`RayMap::iter`]. |
| 158 | + /// |
| 159 | + /// ``` |
| 160 | + /// # use bevy_ecs::prelude::*; |
| 161 | + /// # use bevy_picking::backend::ray::RayMap; |
| 162 | + /// # use bevy_picking::backend::PointerHits; |
| 163 | + /// // My raycasting backend |
| 164 | + /// pub fn update_hits(ray_map: Res<RayMap>, mut output_events: EventWriter<PointerHits>,) { |
| 165 | + /// for (&ray_id, &ray) in ray_map.iter() { |
| 166 | + /// // Run a raycast with each ray, returning any `PointerHits` found. |
| 167 | + /// } |
| 168 | + /// } |
| 169 | + /// ``` |
| 170 | + #[derive(Clone, Debug, Default, Resource)] |
| 171 | + pub struct RayMap { |
| 172 | + map: HashMap<RayId, Ray3d>, |
| 173 | + } |
| 174 | + |
| 175 | + impl RayMap { |
| 176 | + /// Iterates over all world space rays for every picking pointer. |
| 177 | + pub fn iter(&self) -> Iter<'_, RayId, Ray3d> { |
| 178 | + self.map.iter() |
| 179 | + } |
| 180 | + |
| 181 | + /// The hash map of all rays cast in the current frame. |
| 182 | + pub fn map(&self) -> &HashMap<RayId, Ray3d> { |
| 183 | + &self.map |
| 184 | + } |
| 185 | + |
| 186 | + /// Clears the [`RayMap`] and re-populates it with one ray for each |
| 187 | + /// combination of pointer entity and camera entity where the pointer |
| 188 | + /// intersects the camera's viewport. |
| 189 | + pub fn repopulate( |
| 190 | + mut ray_map: ResMut<Self>, |
| 191 | + primary_window_entity: Query<Entity, With<PrimaryWindow>>, |
| 192 | + cameras: Query<(Entity, &Camera, &GlobalTransform)>, |
| 193 | + pointers: Query<(&PointerId, &PointerLocation)>, |
| 194 | + ) { |
| 195 | + ray_map.map.clear(); |
| 196 | + |
| 197 | + for (camera_entity, camera, camera_tfm) in &cameras { |
| 198 | + if !camera.is_active { |
| 199 | + continue; |
| 200 | + } |
| 201 | + |
| 202 | + for (&pointer_id, pointer_loc) in &pointers { |
| 203 | + if let Some(ray) = |
| 204 | + make_ray(&primary_window_entity, camera, camera_tfm, pointer_loc) |
| 205 | + { |
| 206 | + ray_map |
| 207 | + .map |
| 208 | + .insert(RayId::new(camera_entity, pointer_id), ray); |
| 209 | + } |
| 210 | + } |
| 211 | + } |
| 212 | + } |
| 213 | + } |
| 214 | + |
| 215 | + fn make_ray( |
| 216 | + primary_window_entity: &Query<Entity, With<PrimaryWindow>>, |
| 217 | + camera: &Camera, |
| 218 | + camera_tfm: &GlobalTransform, |
| 219 | + pointer_loc: &PointerLocation, |
| 220 | + ) -> Option<Ray3d> { |
| 221 | + let pointer_loc = pointer_loc.location()?; |
| 222 | + if !pointer_loc.is_in_viewport(camera, primary_window_entity) { |
| 223 | + return None; |
| 224 | + } |
| 225 | + let mut viewport_pos = pointer_loc.position; |
| 226 | + if let Some(viewport) = &camera.viewport { |
| 227 | + let viewport_logical = camera.to_logical(viewport.physical_position)?; |
| 228 | + viewport_pos -= viewport_logical; |
| 229 | + } |
| 230 | + camera.viewport_to_world(camera_tfm, viewport_pos) |
| 231 | + } |
| 232 | +} |
0 commit comments