use alloc::sync::Arc;
use core::{any::Any, fmt};
use bevy_ecs::prelude::*;
use bevy_math::Vec3;
use bevy_reflect::Reflect;
pub mod prelude {
pub use super::{ray::RayMap, HitData, HitDataExtra, PointerHits};
pub use crate::{
pointer::{PointerId, PointerLocation},
Pickable, PickingSystems,
};
}
pub trait HitDataExtra: Any + Send + Sync + fmt::Debug {}
impl<T: Send + Sync + fmt::Debug + Any + 'static> HitDataExtra for T {}
#[derive(Message, Debug, Clone, Reflect)]
#[reflect(Debug, Clone)]
pub struct PointerHits {
pub pointer: prelude::PointerId,
pub picks: Vec<(Entity, HitData)>,
pub order: f32,
}
impl PointerHits {
pub fn new(pointer: prelude::PointerId, picks: Vec<(Entity, HitData)>, order: f32) -> Self {
Self {
pointer,
picks,
order,
}
}
}
#[derive(Debug, Reflect)]
#[reflect(Debug)]
pub struct HitData {
pub camera: Entity,
pub depth: f32,
pub position: Option<Vec3>,
pub normal: Option<Vec3>,
#[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
}
}
impl HitData {
pub fn new(camera: Entity, depth: f32, position: Option<Vec3>, normal: Option<Vec3>) -> Self {
Self {
camera,
depth,
position,
normal,
extra: None,
}
}
pub fn extra_as<T: Any>(&self) -> Option<&T> {
let extra: &dyn Any = self.extra.as_deref()?;
extra.downcast_ref::<T>()
}
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)),
}
}
}
pub mod ray {
use crate::backend::prelude::{PointerId, PointerLocation};
use bevy_camera::{Camera, RenderTarget};
use bevy_ecs::prelude::*;
use bevy_math::Ray3d;
use bevy_platform::collections::{hash_map::Iter, HashMap};
use bevy_reflect::Reflect;
use bevy_transform::prelude::GlobalTransform;
use bevy_window::PrimaryWindow;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Reflect)]
#[reflect(Clone, PartialEq, Hash)]
pub struct RayId {
pub camera: Entity,
pub pointer: PointerId,
}
impl RayId {
pub fn new(camera: Entity, pointer: PointerId) -> Self {
Self { camera, pointer }
}
}
#[derive(Clone, Debug, Default, Resource)]
pub struct RayMap {
pub map: HashMap<RayId, Ray3d>,
}
impl RayMap {
pub fn iter(&self) -> Iter<'_, RayId, Ray3d> {
self.map.iter()
}
pub fn repopulate(
mut ray_map: ResMut<Self>,
primary_window_entity: Query<Entity, With<PrimaryWindow>>,
cameras: Query<(Entity, &Camera, &RenderTarget, &GlobalTransform)>,
pointers: Query<(&PointerId, &PointerLocation)>,
) {
ray_map.map.clear();
for (camera_entity, camera, render_target, 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,
render_target,
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,
render_target: &RenderTarget,
camera_tfm: &GlobalTransform,
pointer_loc: &PointerLocation,
) -> Option<Ray3d> {
let pointer_loc = pointer_loc.location()?;
if !pointer_loc.is_in_viewport(camera, render_target, primary_window_entity) {
return None;
}
camera
.viewport_to_world(camera_tfm, pointer_loc.position)
.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);
}
}