diff --git a/Cargo.toml b/Cargo.toml index 3035be5d..d4c74cce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,6 +123,11 @@ name = "virtual_pointer" path = "examples/virtual_pointer.rs" required-features = ["backend_egui"] +[[example]] +name = "render_to_texture" +path = "examples/render_to_texture.rs" +required-features = ["bevy_egui"] + [[example]] name = "split_screen" path = "examples/split_screen.rs" diff --git a/crates/bevy_picking_input/src/lib.rs b/crates/bevy_picking_input/src/lib.rs index 978f8784..d05d5aa5 100644 --- a/crates/bevy_picking_input/src/lib.rs +++ b/crates/bevy_picking_input/src/lib.rs @@ -64,9 +64,9 @@ impl Plugin for InputPlugin { #[reflect(Resource, Default)] pub struct InputPluginSettings { /// Should touch inputs be updated? - is_touch_enabled: bool, + pub is_touch_enabled: bool, /// Should mouse inputs be updated? - is_mouse_enabled: bool, + pub is_mouse_enabled: bool, } impl Default for InputPluginSettings { diff --git a/examples/render_to_texture.rs b/examples/render_to_texture.rs index 990c549b..c67d5d2f 100644 --- a/examples/render_to_texture.rs +++ b/examples/render_to_texture.rs @@ -1,4 +1,4 @@ -//! Shows how to render to a texture. Useful for mirrors, UI, or exporting images. +//! Renders the 3d scene to a texture, displays it in egui as a viewport, and adds picking support. use bevy::{ prelude::*, @@ -8,29 +8,152 @@ use bevy::{ }, view::RenderLayers, }, + transform, }; +use bevy_egui::*; use bevy_mod_picking::prelude::*; -use bevy_render::camera::NormalizedRenderTarget; -use picking_core::pointer::{InputMove, Location}; +use picking_core::pointer::{InputMove, InputPress, Location}; fn main() { App::new() - .add_plugins(DefaultPlugins.set(low_latency_window_plugin())) - .add_plugins(DefaultPickingPlugins) - .add_systems(Startup, setup) - .add_systems(Update, sys) + .add_plugins(( + DefaultPlugins, + DefaultPickingPlugins.build().disable::(), + ViewportInputPlugin, + EguiPlugin, + )) + .add_systems(Startup, setup_scene) + .add_systems(Update, (ui, spin_cube)) .run(); } -fn setup( +/// Send this event to spawn a new viewport. +#[derive(Event, Default)] +pub struct SpawnViewport; + +/// Replaces bevy_mod_picking's default `InputPlugin`, and replaces it with inputs driven by egui, +/// sending picking inputs when a pointer is over a viewport that has been rendered to a texture and +/// laid out inside the ui. +struct ViewportInputPlugin; + +impl Plugin for ViewportInputPlugin { + fn build(&self, app: &mut App) { + app.add_event::() + .add_systems(First, Self::send_mouse_clicks) + // Default input plugin is disabled, we need to spawn a mouse pointer. + .add_systems(Startup, input::mouse::spawn_mouse_pointer) + .add_systems(Update, spawn_viewport.run_if(on_event::())); + } +} + +impl ViewportInputPlugin { + fn send_mouse_clicks( + mut mouse_inputs: Res>, + mut pointer_press: EventWriter, + ) { + if mouse_inputs.just_pressed(MouseButton::Left) { + pointer_press.send(InputPress { + pointer_id: PointerId::Mouse, + direction: pointer::PressDirection::Down, + button: PointerButton::Primary, + }); + } else if mouse_inputs.just_released(MouseButton::Left) { + pointer_press.send(InputPress { + pointer_id: PointerId::Mouse, + direction: pointer::PressDirection::Up, + button: PointerButton::Primary, + }); + } + } +} + +#[derive(Component)] +struct EguiViewport(Handle); + +fn ui( + mut egui_contexts: EguiContexts, + egui_viewports: Query<(Entity, &EguiViewport)>, + mut pointer_move: EventWriter, + mut spawn_viewport: EventWriter, +) { + egui::TopBottomPanel::top("menu_panel").show(egui_contexts.ctx_mut(), |ui| { + egui::menu::bar(ui, |ui| { + if ui.button("New Viewport").clicked() { + spawn_viewport.send_default(); + } + }); + }); + + // Draw every viewport in a window. This isn't as robust as it could be for the sake of + // demonstration. This only works if the render target and egui texture are rendered at the same + // resolution, and this completely ignores touch inputs and treats everything as a mouse input. + for (viewport_entity, egui_viewport) in &egui_viewports { + let viewport_texture = &egui_viewport.0; + let viewport_texture_id = egui_contexts.add_image(viewport_texture.clone_weak()); + + egui::Window::new(format!("Viewport {:?}", viewport_entity)) + .id(egui::Id::new(viewport_entity)) + .show(egui_contexts.ctx_mut(), |ui| { + // Draw the texture and get a response to check if a pointer is interacting + let viewport_response = ui.add(egui::widgets::Image::new( + egui::load::SizedTexture::new(viewport_texture_id, [256.0, 256.0]), + )); + + if let Some(pointer_pos_window) = viewport_response.hover_pos() { + // Compute the position of the pointer relative to the texture. + let pos = pointer_pos_window - viewport_response.rect.min; + pointer_move.send(InputMove { + pointer_id: PointerId::Mouse, + location: Location { + target: bevy_render::camera::NormalizedRenderTarget::Image( + viewport_texture.clone_weak(), + ), + position: Vec2::new(pos.x, pos.y), + }, + delta: Vec2::ZERO, + }); + } + }); + } +} + +/// Spawn the light and cube mesh. +fn setup_scene( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + mut images: ResMut>, +) { + commands.spawn((PointLightBundle { + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), + ..default() + },)); + + commands.spawn(( + PbrBundle { + mesh: meshes.add(Cuboid::new(4.0, 4.0, 4.0)), + material: materials.add(StandardMaterial { + base_color: Color::rgb(0.8, 0.7, 0.6), + ..default() + }), + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)), + ..default() + }, + PickableBundle::default(), + )); +} + +/// Spawn a new camera to use as a viewport in egui on demand. +fn spawn_viewport( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, mut images: ResMut>, + time: Res