Skip to content

Commit

Permalink
make raycast backend markers optional and compare render layers
Browse files Browse the repository at this point in the history
  • Loading branch information
aevyrie committed Aug 23, 2023
1 parent 1937259 commit 42718da
Show file tree
Hide file tree
Showing 17 changed files with 179 additions and 246 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# UNRELEASED

- Changed: the bevy_mod_raycast backend no longer requires markers on the camera
(`RaycastPickCamera`) and targets (`RaycastPickTarget`).
- Added: `RaycastBackendSettings` resource added to allow toggling the requirement for markers with
the bevy_mod_raycast backend at runtime. Enable the `require_markers` field to match behavior of
the plugin to v0.15 and earlier.
- Added: `bevy_mod_raycast` backend now checks render layers when filtering entities.

# 0.15.0

- Update to Bevy 0.11.
Expand Down
2 changes: 1 addition & 1 deletion backends/bevy_picking_raycast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ resolver = "2"
bevy = { version = "0.11", default-features = false, features = [
"bevy_render",
] }
bevy_mod_raycast = "0.12"
bevy_mod_raycast = "0.13.1"
# Local
bevy_picking_core = { path = "../../crates/bevy_picking_core", version = "0.15" }
130 changes: 58 additions & 72 deletions backends/bevy_picking_raycast/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,23 @@
//! A raycasting backend for `bevy_mod_picking` that uses `bevy_mod_raycast` for raycasting.
#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::too_many_arguments, clippy::type_complexity)]
#![deny(missing_docs)]

use bevy::{prelude::*, utils::HashMap, window::PrimaryWindow};
use bevy_mod_raycast::{
system_param::{Raycast, RaycastVisibility},
Ray3d,
};
use bevy::{prelude::*, render::view::RenderLayers, window::PrimaryWindow};
use bevy_mod_raycast::prelude::*;
use bevy_picking_core::backend::prelude::*;

/// Commonly used imports for the [`bevy_picking_raycast`](crate) crate.
pub mod prelude {
pub use crate::{RaycastBackend, RaycastPickCamera, RaycastPickTarget};
pub use crate::RaycastBackend;
}

/// Adds the raycasting picking backend to your app.
#[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),
);
}
/// Runtime settings for the [`RaycastBackend`].
#[derive(Resource, Default)]
pub struct RaycastBackendSettings {
/// When set to `true` raycasting will only happen between cameras marked with
/// [`RaycastPickCamera`] and entities marked with [`RaycastPickTarget`]. Off by default.
pub require_markers: bool,
}

/// This unit struct is used to tag the generic ray casting types
Expand All @@ -45,65 +26,70 @@ impl Plugin for RaycastBackend {
#[derive(Reflect, Clone)]
pub struct RaycastPickingSet;

/// Marks an entity that should be pickable with [`bevy_mod_raycast`] ray casts.
/// Marks an entity that should be pickable with [`bevy_mod_raycast`] ray casts. Only needed if
/// [`RaycastBackendSettings::require_markers`] is set to true.
pub type RaycastPickTarget = bevy_mod_raycast::RaycastMesh<RaycastPickingSet>;

/// Marks a camera that should be used for picking with [`bevy_mod_raycast`].
/// Marks a camera that should be used for picking with [`bevy_mod_raycast`]. Only needed if
/// [`RaycastBackendSettings::require_markers`] is set to true.
#[derive(Debug, Default, Clone, Component, Reflect)]
pub struct RaycastPickCamera {
#[reflect(ignore)]
/// Maps the pointers visible to this [`RaycastPickCamera`] to their corresponding ray. We need
/// to create a map because many pointers may be visible to this camera.
ray_map: HashMap<PointerId, Ray3d>,
}
pub struct RaycastPickCamera;

impl RaycastPickCamera {
/// Returns a map that defines the [`Ray3d`] associated with every [`PointerId`] that is on this
/// [`RaycastPickCamera`]'s render target.
pub fn ray_map(&self) -> &HashMap<PointerId, Ray3d> {
&self.ray_map
/// Adds the raycasting picking backend to your app.
#[derive(Clone)]
pub struct RaycastBackend;
impl Plugin for RaycastBackend {
fn build(&self, app: &mut App) {
app.init_resource::<RaycastBackendSettings>()
.add_systems(PreUpdate, update_hits.in_set(PickSet::Backend));
}
}

/// Builds rays and updates raycasting [`RaycastPickCamera`]s from [`PointerLocation`]s.
pub fn build_rays_from_pointers(
pub fn update_hits(
pointers: Query<(&PointerId, &PointerLocation)>,
primary_window: Query<Entity, With<PrimaryWindow>>,
mut picking_cameras: Query<(&Camera, &GlobalTransform, &mut RaycastPickCamera)>,
picking_cameras: Query<(
Entity,
&Camera,
&GlobalTransform,
Option<&RaycastPickCamera>,
Option<&RenderLayers>,
)>,
pickables: Query<&Pickable>,
marked_targets: Query<&RaycastPickTarget>,
layers: Query<&RenderLayers>,
backend_settings: Res<RaycastBackendSettings>,
mut raycast: Raycast,
mut output_events: EventWriter<PointerHits>,
) {
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);
}
});
}
}

/// Produces [`PointerHits`]s from [`RaycastSource`] intersections.
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() {
for (cam_entity, camera, ray, cam_layers) in picking_cameras
.iter()
.filter(|(_, camera, ..)| pointer_location.is_in_viewport(camera, &primary_window))
.filter(|(.., marker, _)| marker.is_some() || !backend_settings.require_markers)
.filter_map(|(entity, camera, transform, _, layers)| {
Ray3d::from_screenspace(pointer_location.position, camera, transform)
.map(|ray| (entity, camera, ray, layers))
})
{
let settings = bevy_mod_raycast::system_param::RaycastSettings {
visibility: RaycastVisibility::MustBeVisibleAndInView,
filter: &|_| true, // Consider all entities in the raycasting set
filter: &|entity| {
let marker_requirement =
!backend_settings.require_markers || marked_targets.get(entity).is_ok();
let render_layers_match = match (cam_layers, layers.get(entity)) {
(Some(cam_layers), Ok(entity_layers)) => {
cam_layers.intersects(entity_layers)
}
_ => true, // If either `RenderLayers` components is not present, ignore.
};
marker_requirement && render_layers_match
},
early_exit_test: &|entity_hit| {
pickables
.get(entity_hit)
Expand All @@ -125,8 +111,8 @@ fn update_hits(
.collect::<Vec<_>>();
let order = camera.order as f32;
if !picks.is_empty() {
output_events.send(PointerHits::new(pointer, picks, order));
output_events.send(PointerHits::new(*pointer_id, picks, order));
}
}
});
}
}
21 changes: 8 additions & 13 deletions examples/bevy_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ fn setup_3d(
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..Default::default()
},
PickableBundle::default(), // <- Makes the mesh pickable.
RaycastPickTarget::default(), // <- Needed for the raycast backend.
PickableBundle::default(), // <- Makes the mesh pickable.
));
commands.spawn((
PbrBundle {
Expand All @@ -82,8 +81,7 @@ fn setup_3d(
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..Default::default()
},
PickableBundle::default(), // <- Makes the mesh pickable.
RaycastPickTarget::default(), // <- Needed for the raycast backend.
PickableBundle::default(), // <- Makes the mesh pickable.
));
commands.spawn(PointLightBundle {
point_light: PointLight {
Expand All @@ -94,17 +92,14 @@ fn setup_3d(
transform: Transform::from_xyz(4.0, 8.0, -4.0),
..Default::default()
});
commands.spawn((
Camera3dBundle {
transform: Transform::from_xyz(3.0, 3.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
camera: Camera {
order: 1,
..default()
},
commands.spawn((Camera3dBundle {
transform: Transform::from_xyz(3.0, 3.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
camera: Camera {
order: 1,
..default()
},
RaycastPickCamera::default(), // <- Enable picking for this camera
));
..default()
},));
}

trait NewButton {
Expand Down
13 changes: 4 additions & 9 deletions examples/deselection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ fn setup(
..Default::default()
},
PickableBundle::default(),
RaycastPickTarget::default(), // <- Needed for the raycast backend.
NoDeselect, // <- When this entity is clicked, other entities won't be deselected.
))
.remove::<PickSelection>(); // <- Removing this removes the entity's ability to be selected.
Expand All @@ -40,7 +39,6 @@ fn setup(
..Default::default()
},
PickableBundle::default(),
RaycastPickTarget::default(),
));
commands.spawn(PointLightBundle {
transform: Transform::from_xyz(4.0, 8.0, 4.0),
Expand All @@ -51,11 +49,8 @@ fn setup(
},
..Default::default()
});
commands.spawn((
Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
},
RaycastPickCamera::default(),
));
commands.spawn((Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
},));
}
8 changes: 3 additions & 5 deletions examples/drag_and_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn setup(
mut materials: ResMut<Assets<ColorMaterial>>,
) {
// Spawn camera
commands.spawn((Camera2dBundle::default(), RaycastPickCamera::default()));
commands.spawn(Camera2dBundle::default());
// Spawn squares
for x in -2..=2 {
let z = 0.5 + x as f32 * 0.1;
Expand All @@ -36,13 +36,11 @@ fn setup(
material: materials.add(ColorMaterial::from(Color::hsl(0.0, 1.0, z))),
..Default::default()
},
PickableBundle::default(), // <- Makes the mesh pickable.
RaycastPickTarget::default(), // <- Needed for the raycast backend.
PickableBundle::default(), // <- Makes the mesh pickable.
On::<Pointer<DragStart>>::target_insert(Pickable::IGNORE), // Disable picking
On::<Pointer<DragEnd>>::target_insert(Pickable::default()), // Re-enable picking
On::<Pointer<Drag>>::target_component_mut::<Transform>(|drag, transform| {
// Make the square follow the mouse
transform.translation.x += drag.delta.x;
transform.translation.x += drag.delta.x; // Make the square follow the mouse
transform.translation.y -= drag.delta.y;
}),
On::<Pointer<Drop>>::commands_mut(|event, commands| {
Expand Down
17 changes: 6 additions & 11 deletions examples/egui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ fn setup(
material: materials.add(Color::WHITE.into()),
..Default::default()
},
PickableBundle::default(), // <- Makes the mesh pickable.
RaycastPickTarget::default(), // <- Needed for the raycast backend.
PickableBundle::default(), // <- Makes the mesh pickable.
));
commands.spawn((
PbrBundle {
Expand All @@ -53,8 +52,7 @@ fn setup(
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..Default::default()
},
PickableBundle::default(), // <- Makes the mesh pickable.
RaycastPickTarget::default(), // <- Needed for the raycast backend.
PickableBundle::default(), // <- Makes the mesh pickable.
));
commands.spawn(PointLightBundle {
point_light: PointLight {
Expand All @@ -65,11 +63,8 @@ fn setup(
transform: Transform::from_xyz(4.0, 8.0, -4.0),
..Default::default()
});
commands.spawn((
Camera3dBundle {
transform: Transform::from_xyz(3.0, 3.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
},
RaycastPickCamera::default(), // <- Enable picking for this camera
));
commands.spawn((Camera3dBundle {
transform: Transform::from_xyz(3.0, 3.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
},));
}
13 changes: 4 additions & 9 deletions examples/event_listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ fn setup(
..Default::default()
},
PickableBundle::default(),
RaycastPickTarget::default(),
// Callbacks are just exclusive bevy systems that have access to an event data via
// `](bevy_eventlistener::prelude::Listener) and [`ListenerMut`]. This gives
// you full freedom to write normal bevy
Expand Down Expand Up @@ -109,7 +108,6 @@ fn setup(
..Default::default()
},
PickableBundle::default(),
RaycastPickTarget::default(),
));
}
});
Expand All @@ -123,13 +121,10 @@ fn setup(
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..Default::default()
});
commands.spawn((
Camera3dBundle {
transform: Transform::from_xyz(-2.0, 4.5, 5.0).looking_at(Vec3::Y * 2.0, Vec3::Y),
..Default::default()
},
RaycastPickCamera::default(),
));
commands.spawn((Camera3dBundle {
transform: Transform::from_xyz(-2.0, 4.5, 5.0).looking_at(Vec3::Y * 2.0, Vec3::Y),
..Default::default()
},));
}

/// Change the hue of mesh's `StandardMaterial` when the mouse moves vertically over it.
Expand Down
Loading

0 comments on commit 42718da

Please sign in to comment.