Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into pr/skylerparr/304
Browse files Browse the repository at this point in the history
  • Loading branch information
aevyrie committed Feb 24, 2024
2 parents c7e80e0 + b551d21 commit 5cb783a
Show file tree
Hide file tree
Showing 23 changed files with 471 additions and 206 deletions.
23 changes: 21 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
# UNRELEASED

- Fixed: replaced uses of `.insert` with `.try_insert`, where they could potentially panic.
- Fixed: replace all `.single` calls with matched `.get_single` calls to avoid crashing in
environments where there is no window available
- Fixed: sprite picking depth is now consistent with other picking backends.
- Fixed: entities with identical depth could be dropped due to the use of a BTreeMap to sort
entities by depth. This has been changed to use a sorted Vec, to allow entities with the same
depth to coexist.
- Fixed: Ray construction now respects DPI / window scale
- Added: `RayMap` resource that contains a `Ray` for every (camera, pointer) pair
- Changed: rapier and bevy_mod_raycast backends use the `RayMap` instead of constructing their own
rays
- Fixed: rapier and bevy_mod_raycast backends use `RenderLayers::default` when a camera is missing
them
- Added: support for `bevy_ui` `UiScale`.
- Fixed: the bevy ui backend now ignores clipped areas of UI nodes.
- Added: `RaycastBackendSettings::raycast_visibility` to support picking hidden meshes.

# 0.17.0

- Update for bevy 0.12
Expand All @@ -15,8 +34,8 @@
- Faster compile times.
- Sprites now support atlases, scale, rotation, and anchors.
- All `egui` widgets, including side panels, are now supported.
- `bevy_mod_raycast` and `bevy_rapier` backends are now even simpler, no longer requiring any
marker components to function.
- `bevy_mod_raycast` and `bevy_rapier` backends are now even simpler, no longer requiring any marker
components to function.
- More flexible picking behavior and `bevy_ui` compatibility with the updated `Pickable` component.
- Better support for cameras settings such as `is_active`, `RenderLayers`, and `show_ui`.

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ bevy = { version = "0.12", default-features = false, features = [
"bevy_gltf",
"bevy_scene",
"bevy_ui",
"bevy_gizmos",
"png",
"ktx2",
"zstd",
Expand Down
2 changes: 1 addition & 1 deletion backends/bevy_picking_egui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub fn update_settings(
.remove::<bevy_picking_selection::NoDeselect>(),
false => commands
.entity(entity)
.insert(bevy_picking_selection::NoDeselect),
.try_insert(bevy_picking_selection::NoDeselect),
};
}
}
Expand Down
2 changes: 0 additions & 2 deletions backends/bevy_picking_rapier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ resolver = "2"
[dependencies]
bevy_app = { version = "0.12", default-features = false }
bevy_ecs = { version = "0.12", default-features = false }
bevy_math = { version = "0.12", default-features = false }
bevy_reflect = { version = "0.12", default-features = false }
bevy_render = { version = "0.12", default-features = false }
bevy_transform = { version = "0.12", default-features = false }
bevy_utils = { version = "0.12", default-features = false }
bevy_window = { version = "0.12", default-features = false }

bevy_rapier3d = "0.23"
Expand Down
108 changes: 43 additions & 65 deletions backends/bevy_picking_rapier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{prelude::*, view::RenderLayers};
use bevy_transform::prelude::*;
use bevy_window::PrimaryWindow;

use bevy_picking_core::backend::prelude::*;
use bevy_rapier3d::prelude::*;
Expand Down Expand Up @@ -74,82 +72,62 @@ pub struct RapierPickable;
/// Raycasts into the scene using [`RapierBackendSettings`] and [`PointerLocation`]s, then outputs
/// [`PointerHits`].
pub fn update_hits(
pointers: Query<(&PointerId, &PointerLocation)>,
primary_window_entity: Query<Entity, With<PrimaryWindow>>,
picking_cameras: Query<(
Entity,
&Camera,
&GlobalTransform,
Option<&RapierPickable>,
Option<&RenderLayers>,
)>,
backend_settings: Res<RapierBackendSettings>,
ray_map: Res<RayMap>,
picking_cameras: Query<(&Camera, Option<&RapierPickable>, Option<&RenderLayers>)>,
pickables: Query<&Pickable>,
marked_targets: Query<&RapierPickable>,
layers: Query<&RenderLayers>,
backend_settings: Res<RapierBackendSettings>,
rapier_context: Option<Res<RapierContext>>,
mut output_events: EventWriter<PointerHits>,
) {
let Some(rapier_context) = rapier_context else {
return;
};

for (pointer_id, pointer_location) in &pointers {
let pointer_location = match pointer_location.location() {
Some(l) => l,
None => continue,
for (&ray_id, &ray) in ray_map.map().iter() {
let Ok((camera, cam_pickable, cam_layers)) = picking_cameras.get(ray_id.camera) else {
continue;
};
for (cam_entity, camera, ray, cam_layers) in picking_cameras
.iter()
.filter(|(_, camera, ..)| {
camera.is_active && pointer_location.is_in_viewport(camera, &primary_window_entity)
})
.filter(|(.., marker, _)| marker.is_some() || !backend_settings.require_markers)
.filter_map(|(entity, camera, transform, _, layers)| {
let mut viewport_pos = pointer_location.position;
if let Some(viewport) = &camera.viewport {
viewport_pos -= viewport.physical_position.as_vec2();
}
camera
.viewport_to_world(transform, viewport_pos)
.map(|ray| (entity, camera, ray, layers))
})
{
if let Some((entity, hit_data)) = rapier_context
.cast_ray_and_get_normal(
ray.origin,
ray.direction,
f32::MAX,
true,
QueryFilter::new().predicate(&|entity| {
let marker_requirement =
!backend_settings.require_markers || marked_targets.get(entity).is_ok();
if backend_settings.require_markers && cam_pickable.is_none() {
continue;
}

// Cameras missing render layers intersect all layers
let cam_layers = cam_layers.copied().unwrap_or(RenderLayers::all());
// Other entities missing render layers are on the default layer 0
let entity_layers = layers.get(entity).copied().unwrap_or_default();
let render_layers_match = cam_layers.intersects(&entity_layers);
let cam_layers = cam_layers.copied().unwrap_or_default();

let pickable = pickables
.get(entity)
.map(|p| *p != Pickable::IGNORE)
.unwrap_or(true);
marker_requirement && render_layers_match && pickable
}),
)
.map(|(entity, hit)| {
let hit_data =
HitData::new(cam_entity, hit.toi, Some(hit.point), Some(hit.normal));
(entity, hit_data)
})
{
output_events.send(PointerHits::new(
*pointer_id,
vec![(entity, hit_data)],
camera.order as f32,
));
}
let predicate = |entity| {
let marker_requirement =
!backend_settings.require_markers || marked_targets.get(entity).is_ok();

// Other entities missing render layers are on the default layer 0
let entity_layers = layers.get(entity).copied().unwrap_or_default();
let render_layers_match = cam_layers.intersects(&entity_layers);

let pickable = pickables
.get(entity)
.map(|p| *p != Pickable::IGNORE)
.unwrap_or(true);
marker_requirement && render_layers_match && pickable
};
if let Some((entity, hit_data)) = rapier_context
.cast_ray_and_get_normal(
ray.origin,
ray.direction,
f32::MAX,
true,
QueryFilter::new().predicate(&predicate),
)
.map(|(entity, hit)| {
let hit_data =
HitData::new(ray_id.camera, hit.toi, Some(hit.point), Some(hit.normal));
(entity, hit_data)
})
{
output_events.send(PointerHits::new(
ray_id.pointer,
vec![(entity, hit_data)],
camera.order as f32,
));
}
}
}
130 changes: 57 additions & 73 deletions backends/bevy_picking_raycast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@
#![deny(missing_docs)]

use bevy_app::prelude::*;
use bevy_core_pipeline::prelude::Camera3d;
use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*;
use bevy_render::{prelude::*, view::RenderLayers};
use bevy_transform::prelude::*;
use bevy_window::{PrimaryWindow, Window};

use bevy_mod_raycast::prelude::*;
use bevy_picking_core::backend::prelude::*;
Expand All @@ -34,13 +31,25 @@ pub mod prelude {
}

/// Runtime settings for the [`RaycastBackend`].
#[derive(Resource, Default, Reflect)]
#[derive(Resource, Reflect)]
#[reflect(Resource, Default)]
pub struct RaycastBackendSettings {
/// When set to `true` raycasting will only happen between cameras and entities marked with
/// [`RaycastPickable`]. Off by default. This setting is provided to give you fine-grained
/// control over which cameras and entities should be used by the raycast backend at runtime.
pub require_markers: bool,
/// When set to Ignore, hidden items can be raycasted against.
/// See [`RaycastSettings::visibility`] for more information.
pub raycast_visibility: RaycastVisibility,
}

impl Default for RaycastBackendSettings {
fn default() -> Self {
Self {
require_markers: false,
raycast_visibility: RaycastVisibility::MustBeVisibleAndInView,
}
}
}

/// Optional. Marks cameras and target entities that should be used in the raycast picking backend.
Expand All @@ -64,84 +73,59 @@ impl Plugin for RaycastBackend {
/// Raycasts into the scene using [`RaycastBackendSettings`] and [`PointerLocation`]s, then outputs
/// [`PointerHits`].
pub fn update_hits(
pointers: Query<(&PointerId, &PointerLocation)>,
primary_window_entity: Query<Entity, With<PrimaryWindow>>,
primary_window: Query<&Window, With<PrimaryWindow>>,
picking_cameras: Query<
(
Entity,
&Camera,
&GlobalTransform,
Option<&RaycastPickable>,
Option<&RenderLayers>,
),
With<Camera3d>,
>,
backend_settings: Res<RaycastBackendSettings>,
ray_map: Res<RayMap>,
picking_cameras: Query<(&Camera, Option<&RaycastPickable>, Option<&RenderLayers>)>,
pickables: Query<&Pickable>,
marked_targets: Query<&RaycastPickable>,
layers: Query<&RenderLayers>,
backend_settings: Res<RaycastBackendSettings>,
mut raycast: Raycast,
mut output_events: EventWriter<PointerHits>,
) {
for (pointer_id, pointer_location) in &pointers {
let pointer_location = match pointer_location.location() {
Some(l) => l,
None => continue,
for (&ray_id, &ray) in ray_map.map().iter() {
let Ok((camera, cam_pickable, cam_layers)) = picking_cameras.get(ray_id.camera) else {
continue;
};
for (cam_entity, camera, ray, cam_layers) in picking_cameras
.iter()
.filter(|(_, camera, ..)| {
camera.is_active && pointer_location.is_in_viewport(camera, &primary_window_entity)
})
.filter(|(.., marker, _)| marker.is_some() || !backend_settings.require_markers)
.filter_map(|(entity, camera, transform, _, layers)| {
Ray3d::from_screenspace(
pointer_location.position,
camera,
transform,
primary_window.single(),
)
.map(|ray| (entity, camera, ray, layers))
})
{
let settings = RaycastSettings {
visibility: RaycastVisibility::MustBeVisibleAndInView,
filter: &|entity| {
let marker_requirement =
!backend_settings.require_markers || marked_targets.get(entity).is_ok();
if backend_settings.require_markers && cam_pickable.is_none() {
continue;
}

let cam_layers = cam_layers.copied().unwrap_or_default();

let settings = RaycastSettings {
visibility: backend_settings.raycast_visibility,
filter: &|entity| {
let marker_requirement =
!backend_settings.require_markers || marked_targets.get(entity).is_ok();

// Cameras missing render layers intersect all layers
let cam_layers = cam_layers.copied().unwrap_or(RenderLayers::all());
// Other entities missing render layers are on the default layer 0
let entity_layers = layers.get(entity).copied().unwrap_or_default();
let render_layers_match = cam_layers.intersects(&entity_layers);
// Other entities missing render layers are on the default layer 0
let entity_layers = layers.get(entity).copied().unwrap_or_default();
let render_layers_match = cam_layers.intersects(&entity_layers);

marker_requirement && render_layers_match
},
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_id, picks, order));
}
marker_requirement && render_layers_match
},
early_exit_test: &|entity_hit| {
pickables
.get(entity_hit)
.is_ok_and(|pickable| pickable.should_block_lower)
},
};
let picks = raycast
.cast_ray(ray.into(), &settings)
.iter()
.map(|(entity, hit)| {
let hit_data = HitData::new(
ray_id.camera,
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(ray_id.pointer, picks, order));
}
}
}
Loading

0 comments on commit 5cb783a

Please sign in to comment.