1#![allow(clippy::type_complexity)]
24#![allow(clippy::too_many_arguments)]
25#![deny(missing_docs)]
26
27use bevy_app::prelude::*;
28use bevy_ecs::prelude::*;
29use bevy_reflect::{std_traits::ReflectDefault, Reflect};
30use bevy_render::{prelude::*, view::RenderLayers};
31
32use bevy_picking_core::backend::prelude::*;
33use bevy_xpbd_3d::prelude::*;
34
35pub use bevy_xpbd_3d;
37
38pub mod prelude {
40 pub use crate::{XpbdBackend, XpbdBackendSettings, XpbdPickable};
41}
42
43#[derive(Clone)]
45pub struct XpbdBackend;
46impl Plugin for XpbdBackend {
47 fn build(&self, app: &mut App) {
48 app.init_resource::<XpbdBackendSettings>()
49 .add_systems(PreUpdate, update_hits.in_set(PickSet::Backend))
50 .register_type::<XpbdBackendSettings>()
51 .register_type::<XpbdPickable>();
52 }
53}
54
55#[derive(Resource, Default, Reflect)]
57#[reflect(Resource, Default)]
58pub struct XpbdBackendSettings {
59 pub require_markers: bool,
63}
64
65#[derive(Debug, Clone, Default, Component, Reflect)]
68#[reflect(Component, Default)]
69pub struct XpbdPickable;
70
71pub fn update_hits(
74 picking_cameras: Query<(&Camera, Option<&XpbdPickable>, Option<&RenderLayers>)>,
75 ray_map: Res<RayMap>,
76 pickables: Query<&Pickable>,
77 marked_targets: Query<&XpbdPickable>,
78 layers: Query<&RenderLayers>,
79 backend_settings: Res<XpbdBackendSettings>,
80 spatial_query: Option<Res<SpatialQueryPipeline>>,
81 mut output_events: EventWriter<PointerHits>,
82) {
83 let Some(spatial_query) = spatial_query else {
84 return;
85 };
86
87 for (&ray_id, &ray) in ray_map.map().iter() {
88 let Ok((camera, cam_pickable, cam_layers)) = picking_cameras.get(ray_id.camera) else {
89 continue;
90 };
91 if backend_settings.require_markers && cam_pickable.is_none() || !camera.is_active {
92 continue;
93 }
94
95 let cam_layers = cam_layers.unwrap_or_default();
96
97 if let Some((entity, hit_data)) = spatial_query
98 .cast_ray_predicate(
99 ray.origin,
100 ray.direction,
101 f32::MAX,
102 true,
103 SpatialQueryFilter::default(),
104 &|entity| {
105 let marker_requirement =
106 !backend_settings.require_markers || marked_targets.get(entity).is_ok();
107
108 let entity_layers = layers.get(entity).unwrap_or_default();
110 let render_layers_match = cam_layers.intersects(entity_layers);
111
112 let is_pickable = pickables
113 .get(entity)
114 .map(|p| *p != Pickable::IGNORE)
115 .unwrap_or(true);
116
117 marker_requirement && render_layers_match && is_pickable
118 },
119 )
120 .map(|ray_hit_data| {
121 let hit_data = HitData::new(
122 ray_id.camera,
123 ray_hit_data.time_of_impact,
124 Some(ray.origin + (ray.direction * ray_hit_data.time_of_impact)),
125 Some(ray_hit_data.normal),
126 );
127 (ray_hit_data.entity, hit_data)
128 })
129 {
130 output_events.send(PointerHits::new(
131 ray_id.pointer,
132 vec![(entity, hit_data)],
133 camera.order as f32,
134 ));
135 }
136 }
137}