# Objective Now that #13432 has been merged, it's important we update our reflected types to properly opt into this feature. If we do not, then this could cause issues for users downstream who want to make use of reflection-based cloning. ## Solution This PR is broken into 4 commits: 1. Add `#[reflect(Clone)]` on all types marked `#[reflect(opaque)]` that are also `Clone`. This is mandatory as these types would otherwise cause the cloning operation to fail for any type that contains it at any depth. 2. Update the reflection example to suggest adding `#[reflect(Clone)]` on opaque types. 3. Add `#[reflect(clone)]` attributes on all fields marked `#[reflect(ignore)]` that are also `Clone`. This prevents the ignored field from causing the cloning operation to fail. Note that some of the types that contain these fields are also `Clone`, and thus can be marked `#[reflect(Clone)]`. This makes the `#[reflect(clone)]` attribute redundant. However, I think it's safer to keep it marked in the case that the `Clone` impl/derive is ever removed. I'm open to removing them, though, if people disagree. 4. Finally, I added `#[reflect(Clone)]` on all types that are also `Clone`. While not strictly necessary, it enables us to reduce the generated output since we can just call `Clone::clone` directly instead of calling `PartialReflect::reflect_clone` on each variant/field. It also means we benefit from any optimizations or customizations made in the `Clone` impl, including directly dereferencing `Copy` values and increasing reference counters. Along with that change I also took the liberty of adding any missing registrations that I saw could be applied to the type as well, such as `Default`, `PartialEq`, and `Hash`. There were hundreds of these to edit, though, so it's possible I missed quite a few. That last commit is **_massive_**. There were nearly 700 types to update. So it's recommended to review the first three before moving onto that last one. Additionally, I can break the last commit off into its own PR or into smaller PRs, but I figured this would be the easiest way of doing it (and in a timely manner since I unfortunately don't have as much time as I used to for code contributions). ## Testing You can test locally with a `cargo check`: ``` cargo check --workspace --all-features ```
242 lines
10 KiB
Rust
242 lines
10 KiB
Rust
//! This module provides a simple interface for implementing a picking backend.
|
|
//!
|
|
//! Don't be dissuaded by terminology like "backend"; the idea is dead simple. `bevy_picking`
|
|
//! will tell you where pointers are, all you have to do is send an event if the pointers are
|
|
//! hitting something. That's it. The rest of this documentation explains the requirements in more
|
|
//! detail.
|
|
//!
|
|
//! Because `bevy_picking` is very loosely coupled with its backends, you can mix and match as
|
|
//! many backends as you want. For example, you could use the `rapier` backend to raycast against
|
|
//! physics objects, a picking shader backend to pick non-physics meshes, and the `bevy_ui` backend
|
|
//! for your UI. The [`PointerHits`] instances produced by these various backends will be combined,
|
|
//! sorted, and used as a homogeneous input for the picking systems that consume these events.
|
|
//!
|
|
//! ## Implementation
|
|
//!
|
|
//! - A picking backend only has one job: read [`PointerLocation`](crate::pointer::PointerLocation)
|
|
//! components and produce [`PointerHits`] events. In plain English, a backend is provided the
|
|
//! location of pointers, and is asked to provide a list of entities under those pointers.
|
|
//!
|
|
//! - The [`PointerHits`] events produced by a backend do **not** need to be sorted or filtered, all
|
|
//! that is needed is an unordered list of entities and their [`HitData`].
|
|
//!
|
|
//! - Backends do not need to consider the [`Pickable`](crate::Pickable) component, though they may
|
|
//! use it for optimization purposes. For example, a backend that traverses a spatial hierarchy
|
|
//! may want to exit early if it intersects an entity that blocks lower entities from being
|
|
//! picked.
|
|
//!
|
|
//! ### Raycasting Backends
|
|
//!
|
|
//! Backends that require a ray to cast into the scene should use [`ray::RayMap`]. This
|
|
//! automatically constructs rays in world space for all cameras and pointers, handling details like
|
|
//! viewports and DPI for you.
|
|
|
|
use bevy_ecs::prelude::*;
|
|
use bevy_math::Vec3;
|
|
use bevy_reflect::Reflect;
|
|
|
|
/// The picking backend prelude.
|
|
///
|
|
/// This includes the most common types in this module, re-exported for your convenience.
|
|
pub mod prelude {
|
|
pub use super::{ray::RayMap, HitData, PointerHits};
|
|
pub use crate::{
|
|
pointer::{PointerId, PointerLocation},
|
|
PickSet, Pickable,
|
|
};
|
|
}
|
|
|
|
/// An event produced by a picking backend after it has run its hit tests, describing the entities
|
|
/// under a pointer.
|
|
///
|
|
/// Some backends may only support providing the topmost entity; this is a valid limitation. For
|
|
/// example, a picking shader might only have data on the topmost rendered output from its buffer.
|
|
///
|
|
/// Note that systems reading these events in [`PreUpdate`](bevy_app::PreUpdate) will not report ordering
|
|
/// ambiguities with picking backends. Take care to ensure such systems are explicitly ordered
|
|
/// against [`PickSet::Backend`](crate::PickSet::Backend), or better, avoid reading `PointerHits` in `PreUpdate`.
|
|
#[derive(Event, Debug, Clone, Reflect)]
|
|
#[reflect(Debug, Clone)]
|
|
pub struct PointerHits {
|
|
/// The pointer associated with this hit test.
|
|
pub pointer: prelude::PointerId,
|
|
/// An unordered collection of entities and their distance (depth) from the cursor.
|
|
pub picks: Vec<(Entity, HitData)>,
|
|
/// Set the order of this group of picks. Normally, this is the
|
|
/// [`bevy_render::camera::Camera::order`].
|
|
///
|
|
/// Used to allow multiple `PointerHits` submitted for the same pointer to be ordered.
|
|
/// `PointerHits` with a higher `order` will be checked before those with a lower `order`,
|
|
/// regardless of the depth of each entity pick.
|
|
///
|
|
/// In other words, when pick data is coalesced across all backends, the data is grouped by
|
|
/// pointer, then sorted by order, and checked sequentially, sorting each `PointerHits` by
|
|
/// entity depth. Events with a higher `order` are effectively on top of events with a lower
|
|
/// order.
|
|
///
|
|
/// ### Why is this an `f32`???
|
|
///
|
|
/// Bevy UI is special in that it can share a camera with other things being rendered. in order
|
|
/// to properly sort them, we need a way to make `bevy_ui`'s order a tiny bit higher, like adding
|
|
/// 0.5 to the order. We can't use integers, and we want users to be using camera.order by
|
|
/// default, so this is the best solution at the moment.
|
|
pub order: f32,
|
|
}
|
|
|
|
impl PointerHits {
|
|
#[expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
|
|
pub fn new(pointer: prelude::PointerId, picks: Vec<(Entity, HitData)>, order: f32) -> Self {
|
|
Self {
|
|
pointer,
|
|
picks,
|
|
order,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Holds data from a successful pointer hit test. See [`HitData::depth`] for important details.
|
|
#[derive(Clone, Debug, PartialEq, Reflect)]
|
|
#[reflect(Clone, PartialEq)]
|
|
pub struct HitData {
|
|
/// The camera entity used to detect this hit. Useful when you need to find the ray that was
|
|
/// casted for this hit when using a raycasting backend.
|
|
pub camera: Entity,
|
|
/// `depth` only needs to be self-consistent with other [`PointerHits`]s using the same
|
|
/// [`RenderTarget`](bevy_render::camera::RenderTarget). However, it is recommended to use the
|
|
/// distance from the pointer to the hit, measured from the near plane of the camera, to the
|
|
/// point, in world space.
|
|
pub depth: f32,
|
|
/// The position reported by the backend, if the data is available. Position data may be in any
|
|
/// space (e.g. World space, Screen space, Local space), specified by the backend providing it.
|
|
pub position: Option<Vec3>,
|
|
/// The normal vector of the hit test, if the data is available from the backend.
|
|
pub normal: Option<Vec3>,
|
|
}
|
|
|
|
impl HitData {
|
|
#[expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
|
|
pub fn new(camera: Entity, depth: f32, position: Option<Vec3>, normal: Option<Vec3>) -> Self {
|
|
Self {
|
|
camera,
|
|
depth,
|
|
position,
|
|
normal,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub mod ray {
|
|
//! Types and systems for constructing rays from cameras and pointers.
|
|
|
|
use crate::backend::prelude::{PointerId, PointerLocation};
|
|
use bevy_ecs::prelude::*;
|
|
use bevy_math::Ray3d;
|
|
use bevy_platform_support::collections::{hash_map::Iter, HashMap};
|
|
use bevy_reflect::Reflect;
|
|
use bevy_render::camera::Camera;
|
|
use bevy_transform::prelude::GlobalTransform;
|
|
use bevy_window::PrimaryWindow;
|
|
|
|
/// Identifies a ray constructed from some (pointer, camera) combination. A pointer can be over
|
|
/// multiple cameras, which is why a single pointer may have multiple rays.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Reflect)]
|
|
#[reflect(Clone, PartialEq, Hash)]
|
|
pub struct RayId {
|
|
/// The camera whose projection was used to calculate the ray.
|
|
pub camera: Entity,
|
|
/// The pointer whose pixel coordinates were used to calculate the ray.
|
|
pub pointer: PointerId,
|
|
}
|
|
|
|
impl RayId {
|
|
/// Construct a [`RayId`].
|
|
pub fn new(camera: Entity, pointer: PointerId) -> Self {
|
|
Self { camera, pointer }
|
|
}
|
|
}
|
|
|
|
/// A map from [`RayId`] to [`Ray3d`].
|
|
///
|
|
/// This map is cleared and re-populated every frame before any backends run. Ray-based picking
|
|
/// backends should use this when possible, as it automatically handles viewports, DPI, and
|
|
/// other details of building rays from pointer locations.
|
|
///
|
|
/// ## Usage
|
|
///
|
|
/// Iterate over each [`Ray3d`] and its [`RayId`] with [`RayMap::iter`].
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_picking::backend::ray::RayMap;
|
|
/// # use bevy_picking::backend::PointerHits;
|
|
/// // My raycasting backend
|
|
/// pub fn update_hits(ray_map: Res<RayMap>, mut output_events: EventWriter<PointerHits>,) {
|
|
/// for (&ray_id, &ray) in ray_map.iter() {
|
|
/// // Run a raycast with each ray, returning any `PointerHits` found.
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
#[derive(Clone, Debug, Default, Resource)]
|
|
pub struct RayMap {
|
|
map: HashMap<RayId, Ray3d>,
|
|
}
|
|
|
|
impl RayMap {
|
|
/// Iterates over all world space rays for every picking pointer.
|
|
pub fn iter(&self) -> Iter<'_, RayId, Ray3d> {
|
|
self.map.iter()
|
|
}
|
|
|
|
/// The hash map of all rays cast in the current frame.
|
|
pub fn map(&self) -> &HashMap<RayId, Ray3d> {
|
|
&self.map
|
|
}
|
|
|
|
/// Clears the [`RayMap`] and re-populates it with one ray for each
|
|
/// combination of pointer entity and camera entity where the pointer
|
|
/// intersects the camera's viewport.
|
|
pub fn repopulate(
|
|
mut ray_map: ResMut<Self>,
|
|
primary_window_entity: Query<Entity, With<PrimaryWindow>>,
|
|
cameras: Query<(Entity, &Camera, &GlobalTransform)>,
|
|
pointers: Query<(&PointerId, &PointerLocation)>,
|
|
) {
|
|
ray_map.map.clear();
|
|
|
|
for (camera_entity, camera, camera_tfm) in &cameras {
|
|
if !camera.is_active {
|
|
continue;
|
|
}
|
|
|
|
for (&pointer_id, pointer_loc) in &pointers {
|
|
if let Some(ray) =
|
|
make_ray(&primary_window_entity, camera, camera_tfm, pointer_loc)
|
|
{
|
|
ray_map
|
|
.map
|
|
.insert(RayId::new(camera_entity, pointer_id), ray);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn make_ray(
|
|
primary_window_entity: &Query<Entity, With<PrimaryWindow>>,
|
|
camera: &Camera,
|
|
camera_tfm: &GlobalTransform,
|
|
pointer_loc: &PointerLocation,
|
|
) -> Option<Ray3d> {
|
|
let pointer_loc = pointer_loc.location()?;
|
|
if !pointer_loc.is_in_viewport(camera, primary_window_entity) {
|
|
return None;
|
|
}
|
|
let mut viewport_pos = pointer_loc.position;
|
|
if let Some(viewport) = &camera.viewport {
|
|
let viewport_logical = camera.to_logical(viewport.physical_position)?;
|
|
viewport_pos -= viewport_logical;
|
|
}
|
|
camera.viewport_to_world(camera_tfm, viewport_pos).ok()
|
|
}
|
|
}
|