#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
#![deny(missing_docs)]
use bevy::{prelude::*, utils::HashMap, window::PrimaryWindow};
use bevy_mod_raycast::{
system_param::{Raycast, RaycastVisibility},
Ray3d,
};
use bevy_picking_core::backend::prelude::*;
pub mod prelude {
pub use crate::{RaycastBackend, RaycastPickCamera, RaycastPickTarget};
}
#[derive(Clone)]
pub struct RaycastBackend;
impl Plugin for RaycastBackend {
fn build(&self, app: &mut App) {
app.add_systems(
First,
(build_rays_from_pointers)
.chain()
.in_set(PickSet::PostInput),
)
.add_systems(
PreUpdate,
(
bevy_mod_raycast::update_raycast::<RaycastPickingSet>,
update_hits,
)
.chain()
.in_set(PickSet::Backend),
);
}
}
#[derive(Reflect, Clone)]
pub struct RaycastPickingSet;
pub type RaycastPickTarget = bevy_mod_raycast::RaycastMesh<RaycastPickingSet>;
#[derive(Debug, Default, Clone, Component, Reflect)]
pub struct RaycastPickCamera {
#[reflect(ignore)]
ray_map: HashMap<PointerId, Ray3d>,
}
impl RaycastPickCamera {
pub fn ray_map(&self) -> &HashMap<PointerId, Ray3d> {
&self.ray_map
}
}
pub fn build_rays_from_pointers(
pointers: Query<(&PointerId, &PointerLocation)>,
primary_window: Query<Entity, With<PrimaryWindow>>,
mut picking_cameras: Query<(&Camera, &GlobalTransform, &mut RaycastPickCamera)>,
) {
picking_cameras.iter_mut().for_each(|(_, _, mut pick_cam)| {
pick_cam.ray_map.clear();
});
for (pointer_id, pointer_location) in &pointers {
let pointer_location = match pointer_location.location() {
Some(l) => l,
None => continue,
};
picking_cameras
.iter_mut()
.filter(|(camera, _, _)| pointer_location.is_in_viewport(camera, &primary_window))
.for_each(|(camera, transform, mut source)| {
if let Some(ray) =
Ray3d::from_screenspace(pointer_location.position, camera, transform)
{
source.ray_map.insert(*pointer_id, ray);
}
});
}
}
fn update_hits(
pick_cameras: Query<(Entity, &Camera, &RaycastPickCamera)>,
mut raycast: Raycast<RaycastPickingSet>,
mut output_events: EventWriter<PointerHits>,
pickables: Query<&Pickable>,
) {
pick_cameras.iter().for_each(|(cam_entity, camera, map)| {
for (&pointer, &ray) in map.ray_map().iter() {
let settings = bevy_mod_raycast::system_param::RaycastSettings {
visibility: RaycastVisibility::MustBeVisibleAndInView,
filter: &|_| true, early_exit_test: &|entity_hit| {
pickables
.get(entity_hit)
.is_ok_and(|pickable| pickable.should_block_lower)
},
};
let picks = raycast
.cast_ray(ray, &settings)
.iter()
.map(|(entity, hit)| {
let hit_data = HitData::new(
cam_entity,
hit.distance(),
Some(hit.position()),
Some(hit.normal()),
);
(*entity, hit_data)
})
.collect::<Vec<_>>();
let order = camera.order as f32;
if !picks.is_empty() {
output_events.send(PointerHits::new(pointer, picks, order));
}
}
});
}