From 07eefb11ce7aa98efd68b16b0da6a157fc812298 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Fri, 23 Jun 2023 08:53:37 -0700 Subject: [PATCH] Added render-to-UI-image example The gotcha was that the 3d camera was rendering the UI but shouldn't have. Now I think we can show the previews. Still not sure how to render each preview in it's own "space" without them overlapping... --- Cargo.toml | 4 ++ bin/camera-image-ui.rs | 130 +++++++++++++++++++++++++++++++++++++++++ bin/gltf-inspect.rs | 123 +++++++++++++++++++++++++++++++++++--- 3 files changed, 249 insertions(+), 8 deletions(-) create mode 100644 bin/camera-image-ui.rs diff --git a/Cargo.toml b/Cargo.toml index 1bf1a2d..958e98f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,9 @@ edition = "2021" name = "gltf-inspect" path = "bin/gltf-inspect.rs" +[[bin]] +name = "camera-image-ui" +path = "bin/camera-image-ui.rs" + [dependencies] bevy = "0.10" diff --git a/bin/camera-image-ui.rs b/bin/camera-image-ui.rs new file mode 100644 index 0000000..f050ccf --- /dev/null +++ b/bin/camera-image-ui.rs @@ -0,0 +1,130 @@ +use bevy::{ + core_pipeline::clear_color::ClearColorConfig, + prelude::*, + render::{camera::RenderTarget, render_resource::*, view::RenderLayers}, +}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + title: "Camera Image UI".into(), + resolution: (640., 480.).into(), + ..default() + }), + ..default() + })) + .add_startup_system(init) + .run(); +} + +fn init( + mut commands: Commands, + mut images: ResMut>, + mut meshes: ResMut>, +) { + info!("Creating preview"); + + let size = Extent3d { + width: 512, + height: 512, + ..default() + }; + + // Create render target image for the preview camera + let mut image = Image { + texture_descriptor: TextureDescriptor { + label: None, + size, + dimension: TextureDimension::D2, + format: TextureFormat::Bgra8UnormSrgb, + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }, + ..default() + }; + + // Fill with zeroes + image.resize(size); + + let image_handle = images.add(image); + + commands + .spawn(( + TransformBundle { ..default() }, + VisibilityBundle { ..default() }, + )) + .with_children(|parent| { + // Spawn preview camera + parent.spawn(( + Camera3dBundle { + camera_3d: Camera3d { + clear_color: ClearColorConfig::Custom(Color::WHITE), + ..default() + }, + camera: Camera { + order: -1, + target: RenderTarget::Image(image_handle.clone()), + ..default() + }, + transform: Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }, + UiCameraConfig { show_ui: false }, + RenderLayers::layer(1), + )); + + parent.spawn(( + PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })), + transform: Transform::from_xyz(0.0, 0.0, 0.0), + ..default() + }, + RenderLayers::layer(1), + )); + + parent.spawn(PointLightBundle { + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), + ..default() + }); + }); + + // Assign render target image to UI element + commands + .spawn(NodeBundle { + style: Style { + size: Size::all(Val::Percent(50.0)), + justify_content: JustifyContent::SpaceBetween, + ..default() + }, + ..default() + }) + .with_children(|parent| { + parent.spawn(ImageBundle { + image: UiImage { + texture: image_handle.clone(), + ..default() + }, + style: Style { + size: Size::all(Val::Percent(100.0)), + ..default() + }, + ..default() + }); + }); + + commands.spawn(( + Camera2dBundle { + camera_2d: Camera2d { + clear_color: ClearColorConfig::Custom(Color::TEAL), + }, + transform: Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }, + RenderLayers::layer(0), + )); +} diff --git a/bin/gltf-inspect.rs b/bin/gltf-inspect.rs index 67b12e2..d3ade53 100644 --- a/bin/gltf-inspect.rs +++ b/bin/gltf-inspect.rs @@ -1,4 +1,5 @@ use bevy::{ + core_pipeline::clear_color::ClearColorConfig, gltf::Gltf, input::{ keyboard::KeyboardInput, @@ -7,6 +8,14 @@ use bevy::{ }, pbr::CascadeShadowConfigBuilder, prelude::*, + render::{ + camera::RenderTarget, + render_resource::{ + Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + }, + view::RenderLayers, + }, + winit::WinitSettings, }; fn main() { @@ -19,9 +28,12 @@ fn main() { }), ..default() })) + // Only run when window is active to reduce cpu cycles + // .insert_resource(WinitSettings::desktop_app()) .add_startup_system(load_models) .add_startup_system(spawn_base_scene) .add_system(spawn_models) + // .add_system(control_ui) .add_system(control_camera) .add_system(rotate_model) .add_system(zoom_model) @@ -32,7 +44,7 @@ fn main() { /// Stores GLTF handles for later use #[derive(Resource)] struct Models { - handles: Vec>, + _handles: Vec>, } /// @@ -43,16 +55,19 @@ struct Inspect; #[derive(Component)] struct SelectionUI; +#[derive(Component)] +struct PreviewCamera; + /// /// Load all GLTF models on startup fn load_models(mut commands: Commands, ass: Res) { let weak_handles = ass.load_folder("models").expect("Load gltfs"); - let handles: Vec> = weak_handles + let _handles: Vec> = weak_handles .iter() .map(|weak| weak.clone().typed::()) .collect(); - info!("Scene Handles: {:#?}", handles); - commands.insert_resource(Models { handles }); + info!("Scene Handles: {:#?}", _handles); + commands.insert_resource(Models { _handles }); } /// @@ -60,7 +75,9 @@ fn load_models(mut commands: Commands, ass: Res) { fn spawn_base_scene(mut commands: Commands) { commands.spawn(( Camera2dBundle { - transform: Transform::from_xyz(0.0, 0.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + camera_2d: Camera2d { + clear_color: ClearColorConfig::Custom(Color::BLACK), + }, ..default() }, UiCameraConfig { ..default() }, @@ -72,7 +89,6 @@ fn control_camera( mut keyboard_evr: EventReader, mut ui_camera: Query<&mut Camera, (With, Without)>, mut scene_camera: Query<&mut Camera, (With, Without)>, - mut scene_objects: Query<&mut ComputedVisibility, With>, ) { for ev in keyboard_evr.iter() { match ev { @@ -111,8 +127,8 @@ fn control_camera( /// TODO: Update/add/delete when files are updated fn spawn_models( mut commands: Commands, - // TODO: Need to iter over gltf not scenes? mut scene_evr: EventReader>, + mut images: ResMut>, ) { if !scene_evr.is_empty() { info!("Spawning scenes"); @@ -121,15 +137,99 @@ fn spawn_models( match ev { AssetEvent::Created { handle } => { info!("Creating scene {:#?}", handle); + + // Preview image + { + info!("Creating preview"); + + let size = Extent3d { + width: 512, + height: 512, + ..default() + }; + + // Create render target image for the preview camera + let mut image = Image { + texture_descriptor: TextureDescriptor { + label: None, + size, + dimension: TextureDimension::D2, + format: TextureFormat::Bgra8UnormSrgb, + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }, + ..default() + }; + + // Fill with zeroes + image.resize(size); + + let image_handle = images.add(image); + + // Spawn preview camera + commands.spawn(( + Camera3dBundle { + camera_3d: Camera3d { + clear_color: ClearColorConfig::Custom(Color::WHITE), + ..default() + }, + camera: Camera { + order: -1, + target: RenderTarget::Image(image_handle.clone()), + ..default() + }, + transform: Transform::from_xyz(5.0, 5.0, 5.0) + .looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }, + UiCameraConfig { show_ui: false }, + PreviewCamera, + )); + + // Assign render target image to UI element + commands + .spawn(NodeBundle { + style: Style { + size: Size::all(Val::Percent(50.0)), + justify_content: JustifyContent::SpaceBetween, + ..default() + }, + ..default() + }) + .with_children(|parent| { + parent.spawn(ImageBundle { + style: Style { + size: Size::all(Val::Percent(100.0)), + ..default() + }, + image: UiImage { + texture: image_handle.clone(), + ..default() + }, + ..default() + }); + }); + } + + // Spawn the actual scene commands .spawn(( Inspect, VisibilityBundle::default(), - TransformBundle::default(), + TransformBundle::default(), // TODO: Move to staging area + RenderLayers::layer(1), // SUS )) .with_children(|builder| { builder.spawn(( Camera3dBundle { + camera_3d: Camera3d { + clear_color: ClearColorConfig::Custom(Color::WHITE), + ..default() + }, camera: Camera { is_active: false, ..default() @@ -139,6 +239,7 @@ fn spawn_models( ..default() }, Inspect, + RenderLayers::layer(1), // SUS )); builder.spawn(( @@ -156,6 +257,7 @@ fn spawn_models( ..default() }, Inspect, + RenderLayers::layer(1), // SUS )); builder.spawn(( @@ -164,6 +266,7 @@ fn spawn_models( ..default() }, Inspect, + RenderLayers::layer(1), // SUS )); }); } @@ -177,6 +280,10 @@ fn spawn_models( } } +// fn control_ui(mut commands: Commands, cameras: Query>) { +// todo!() +// } + /// /// Rotate a model as part of inspection fn rotate_model(