From 03372e590d82ce990f386aec7e9505a2e856eec6 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Wed, 30 Oct 2024 20:04:24 -0700 Subject: [PATCH] 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. --- examples/picking/mesh_picking.rs | 166 ++++++++++------------------- examples/picking/simple_picking.rs | 52 ++++----- 2 files changed, 80 insertions(+), 138 deletions(-) diff --git a/examples/picking/mesh_picking.rs b/examples/picking/mesh_picking.rs index 5193c4ea8f..40ba4806b3 100644 --- a/examples/picking/mesh_picking.rs +++ b/examples/picking/mesh_picking.rs @@ -21,38 +21,17 @@ use std::f32::consts::PI; -use bevy::{ - color::palettes::{ - css::{PINK, RED, SILVER}, - tailwind::{CYAN_300, YELLOW_300}, - }, - picking::backend::PointerHits, - prelude::*, -}; +use bevy::{color::palettes::tailwind::*, picking::pointer::PointerInteraction, prelude::*}; fn main() { App::new() - .add_plugins(( - DefaultPlugins, - // The mesh picking plugin is not enabled by default, because raycasting against all - // meshes has a performance cost. - MeshPickingPlugin, - )) - .init_resource::() - .add_systems(Startup, setup) - .add_systems(Update, (on_mesh_hover, rotate)) + // MeshPickingPlugin is not a default plugin + .add_plugins((DefaultPlugins, MeshPickingPlugin)) + .add_systems(Startup, setup_scene) + .add_systems(Update, (draw_mesh_intersections, rotate)) .run(); } -/// Materials for the scene -#[derive(Resource, Default)] -struct SceneMaterials { - pub white: Handle, - pub ground: Handle, - pub hover: Handle, - pub pressed: Handle, -} - /// A marker component for our shapes so we can query them separately from the ground plane. #[derive(Component)] struct Shape; @@ -61,17 +40,16 @@ const SHAPES_X_EXTENT: f32 = 14.0; const EXTRUSION_X_EXTENT: f32 = 16.0; const Z_EXTENT: f32 = 5.0; -fn setup( +fn setup_scene( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, - mut scene_materials: ResMut, ) { // Set up the materials. - scene_materials.white = materials.add(Color::WHITE); - scene_materials.ground = materials.add(Color::from(SILVER)); - scene_materials.hover = materials.add(Color::from(CYAN_300)); - scene_materials.pressed = materials.add(Color::from(YELLOW_300)); + let white_matl = materials.add(Color::WHITE); + let ground_matl = materials.add(Color::from(GRAY_300)); + let hover_matl = materials.add(Color::from(CYAN_300)); + let pressed_matl = materials.add(Color::from(YELLOW_300)); let shapes = [ meshes.add(Cuboid::default()), @@ -102,7 +80,7 @@ fn setup( commands .spawn(( Mesh3d(shape), - MeshMaterial3d(scene_materials.white.clone()), + MeshMaterial3d(white_matl.clone()), Transform::from_xyz( -SHAPES_X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * SHAPES_X_EXTENT, 2.0, @@ -111,10 +89,11 @@ fn setup( .with_rotation(Quat::from_rotation_x(-PI / 4.)), Shape, )) - .observe(on_pointer_over) - .observe(on_pointer_out) - .observe(on_pointer_down) - .observe(on_pointer_up); + .observe(update_material_on::>(hover_matl.clone())) + .observe(update_material_on::>(white_matl.clone())) + .observe(update_material_on::>(pressed_matl.clone())) + .observe(update_material_on::>(hover_matl.clone())) + .observe(rotate_on_drag); } let num_extrusions = extrusions.len(); @@ -123,7 +102,7 @@ fn setup( commands .spawn(( Mesh3d(shape), - MeshMaterial3d(scene_materials.white.clone()), + MeshMaterial3d(white_matl.clone()), Transform::from_xyz( -EXTRUSION_X_EXTENT / 2. + 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.)), Shape, )) - .observe(on_pointer_over) - .observe(on_pointer_out) - .observe(on_pointer_down) - .observe(on_pointer_up); + .observe(update_material_on::>(hover_matl.clone())) + .observe(update_material_on::>(white_matl.clone())) + .observe(update_material_on::>(pressed_matl.clone())) + .observe(update_material_on::>(hover_matl.clone())) + .observe(rotate_on_drag); } - // Disable picking for the ground plane. + // Ground commands.spawn(( Mesh3d(meshes.add(Plane3d::default().mesh().size(50.0, 50.0).subdivisions(10))), - MeshMaterial3d(scene_materials.ground.clone()), - PickingBehavior::IGNORE, + MeshMaterial3d(ground_matl.clone()), + PickingBehavior::IGNORE, // Disable picking for the ground plane. )); // Light @@ -166,7 +146,7 @@ fn setup( // Instructions commands.spawn(( - Text::new("Hover over the shapes to pick them"), + Text::new("Hover over the shapes to pick them\nDrag to rotate"), Node { position_type: PositionType::Absolute, top: Val::Px(12.0), @@ -176,80 +156,42 @@ fn setup( )); } -/// Changes the material when the pointer is over the mesh. -fn on_pointer_over( - trigger: Trigger>, - scene_materials: Res, - mut query: Query<&mut MeshMaterial3d>, -) { - if let Ok(mut material) = query.get_mut(trigger.entity()) { - material.0 = scene_materials.hover.clone(); +/// Returns an observer that updates the entity's material to the one specified. +fn update_material_on( + new_material: Handle, +) -> impl Fn(Trigger, Query<&mut MeshMaterial3d>) { + // 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 + // material. Instead, the event type is a generic, and the material is passed in. + 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. -fn on_pointer_out( - trigger: Trigger>, - scene_materials: Res, - mut query: Query<&mut MeshMaterial3d>, -) { - if let Ok(mut material) = query.get_mut(trigger.entity()) { - material.0 = scene_materials.white.clone(); +/// A system that draws hit indicators for every pointer. +fn draw_mesh_intersections(pointers: Query<&PointerInteraction>, mut gizmos: Gizmos) { + for (point, normal) in pointers + .iter() + .filter_map(|interaction| interaction.get_nearest_hit()) + .filter_map(|(_entity, hit)| hit.position.zip(hit.normal)) + { + 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. -fn on_pointer_down( - trigger: Trigger>, - scene_materials: Res, - mut query: Query<&mut MeshMaterial3d>, -) { - 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>, - scene_materials: Res, - mut query: Query<&mut MeshMaterial3d>, -) { - 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, - meshes: Query>, - 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. +/// A system that rotates all shapes. fn rotate(mut query: Query<&mut Transform, With>, time: Res