From 5c15946bb29b7391d703a02ade127940c1cd5da4 Mon Sep 17 00:00:00 2001 From: sam edelsten Date: Thu, 27 Feb 2025 10:09:03 +0000 Subject: [PATCH 1/3] use RayMap in sprite picking --- crates/bevy_sprite/src/picking_backend.rs | 85 ++++++++++-------- examples/hello_world.rs | 100 ++++++++++++++++++++-- 2 files changed, 145 insertions(+), 40 deletions(-) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 332327defacd3..8adb1c2debad5 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -16,7 +16,7 @@ use bevy_image::prelude::*; use bevy_math::{prelude::*, FloatExt}; use bevy_picking::backend::prelude::*; use bevy_reflect::prelude::*; -use bevy_render::prelude::*; +use bevy_render::{prelude::*, view::RenderLayers}; use bevy_transform::prelude::*; use bevy_window::PrimaryWindow; @@ -78,15 +78,14 @@ impl Plugin for SpritePickingPlugin { } fn sprite_picking( - pointers: Query<(&PointerId, &PointerLocation)>, cameras: Query<( Entity, &Camera, &GlobalTransform, &Projection, + Option<&RenderLayers>, Has, )>, - primary_window: Query>, images: Res>, texture_atlas_layout: Res>, settings: Res, @@ -96,14 +95,18 @@ fn sprite_picking( &GlobalTransform, &Pickable, &ViewVisibility, + Option<&RenderLayers>, )>, mut output: EventWriter, + ray_map: Res, + pointer_locations: Query<(&PointerId, &PointerLocation)>, + primary_window: Query>, ) { let mut sorted_sprites: Vec<_> = sprite_query .iter() - .filter_map(|(entity, sprite, transform, pickable, vis)| { + .filter_map(|(entity, sprite, transform, pickable, vis, layers)| { if !transform.affine().is_nan() && vis.get() { - Some((entity, sprite, transform, pickable)) + Some((entity, sprite, transform, pickable, layers)) } else { None } @@ -111,56 +114,70 @@ fn sprite_picking( .collect(); // radsort is a stable radix sort that performed better than `slice::sort_by_key` - radsort::sort_by_key(&mut sorted_sprites, |(_, _, transform, _)| { + radsort::sort_by_key(&mut sorted_sprites, |(_, _, transform, _, _)| { -transform.translation().z }); - let primary_window = primary_window.get_single().ok(); - for (pointer, location) in pointers.iter().filter_map(|(pointer, pointer_location)| { - pointer_location.location().map(|loc| (pointer, loc)) - }) { - let mut blocked = false; - let Some((cam_entity, camera, cam_transform, Projection::Orthographic(cam_ortho), _)) = - cameras - .iter() - .filter(|(_, camera, _, _, cam_can_pick)| { - let marker_requirement = !settings.require_markers || *cam_can_pick; - camera.is_active && marker_requirement - }) - .find(|(_, camera, _, _, _)| { - camera - .target - .normalize(primary_window) - .is_some_and(|x| x == location.target) - }) + for (ray_id, ray) in ray_map.iter() { + let Ok(( + cam_entity, + camera, + cam_transform, + Projection::Orthographic(cam_ortho), + cam_render_layers, + cam_can_pick, + )) = cameras.get(ray_id.camera) else { continue; }; - let viewport_pos = camera - .logical_viewport_rect() - .map(|v| v.min) - .unwrap_or_default(); - let pos_in_viewport = location.position - viewport_pos; + let marker_requirement = !settings.require_markers || cam_can_pick; + if !(camera.is_active && marker_requirement) { + continue; + } - let Ok(cursor_ray_world) = camera.viewport_to_world(cam_transform, pos_in_viewport) else { + let Some(location) = pointer_locations.iter().find_map(|(id, loc)| { + if *id == ray_id.pointer { + return loc.location.as_ref(); + } + None + }) else { continue; }; + + if !camera + .target + .normalize(primary_window) + .is_some_and(|x| x == location.target) + { + continue; + } + let cursor_ray_len = cam_ortho.far - cam_ortho.near; - let cursor_ray_end = cursor_ray_world.origin + cursor_ray_world.direction * cursor_ray_len; + let cursor_ray_end = ray.origin + ray.direction * cursor_ray_len; + + let mut blocked = false; let picks: Vec<(Entity, HitData)> = sorted_sprites .iter() .copied() - .filter_map(|(entity, sprite, sprite_transform, pickable)| { + .filter_map(|(entity, sprite, sprite_transform, pickable, layers)| { if blocked { return None; } + if let Some(cam_render_layers) = cam_render_layers { + if let Some(layers) = layers { + if !cam_render_layers.intersects(layers) { + return None; + } + } + } + // Transform cursor line segment to sprite coordinate system let world_to_sprite = sprite_transform.affine().inverse(); - let cursor_start_sprite = world_to_sprite.transform_point3(cursor_ray_world.origin); + let cursor_start_sprite = world_to_sprite.transform_point3(ray.origin); let cursor_end_sprite = world_to_sprite.transform_point3(cursor_ray_end); // Find where the cursor segment intersects the plane Z=0 (which is the sprite's @@ -244,6 +261,6 @@ fn sprite_picking( .collect(); let order = camera.order as f32; - output.write(PointerHits::new(*pointer, picks, order)); + output.write(PointerHits::new(ray_id.pointer, picks, order)); } } diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 40f55c10c31bb..3f593815add65 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,11 +1,99 @@ -//! A minimal example that outputs "hello world" - -use bevy::prelude::*; +//! blah +use bevy::{ + color::palettes::tailwind::CYAN_300, + prelude::*, + render::{camera::ScalingMode, view::RenderLayers}, +}; +// use bevy_inspector_egui::quick::WorldInspectorPlugin; fn main() { - App::new().add_systems(Update, hello_world_system).run(); + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + // .add_plugins(WorldInspectorPlugin::new()) + .run(); } -fn hello_world_system() { - println!("hello world"); +/// Set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands + .spawn(( + Mesh2d(meshes.add(RegularPolygon::new(100.0, 6)).clone()), + MeshMaterial2d(materials.add(Color::srgb(0.3, 0.5, 0.3)).clone()), + Transform::from_xyz(100.0, 100., 1.0), + RenderLayers::from_layers(&[1]), + Sprite::sized(Vec2::new(100., 100.)), + )) + .observe(on_hover) + .observe(on_out); + + commands + .spawn(( + Mesh2d(meshes.add(RegularPolygon::new(50., 5))), + MeshMaterial2d(materials.add(Color::srgb(0.8, 0.7, 0.6))), + Transform::from_xyz(-100.0, 0.5, 2.0), + RenderLayers::from_layers(&[0]), + Sprite::sized(Vec2::new(50., 50.)), + )) + .observe(on_hover) + .observe(on_out); + + // Main Camera + let mut projection = OrthographicProjection::default_2d(); + projection.scaling_mode = ScalingMode::FixedHorizontal { + viewport_width: 300., + }; + commands.spawn(( + Camera2d, + Transform::from_xyz(-2.0, 2.5, 5.0), + Projection::from(projection), + Camera { + order: 0, + ..default() + }, + RenderLayers::from_layers(&[0]), + )); + + // Overlay camera + let mut projection = OrthographicProjection::default_2d(); + projection.scaling_mode = ScalingMode::WindowSize; + commands.spawn(( + Camera2d, + Camera { + order: 1, + ..default() + }, + Projection::from(projection), + RenderLayers::from_layers(&[1]), + Transform::from_xyz(-2.0, 2.5, 10.0), + )); +} + +fn on_hover( + hover: Trigger>, + mut mesh: Query<&MeshMaterial2d>, + mut materials: ResMut>, +) { + if let Ok(material) = mesh.get_mut(hover.target) { + info!("found material"); + if let Some(material) = materials.get_mut(material) { + material.color = Color::WHITE; + } + } +} +fn on_out( + hover: Trigger>, + mut mesh: Query<&MeshMaterial2d>, + mut materials: ResMut>, +) { + info!("observiing"); + if let Ok(material) = mesh.get_mut(hover.target) { + if let Some(material) = materials.get_mut(material) { + material.color = Color::from(CYAN_300); + } + } } From 4c539a7d42a703db3efb305d5d694e78a95933ef Mon Sep 17 00:00:00 2001 From: sam edelsten Date: Thu, 27 Feb 2025 10:41:41 +0000 Subject: [PATCH 2/3] restore hello world example --- examples/hello_world.rs | 100 +++------------------------------------- 1 file changed, 6 insertions(+), 94 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 3f593815add65..40f55c10c31bb 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,99 +1,11 @@ -//! blah -use bevy::{ - color::palettes::tailwind::CYAN_300, - prelude::*, - render::{camera::ScalingMode, view::RenderLayers}, -}; -// use bevy_inspector_egui::quick::WorldInspectorPlugin; +//! A minimal example that outputs "hello world" -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .add_systems(Startup, setup) - // .add_plugins(WorldInspectorPlugin::new()) - .run(); -} - -/// Set up a simple 3D scene -fn setup( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - commands - .spawn(( - Mesh2d(meshes.add(RegularPolygon::new(100.0, 6)).clone()), - MeshMaterial2d(materials.add(Color::srgb(0.3, 0.5, 0.3)).clone()), - Transform::from_xyz(100.0, 100., 1.0), - RenderLayers::from_layers(&[1]), - Sprite::sized(Vec2::new(100., 100.)), - )) - .observe(on_hover) - .observe(on_out); - - commands - .spawn(( - Mesh2d(meshes.add(RegularPolygon::new(50., 5))), - MeshMaterial2d(materials.add(Color::srgb(0.8, 0.7, 0.6))), - Transform::from_xyz(-100.0, 0.5, 2.0), - RenderLayers::from_layers(&[0]), - Sprite::sized(Vec2::new(50., 50.)), - )) - .observe(on_hover) - .observe(on_out); +use bevy::prelude::*; - // Main Camera - let mut projection = OrthographicProjection::default_2d(); - projection.scaling_mode = ScalingMode::FixedHorizontal { - viewport_width: 300., - }; - commands.spawn(( - Camera2d, - Transform::from_xyz(-2.0, 2.5, 5.0), - Projection::from(projection), - Camera { - order: 0, - ..default() - }, - RenderLayers::from_layers(&[0]), - )); - - // Overlay camera - let mut projection = OrthographicProjection::default_2d(); - projection.scaling_mode = ScalingMode::WindowSize; - commands.spawn(( - Camera2d, - Camera { - order: 1, - ..default() - }, - Projection::from(projection), - RenderLayers::from_layers(&[1]), - Transform::from_xyz(-2.0, 2.5, 10.0), - )); +fn main() { + App::new().add_systems(Update, hello_world_system).run(); } -fn on_hover( - hover: Trigger>, - mut mesh: Query<&MeshMaterial2d>, - mut materials: ResMut>, -) { - if let Ok(material) = mesh.get_mut(hover.target) { - info!("found material"); - if let Some(material) = materials.get_mut(material) { - material.color = Color::WHITE; - } - } -} -fn on_out( - hover: Trigger>, - mut mesh: Query<&MeshMaterial2d>, - mut materials: ResMut>, -) { - info!("observiing"); - if let Ok(material) = mesh.get_mut(hover.target) { - if let Some(material) = materials.get_mut(material) { - material.color = Color::from(CYAN_300); - } - } +fn hello_world_system() { + println!("hello world"); } From bc7db8c84123adc4092d74e03c546ae94edd886f Mon Sep 17 00:00:00 2001 From: sam edelsten Date: Thu, 27 Feb 2025 11:00:18 +0000 Subject: [PATCH 3/3] simplify bool for clippy --- crates/bevy_sprite/src/picking_backend.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 8adb1c2debad5..762d2c0d6c53a 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -146,10 +146,10 @@ fn sprite_picking( continue; }; - if !camera + if camera .target .normalize(primary_window) - .is_some_and(|x| x == location.target) + .is_none_or(|x| x != location.target) { continue; }