Picking example touchups (#16129)
# Objective - Cleanup pass to make the examples a bit more succinct, focusing on the topic at hand. - Added drag rotation to make the picking examples more interesting, and to demonstrate a simple use case.
This commit is contained in:
		
							parent
							
								
									8b636f32cf
								
							
						
					
					
						commit
						03372e590d
					
				| @ -21,38 +21,17 @@ | |||||||
| 
 | 
 | ||||||
| use std::f32::consts::PI; | use std::f32::consts::PI; | ||||||
| 
 | 
 | ||||||
| use bevy::{ | use bevy::{color::palettes::tailwind::*, picking::pointer::PointerInteraction, prelude::*}; | ||||||
|     color::palettes::{ |  | ||||||
|         css::{PINK, RED, SILVER}, |  | ||||||
|         tailwind::{CYAN_300, YELLOW_300}, |  | ||||||
|     }, |  | ||||||
|     picking::backend::PointerHits, |  | ||||||
|     prelude::*, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| fn main() { | fn main() { | ||||||
|     App::new() |     App::new() | ||||||
|         .add_plugins(( |         // MeshPickingPlugin is not a default plugin
 | ||||||
|             DefaultPlugins, |         .add_plugins((DefaultPlugins, MeshPickingPlugin)) | ||||||
|             // The mesh picking plugin is not enabled by default, because raycasting against all
 |         .add_systems(Startup, setup_scene) | ||||||
|             // meshes has a performance cost.
 |         .add_systems(Update, (draw_mesh_intersections, rotate)) | ||||||
|             MeshPickingPlugin, |  | ||||||
|         )) |  | ||||||
|         .init_resource::<SceneMaterials>() |  | ||||||
|         .add_systems(Startup, setup) |  | ||||||
|         .add_systems(Update, (on_mesh_hover, rotate)) |  | ||||||
|         .run(); |         .run(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Materials for the scene
 |  | ||||||
| #[derive(Resource, Default)] |  | ||||||
| struct SceneMaterials { |  | ||||||
|     pub white: Handle<StandardMaterial>, |  | ||||||
|     pub ground: Handle<StandardMaterial>, |  | ||||||
|     pub hover: Handle<StandardMaterial>, |  | ||||||
|     pub pressed: Handle<StandardMaterial>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// A marker component for our shapes so we can query them separately from the ground plane.
 | /// A marker component for our shapes so we can query them separately from the ground plane.
 | ||||||
| #[derive(Component)] | #[derive(Component)] | ||||||
| struct Shape; | struct Shape; | ||||||
| @ -61,17 +40,16 @@ const SHAPES_X_EXTENT: f32 = 14.0; | |||||||
| const EXTRUSION_X_EXTENT: f32 = 16.0; | const EXTRUSION_X_EXTENT: f32 = 16.0; | ||||||
| const Z_EXTENT: f32 = 5.0; | const Z_EXTENT: f32 = 5.0; | ||||||
| 
 | 
 | ||||||
| fn setup( | fn setup_scene( | ||||||
|     mut commands: Commands, |     mut commands: Commands, | ||||||
|     mut meshes: ResMut<Assets<Mesh>>, |     mut meshes: ResMut<Assets<Mesh>>, | ||||||
|     mut materials: ResMut<Assets<StandardMaterial>>, |     mut materials: ResMut<Assets<StandardMaterial>>, | ||||||
|     mut scene_materials: ResMut<SceneMaterials>, |  | ||||||
| ) { | ) { | ||||||
|     // Set up the materials.
 |     // Set up the materials.
 | ||||||
|     scene_materials.white = materials.add(Color::WHITE); |     let white_matl = materials.add(Color::WHITE); | ||||||
|     scene_materials.ground = materials.add(Color::from(SILVER)); |     let ground_matl = materials.add(Color::from(GRAY_300)); | ||||||
|     scene_materials.hover = materials.add(Color::from(CYAN_300)); |     let hover_matl = materials.add(Color::from(CYAN_300)); | ||||||
|     scene_materials.pressed = materials.add(Color::from(YELLOW_300)); |     let pressed_matl = materials.add(Color::from(YELLOW_300)); | ||||||
| 
 | 
 | ||||||
|     let shapes = [ |     let shapes = [ | ||||||
|         meshes.add(Cuboid::default()), |         meshes.add(Cuboid::default()), | ||||||
| @ -102,7 +80,7 @@ fn setup( | |||||||
|         commands |         commands | ||||||
|             .spawn(( |             .spawn(( | ||||||
|                 Mesh3d(shape), |                 Mesh3d(shape), | ||||||
|                 MeshMaterial3d(scene_materials.white.clone()), |                 MeshMaterial3d(white_matl.clone()), | ||||||
|                 Transform::from_xyz( |                 Transform::from_xyz( | ||||||
|                     -SHAPES_X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * SHAPES_X_EXTENT, |                     -SHAPES_X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * SHAPES_X_EXTENT, | ||||||
|                     2.0, |                     2.0, | ||||||
| @ -111,10 +89,11 @@ fn setup( | |||||||
|                 .with_rotation(Quat::from_rotation_x(-PI / 4.)), |                 .with_rotation(Quat::from_rotation_x(-PI / 4.)), | ||||||
|                 Shape, |                 Shape, | ||||||
|             )) |             )) | ||||||
|             .observe(on_pointer_over) |             .observe(update_material_on::<Pointer<Over>>(hover_matl.clone())) | ||||||
|             .observe(on_pointer_out) |             .observe(update_material_on::<Pointer<Out>>(white_matl.clone())) | ||||||
|             .observe(on_pointer_down) |             .observe(update_material_on::<Pointer<Down>>(pressed_matl.clone())) | ||||||
|             .observe(on_pointer_up); |             .observe(update_material_on::<Pointer<Up>>(hover_matl.clone())) | ||||||
|  |             .observe(rotate_on_drag); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let num_extrusions = extrusions.len(); |     let num_extrusions = extrusions.len(); | ||||||
| @ -123,7 +102,7 @@ fn setup( | |||||||
|         commands |         commands | ||||||
|             .spawn(( |             .spawn(( | ||||||
|                 Mesh3d(shape), |                 Mesh3d(shape), | ||||||
|                 MeshMaterial3d(scene_materials.white.clone()), |                 MeshMaterial3d(white_matl.clone()), | ||||||
|                 Transform::from_xyz( |                 Transform::from_xyz( | ||||||
|                     -EXTRUSION_X_EXTENT / 2. |                     -EXTRUSION_X_EXTENT / 2. | ||||||
|                         + i as f32 / (num_extrusions - 1) as f32 * EXTRUSION_X_EXTENT, |                         + i as f32 / (num_extrusions - 1) as f32 * EXTRUSION_X_EXTENT, | ||||||
| @ -133,17 +112,18 @@ fn setup( | |||||||
|                 .with_rotation(Quat::from_rotation_x(-PI / 4.)), |                 .with_rotation(Quat::from_rotation_x(-PI / 4.)), | ||||||
|                 Shape, |                 Shape, | ||||||
|             )) |             )) | ||||||
|             .observe(on_pointer_over) |             .observe(update_material_on::<Pointer<Over>>(hover_matl.clone())) | ||||||
|             .observe(on_pointer_out) |             .observe(update_material_on::<Pointer<Out>>(white_matl.clone())) | ||||||
|             .observe(on_pointer_down) |             .observe(update_material_on::<Pointer<Down>>(pressed_matl.clone())) | ||||||
|             .observe(on_pointer_up); |             .observe(update_material_on::<Pointer<Up>>(hover_matl.clone())) | ||||||
|  |             .observe(rotate_on_drag); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Disable picking for the ground plane.
 |     // Ground
 | ||||||
|     commands.spawn(( |     commands.spawn(( | ||||||
|         Mesh3d(meshes.add(Plane3d::default().mesh().size(50.0, 50.0).subdivisions(10))), |         Mesh3d(meshes.add(Plane3d::default().mesh().size(50.0, 50.0).subdivisions(10))), | ||||||
|         MeshMaterial3d(scene_materials.ground.clone()), |         MeshMaterial3d(ground_matl.clone()), | ||||||
|         PickingBehavior::IGNORE, |         PickingBehavior::IGNORE, // Disable picking for the ground plane.
 | ||||||
|     )); |     )); | ||||||
| 
 | 
 | ||||||
|     // Light
 |     // Light
 | ||||||
| @ -166,7 +146,7 @@ fn setup( | |||||||
| 
 | 
 | ||||||
|     // Instructions
 |     // Instructions
 | ||||||
|     commands.spawn(( |     commands.spawn(( | ||||||
|         Text::new("Hover over the shapes to pick them"), |         Text::new("Hover over the shapes to pick them\nDrag to rotate"), | ||||||
|         Node { |         Node { | ||||||
|             position_type: PositionType::Absolute, |             position_type: PositionType::Absolute, | ||||||
|             top: Val::Px(12.0), |             top: Val::Px(12.0), | ||||||
| @ -176,80 +156,42 @@ fn setup( | |||||||
|     )); |     )); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Changes the material when the pointer is over the mesh.
 | /// Returns an observer that updates the entity's material to the one specified.
 | ||||||
| fn on_pointer_over( | fn update_material_on<E>( | ||||||
|     trigger: Trigger<Pointer<Over>>, |     new_material: Handle<StandardMaterial>, | ||||||
|     scene_materials: Res<SceneMaterials>, | ) -> impl Fn(Trigger<E>, Query<&mut MeshMaterial3d<StandardMaterial>>) { | ||||||
|     mut query: Query<&mut MeshMaterial3d<StandardMaterial>>, |     // An observer closure that captures `new_material`. We do this to avoid needing to write four
 | ||||||
| ) { |     // versions of this observer, each triggered by a different event and with a different hardcoded
 | ||||||
|     if let Ok(mut material) = query.get_mut(trigger.entity()) { |     // material. Instead, the event type is a generic, and the material is passed in.
 | ||||||
|         material.0 = scene_materials.hover.clone(); |     move |trigger, mut query| { | ||||||
|  |         if let Ok(mut material) = query.get_mut(trigger.entity()) { | ||||||
|  |             material.0 = new_material.clone(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Resets the material when the pointer leaves the mesh.
 | /// A system that draws hit indicators for every pointer.
 | ||||||
| fn on_pointer_out( | fn draw_mesh_intersections(pointers: Query<&PointerInteraction>, mut gizmos: Gizmos) { | ||||||
|     trigger: Trigger<Pointer<Out>>, |     for (point, normal) in pointers | ||||||
|     scene_materials: Res<SceneMaterials>, |         .iter() | ||||||
|     mut query: Query<&mut MeshMaterial3d<StandardMaterial>>, |         .filter_map(|interaction| interaction.get_nearest_hit()) | ||||||
| ) { |         .filter_map(|(_entity, hit)| hit.position.zip(hit.normal)) | ||||||
|     if let Ok(mut material) = query.get_mut(trigger.entity()) { |     { | ||||||
|         material.0 = scene_materials.white.clone(); |         gizmos.sphere(point, 0.05, RED_500); | ||||||
|  |         gizmos.arrow(point, point + normal.normalize() * 0.5, PINK_100); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Changes the material when the pointer is pressed.
 | /// A system that rotates all shapes.
 | ||||||
| fn on_pointer_down( |  | ||||||
|     trigger: Trigger<Pointer<Down>>, |  | ||||||
|     scene_materials: Res<SceneMaterials>, |  | ||||||
|     mut query: Query<&mut MeshMaterial3d<StandardMaterial>>, |  | ||||||
| ) { |  | ||||||
|     if let Ok(mut material) = query.get_mut(trigger.entity()) { |  | ||||||
|         material.0 = scene_materials.pressed.clone(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// Resets the material when the pointer is released.
 |  | ||||||
| fn on_pointer_up( |  | ||||||
|     trigger: Trigger<Pointer<Up>>, |  | ||||||
|     scene_materials: Res<SceneMaterials>, |  | ||||||
|     mut query: Query<&mut MeshMaterial3d<StandardMaterial>>, |  | ||||||
| ) { |  | ||||||
|     if let Ok(mut material) = query.get_mut(trigger.entity()) { |  | ||||||
|         material.0 = scene_materials.hover.clone(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// Draws the closest point of intersection for pointer hits.
 |  | ||||||
| fn on_mesh_hover( |  | ||||||
|     mut pointer_hits: EventReader<PointerHits>, |  | ||||||
|     meshes: Query<Entity, With<Mesh3d>>, |  | ||||||
|     mut gizmos: Gizmos, |  | ||||||
| ) { |  | ||||||
|     for hit in pointer_hits.read() { |  | ||||||
|         // Get the first mesh hit.
 |  | ||||||
|         // The hits are sorted by distance from the camera, so this is the closest hit.
 |  | ||||||
|         let Some(closest_hit) = hit |  | ||||||
|             .picks |  | ||||||
|             .iter() |  | ||||||
|             .filter_map(|(entity, hit)| meshes.get(*entity).map(|_| hit).ok()) |  | ||||||
|             .next() |  | ||||||
|         else { |  | ||||||
|             continue; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         let (Some(point), Some(normal)) = (closest_hit.position, closest_hit.normal) else { |  | ||||||
|             return; |  | ||||||
|         }; |  | ||||||
| 
 |  | ||||||
|         gizmos.sphere(point, 0.05, RED); |  | ||||||
|         gizmos.arrow(point, point + normal * 0.5, PINK); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// Rotates the shapes.
 |  | ||||||
| fn rotate(mut query: Query<&mut Transform, With<Shape>>, time: Res<Time>) { | fn rotate(mut query: Query<&mut Transform, With<Shape>>, time: Res<Time>) { | ||||||
|     for mut transform in &mut query { |     for mut transform in &mut query { | ||||||
|         transform.rotate_y(time.delta_secs() / 2.); |         transform.rotate_y(time.delta_secs() / 2.); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /// An observer to rotate an entity when it is dragged
 | ||||||
|  | fn rotate_on_drag(drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>) { | ||||||
|  |     let mut transform = transforms.get_mut(drag.entity()).unwrap(); | ||||||
|  |     transform.rotate_y(drag.delta.x * 0.02); | ||||||
|  |     transform.rotate_x(drag.delta.y * 0.02); | ||||||
|  | } | ||||||
|  | |||||||
| @ -4,16 +4,13 @@ use bevy::prelude::*; | |||||||
| 
 | 
 | ||||||
| fn main() { | fn main() { | ||||||
|     App::new() |     App::new() | ||||||
|         .add_plugins(( |         // Unlike UiPickingPlugin, MeshPickingPlugin is not a default plugin
 | ||||||
|             DefaultPlugins, |         .add_plugins((DefaultPlugins, MeshPickingPlugin)) | ||||||
|             MeshPickingPlugin, // Needed for mesh picking, not added by default
 |         .add_systems(Startup, setup_scene) | ||||||
|         )) |  | ||||||
|         .add_systems(Startup, setup) |  | ||||||
|         .run(); |         .run(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// set up a simple 3D scene
 | fn setup_scene( | ||||||
| fn setup( |  | ||||||
|     mut commands: Commands, |     mut commands: Commands, | ||||||
|     mut meshes: ResMut<Assets<Mesh>>, |     mut meshes: ResMut<Assets<Mesh>>, | ||||||
|     mut materials: ResMut<Assets<StandardMaterial>>, |     mut materials: ResMut<Assets<StandardMaterial>>, | ||||||
| @ -28,26 +25,28 @@ fn setup( | |||||||
|                 ..default() |                 ..default() | ||||||
|             }, |             }, | ||||||
|         )) |         )) | ||||||
|         .observe(on_pointer_click_spawn_cube) |         .observe(on_click_spawn_cube) | ||||||
|         .observe( |         .observe( | ||||||
|             |evt: Trigger<Pointer<Out>>, mut texts: Query<&mut TextColor>| { |             |out: Trigger<Pointer<Out>>, mut texts: Query<&mut TextColor>| { | ||||||
|                 let mut color = texts.get_mut(evt.entity()).unwrap(); |                 let mut text_color = texts.get_mut(out.entity()).unwrap(); | ||||||
|                 color.0 = Color::WHITE; |                 text_color.0 = Color::WHITE; | ||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|         .observe( |         .observe( | ||||||
|             |evt: Trigger<Pointer<Over>>, mut texts: Query<&mut TextColor>| { |             |over: Trigger<Pointer<Over>>, mut texts: Query<&mut TextColor>| { | ||||||
|                 let mut color = texts.get_mut(evt.entity()).unwrap(); |                 let mut color = texts.get_mut(over.entity()).unwrap(); | ||||||
|                 color.0 = bevy::color::palettes::tailwind::CYAN_400.into(); |                 color.0 = bevy::color::palettes::tailwind::CYAN_400.into(); | ||||||
|             }, |             }, | ||||||
|         ); |         ); | ||||||
|     // circular base
 | 
 | ||||||
|  |     // Base
 | ||||||
|     commands.spawn(( |     commands.spawn(( | ||||||
|         Mesh3d(meshes.add(Circle::new(4.0))), |         Mesh3d(meshes.add(Circle::new(4.0))), | ||||||
|         MeshMaterial3d(materials.add(Color::WHITE)), |         MeshMaterial3d(materials.add(Color::WHITE)), | ||||||
|         Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)), |         Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)), | ||||||
|     )); |     )); | ||||||
|     // light
 | 
 | ||||||
|  |     // Light
 | ||||||
|     commands.spawn(( |     commands.spawn(( | ||||||
|         PointLight { |         PointLight { | ||||||
|             shadows_enabled: true, |             shadows_enabled: true, | ||||||
| @ -55,14 +54,15 @@ fn setup( | |||||||
|         }, |         }, | ||||||
|         Transform::from_xyz(4.0, 8.0, 4.0), |         Transform::from_xyz(4.0, 8.0, 4.0), | ||||||
|     )); |     )); | ||||||
|     // camera
 | 
 | ||||||
|  |     // Camera
 | ||||||
|     commands.spawn(( |     commands.spawn(( | ||||||
|         Camera3d::default(), |         Camera3d::default(), | ||||||
|         Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), |         Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), | ||||||
|     )); |     )); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn on_pointer_click_spawn_cube( | fn on_click_spawn_cube( | ||||||
|     _click: Trigger<Pointer<Click>>, |     _click: Trigger<Pointer<Click>>, | ||||||
|     mut commands: Commands, |     mut commands: Commands, | ||||||
|     mut meshes: ResMut<Assets<Mesh>>, |     mut meshes: ResMut<Assets<Mesh>>, | ||||||
| @ -75,14 +75,14 @@ fn on_pointer_click_spawn_cube( | |||||||
|             MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))), |             MeshMaterial3d(materials.add(Color::srgb_u8(124, 144, 255))), | ||||||
|             Transform::from_xyz(0.0, 0.25 + 0.55 * *num as f32, 0.0), |             Transform::from_xyz(0.0, 0.25 + 0.55 * *num as f32, 0.0), | ||||||
|         )) |         )) | ||||||
|         // With the MeshPickingPlugin added, you can add pointer event observers to meshes as well:
 |         // With the MeshPickingPlugin added, you can add pointer event observers to meshes:
 | ||||||
|         .observe( |         .observe(on_drag_rotate); | ||||||
|             |drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>| { |  | ||||||
|                 if let Ok(mut transform) = transforms.get_mut(drag.entity()) { |  | ||||||
|                     transform.rotate_y(drag.delta.x * 0.02); |  | ||||||
|                     transform.rotate_x(drag.delta.y * 0.02); |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|         ); |  | ||||||
|     *num += 1; |     *num += 1; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | fn on_drag_rotate(drag: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>) { | ||||||
|  |     if let Ok(mut transform) = transforms.get_mut(drag.entity()) { | ||||||
|  |         transform.rotate_y(drag.delta.x * 0.02); | ||||||
|  |         transform.rotate_x(drag.delta.y * 0.02); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Aevyrie
						Aevyrie