use bevy::app::prelude::*;
use bevy::ecs::prelude::*;
use bevy::picking::{
backend::{ray::RayMap, HitData, PointerHits},
PickSet,
};
use bevy::prelude::PickingPlugin;
use bevy::reflect::prelude::*;
use bevy::render::{prelude::*, view::RenderLayers};
#[derive(Clone, Copy, Reflect)]
pub enum RapierCastVisibility {
Any,
Visible,
}
#[derive(Resource, Reflect)]
#[reflect(Resource, Default)]
pub struct RapierPickingSettings {
pub require_markers: bool,
pub ray_cast_visibility: RapierCastVisibility,
}
impl Default for RapierPickingSettings {
fn default() -> Self {
Self {
require_markers: false,
ray_cast_visibility: RapierCastVisibility::Visible,
}
}
}
#[derive(Debug, Clone, Default, Component, Reflect)]
#[reflect(Component, Default)]
pub struct RapierPickable;
#[derive(Clone, Default)]
pub struct RapierPickingPlugin;
impl Plugin for RapierPickingPlugin {
fn build(&self, app: &mut App) {
app.register_type::<(RapierPickable, RapierPickingSettings)>()
.init_resource::<RapierPickingSettings>()
.add_systems(PreUpdate, update_hits.in_set(PickSet::Backend));
if !app.is_plugin_added::<PickingPlugin>() {
app.add_plugins(PickingPlugin::default());
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn update_hits(
backend_settings: Res<RapierPickingSettings>,
ray_map: Res<RayMap>,
picking_cameras: Query<(&Camera, Option<&RapierPickable>, Option<&RenderLayers>)>,
marked_targets: Query<&RapierPickable>,
culling_query: Query<(Option<&InheritedVisibility>, Option<&ViewVisibility>)>,
layers: Query<&RenderLayers>,
rapier_context: Query<(
&crate::prelude::RapierContextColliders,
&crate::prelude::RapierRigidBodySet,
&crate::prelude::RapierQueryPipeline,
)>,
mut output: EventWriter<PointerHits>,
) {
for (&ray_id, &ray) in ray_map.map.iter() {
let Ok((camera, cam_pickable, cam_layers)) = picking_cameras.get(ray_id.camera) else {
continue;
};
if backend_settings.require_markers && cam_pickable.is_none() {
continue;
}
let order = camera.order as f32;
for (colliders, bodies, query_pipeline) in rapier_context.iter() {
let predicate = |entity| {
let marker_requirement =
!backend_settings.require_markers || marked_targets.get(entity).is_ok();
if !marker_requirement {
return false;
}
let visibility_requirement = match backend_settings.ray_cast_visibility {
RapierCastVisibility::Any => true,
RapierCastVisibility::Visible => {
let is_visible = culling_query
.get(entity)
.map(|(inherited_visibility, _)| {
inherited_visibility.map(|v| v.get()).unwrap_or(false)
})
.unwrap_or(false);
is_visible
}
};
if !visibility_requirement {
return false;
}
let entity_layers = layers.get(entity).to_owned().unwrap_or_default();
let cam_layers = cam_layers.to_owned().unwrap_or_default();
let render_layers_match = cam_layers.intersects(entity_layers);
if !render_layers_match {
return false;
}
true
};
let mut picks = Vec::new();
#[cfg(feature = "dim2")]
query_pipeline.intersections_with_point(
colliders,
bodies,
bevy::math::Vec2::new(ray.origin.x, ray.origin.y),
crate::prelude::QueryFilter::default().predicate(&predicate),
|entity| {
let hit_data = HitData {
camera: ray_id.camera,
position: Some(bevy::math::Vec3::new(ray.origin.x, ray.origin.y, 0.0)),
normal: None,
depth: 0.0,
};
picks.push((entity, hit_data));
true
},
);
#[cfg(feature = "dim3")]
query_pipeline.intersections_with_ray(
colliders,
bodies,
ray.origin,
ray.direction.into(),
f32::MAX,
true,
crate::prelude::QueryFilter::default().predicate(&predicate),
|entity, intersection| {
let hit_data = HitData {
camera: ray_id.camera,
position: Some(intersection.point),
normal: Some(intersection.normal),
depth: intersection.time_of_impact,
};
picks.push((entity, hit_data));
true
},
);
if !picks.is_empty() {
output.write(PointerHits::new(ray_id.pointer, picks, order));
}
}
}
}