-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Add support for custom picking data #23245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
28f786a
1ab9ae7
91368e9
19b2303
46c7ef7
2a62f95
b1f078a
94114a9
c815c8d
e7c410d
94183a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,9 @@ | |
| //! automatically constructs rays in world space for all cameras and pointers, handling details like | ||
| //! viewports and DPI for you. | ||
|
|
||
| use alloc::sync::Arc; | ||
| use core::{any::Any, fmt}; | ||
|
|
||
| use bevy_ecs::prelude::*; | ||
| use bevy_math::Vec3; | ||
| use bevy_reflect::Reflect; | ||
|
|
@@ -39,13 +42,43 @@ use bevy_reflect::Reflect; | |
| /// | ||
| /// 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 super::{ray::RayMap, HitData, HitDataExtra, PointerHits}; | ||
| pub use crate::{ | ||
| pointer::{PointerId, PointerLocation}, | ||
| Pickable, PickingSystems, | ||
| }; | ||
| } | ||
|
|
||
| /// Extra data attached to a [`HitData`] by a picking backend. | ||
| /// | ||
| /// Use this for backend-specific data like triangle indices, UVs, or material | ||
| /// information. | ||
| /// Any `Send + Sync + fmt::Debug + 'static` type implements this trait | ||
| /// automatically. `Clone` is not required: extra data is stored in an [`Arc`], | ||
| /// so [`HitData`] can still implement [`Clone`]. `Clone` requires knowing the | ||
| /// size of the type, which is not possible with dynamically dispatched types, | ||
| /// so it cannot be used for `dyn HitDataExtra`. | ||
| /// | ||
| /// ```rust | ||
| /// #[derive(Debug)] | ||
| /// struct MyHitInfo { triangle_index: u32 } | ||
| /// ``` | ||
| /// | ||
| /// Read it back with [`HitData::extra_as`]: | ||
| /// | ||
| /// ```rust | ||
| /// # use bevy_picking::backend::HitData; | ||
| /// # #[derive(Debug)] struct MyHitInfo { triangle_index: u32 } | ||
| /// fn read_extra(hit: &HitData) { | ||
| /// if let Some(info) = hit.extra_as::<MyHitInfo>() { | ||
| /// println!("Hit triangle {}", info.triangle_index); | ||
| /// } | ||
| /// } | ||
| /// ``` | ||
| pub trait HitDataExtra: Any + Send + Sync + fmt::Debug {} | ||
|
|
||
| impl<T: Send + Sync + fmt::Debug + Any + 'static> HitDataExtra for T {} | ||
|
|
||
| /// A message produced by a picking backend after it has run its hit tests, describing the entities | ||
| /// under a pointer. | ||
| /// | ||
|
|
@@ -95,8 +128,10 @@ impl PointerHits { | |
| } | ||
|
|
||
| /// Holds data from a successful pointer hit test. See [`HitData::depth`] for important details. | ||
| #[derive(Clone, Debug, PartialEq, Reflect)] | ||
| #[reflect(Clone, PartialEq)] | ||
| /// | ||
| /// Backends can attach arbitrary typed data via [`HitData::extra`]. See [`HitDataExtra`]. | ||
| #[derive(Debug, Reflect)] | ||
| #[reflect(Debug)] | ||
| pub struct HitData { | ||
| /// The camera entity used to detect this hit. Useful when you need to find the ray that was | ||
| /// cast for this hit when using a raycasting backend. | ||
|
|
@@ -111,6 +146,34 @@ pub struct HitData { | |
| pub position: Option<Vec3>, | ||
| /// The normal vector of the hit test, if the data is available from the backend. | ||
| pub normal: Option<Vec3>, | ||
| /// Optional backend-specific extra data attached to this hit. Read it with [`HitData::extra_as`]. | ||
| /// | ||
| /// This is stored in an [`Arc`] so cloning [`HitData`] stays cheap. This field is excluded | ||
| /// from [`PartialEq`] because value equality for trait objects would require extra dynamic | ||
| /// downcasting that the picking pipeline does not need. | ||
| #[reflect(ignore)] | ||
| pub extra: Option<Arc<dyn HitDataExtra>>, | ||
| } | ||
|
|
||
| impl Clone for HitData { | ||
| fn clone(&self) -> Self { | ||
| Self { | ||
| camera: self.camera, | ||
| depth: self.depth, | ||
| position: self.position, | ||
| normal: self.normal, | ||
| extra: self.extra.as_ref().map(Arc::clone), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl PartialEq for HitData { | ||
| fn eq(&self, other: &Self) -> bool { | ||
| self.camera == other.camera | ||
| && self.depth == other.depth | ||
| && self.position == other.position | ||
| && self.normal == other.normal | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why doesn't this check It looks like
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Equality would require downcasting both sides every time there is a comparison (https://users.rust-lang.org/t/how-to-compare-two-trait-objects-for-equality/88063), which feels overkill. The explanation was added in e7c410d |
||
| } | ||
|
|
||
| impl HitData { | ||
|
|
@@ -121,6 +184,46 @@ impl HitData { | |
| depth, | ||
| position, | ||
| normal, | ||
| extra: None, | ||
| } | ||
| } | ||
|
|
||
| /// Returns any attached extra data as `T` if available. | ||
| /// | ||
| /// This returns `None` if no extra data was attached, or if the hit stores a | ||
| /// different concrete extra data type. | ||
| pub fn extra_as<T: Any>(&self) -> Option<&T> { | ||
| let extra: &dyn Any = self.extra.as_deref()?; | ||
| extra.downcast_ref::<T>() | ||
| } | ||
|
|
||
| /// Creates a [`HitData`] with backend-specific extra data. `extra` can be | ||
| /// any [`HitDataExtra`]. | ||
| /// | ||
| /// # Example | ||
| /// | ||
| /// ```rust | ||
| /// # use bevy_ecs::prelude::*; | ||
| /// # use bevy_picking::backend::HitData; | ||
| /// #[derive(Debug)] | ||
| /// struct MyHitInfo { triangle_index: u32 } | ||
| /// | ||
| /// # let camera = Entity::PLACEHOLDER; | ||
| /// let hit = HitData::new_with_extra(camera, 1.0, None, None, MyHitInfo { triangle_index: 7 }); | ||
| /// ``` | ||
| pub fn new_with_extra( | ||
| camera: Entity, | ||
| depth: f32, | ||
| position: Option<Vec3>, | ||
| normal: Option<Vec3>, | ||
| extra: impl HitDataExtra, | ||
| ) -> Self { | ||
| Self { | ||
| camera, | ||
| depth, | ||
| position, | ||
| normal, | ||
| extra: Some(Arc::new(extra)), | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -239,3 +342,53 @@ pub mod ray { | |
| .ok() | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[derive(Debug, PartialEq)] | ||
| struct TriangleHitInfo { | ||
| triangle_index: u32, | ||
| } | ||
|
|
||
| #[derive(Debug, PartialEq)] | ||
| struct OtherHitInfo { | ||
| triangle_index: u32, | ||
| } | ||
|
|
||
| #[test] | ||
| fn hit_data_extra() { | ||
| let camera = Entity::PLACEHOLDER; | ||
|
|
||
| let hit = HitData::new_with_extra( | ||
| camera, | ||
| 1.0, | ||
| Some(Vec3::new(1.0, 2.0, 3.0)), | ||
| Some(Vec3::Y), | ||
| TriangleHitInfo { triangle_index: 7 }, | ||
| ); | ||
|
|
||
| assert_eq!( | ||
| hit.extra_as::<TriangleHitInfo>(), | ||
| Some(&TriangleHitInfo { triangle_index: 7 }) | ||
| ); | ||
| assert_eq!(hit.extra_as::<OtherHitInfo>(), None); | ||
|
|
||
| let cloned = hit.clone(); | ||
| assert_eq!( | ||
| cloned.extra_as::<TriangleHitInfo>(), | ||
| Some(&TriangleHitInfo { triangle_index: 7 }) | ||
| ); | ||
|
|
||
| let other_extra = HitData::new_with_extra( | ||
| camera, | ||
| 1.0, | ||
| Some(Vec3::new(1.0, 2.0, 3.0)), | ||
| Some(Vec3::Y), | ||
| TriangleHitInfo { triangle_index: 99 }, | ||
| ); | ||
|
|
||
| assert_eq!(hit, other_extra); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1248,6 +1248,7 @@ mod tests { | |
| camera, | ||
| position: None, | ||
| normal: None, | ||
| extra: None, | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.