#![allow(clippy::type_complexity)]
use bevy::math::{primitives::InfinitePlane3d, Vec2, Vec3Swizzles};
use bevy::transform::components::GlobalTransform;
use bevy::{
camera::visibility::RenderLayers,
ecs::{
component::Component,
entity::{Entity, EntityHashMap},
message::MessageWriter,
query::With,
system::{Query, Res},
},
};
use bevy::{
picking::backend::{ray::RayMap, HitData, PointerHits},
prelude::Camera,
};
use crate::{Dimension, RectrayFrame, RotatedRect, Transform2D};
#[derive(Debug, Component, Default, Clone, Copy, PartialEq, Eq)]
#[require(Transform2D, Dimension)]
pub struct RectrayPickable;
pub fn rectray_picking_backend(
map: Res<RayMap>,
layers: Query<(Option<&RenderLayers>, &Camera)>,
frames: Query<(&GlobalTransform, &RectrayFrame)>,
query: Query<(Entity, &RotatedRect, Option<&RenderLayers>), With<RectrayPickable>>,
mut writer: MessageWriter<PointerHits>,
) {
let mut inverses = EntityHashMap::default();
for (ray_id, ray) in map.iter() {
let mut ray_hits = EntityHashMap::default();
let Ok((layer, cam)) = layers.get(ray_id.camera) else {
continue;
};
let cam_layer = if let Some(layer) = layer {
layer
} else {
&RenderLayers::default()
};
let mut event = PointerHits {
pointer: ray_id.pointer,
picks: Vec::new(),
order: cam.order as f32,
};
for (entity, rect, layers) in query.iter() {
let layer = if let Some(layer) = layers {
layer
} else {
&RenderLayers::default()
};
if !cam_layer.intersects(layer) {
continue;
}
let Some(frame) = rect.frame_entity else {
continue;
};
let ray_hit = ray_hits.entry(frame).or_insert_with(|| {
let (transform, f) = frames.get(frame).ok()?;
if !f.pickable {
return None;
}
let inv = inverses
.entry(frame)
.or_insert_with(|| transform.affine().inverse());
let plane = InfinitePlane3d::new(transform.forward());
let depth = ray.intersect_plane(transform.translation(), plane)?;
Some((
inv.transform_point3(ray.get_point(depth)),
depth,
transform.forward(),
))
});
let Some((ray_hit, depth, forward)) = *ray_hit else {
continue;
};
let local = ray_hit.xy() - rect.center;
let half_size = rect.dimension * rect.scale / 2.0;
let inside = Vec2::from_angle(-rect.rotation)
.rotate(local)
.abs()
.cmple(half_size)
.all();
if inside {
event.picks.push((
entity,
HitData {
camera: ray_id.camera,
depth,
position: Some(ray_hit),
normal: Some(forward.into()),
},
))
}
}
if !event.picks.is_empty() {
writer.write(event);
}
}
}