From 8b257b1914bb2dad2348bd8456173eb5a4069d50 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Mon, 26 Jun 2023 14:27:11 -0700 Subject: [PATCH] Only-move-active implemented For some reason the camera is not moving with the scene, so that's a bug. --- NOTES.md | 13 ++ assets/models/monkey-nod.blend | 3 + assets/models/monkey-nod.glb | 3 + assets/models/torus-spin.blend | 3 + assets/models/torus-spin.blend1 | 3 + assets/models/torus-spin.glb | 3 + bin/gltf-inspect.rs | 367 +++++++++++++++++++++----------- 7 files changed, 265 insertions(+), 130 deletions(-) create mode 100644 NOTES.md create mode 100644 assets/models/monkey-nod.blend create mode 100644 assets/models/monkey-nod.glb create mode 100644 assets/models/torus-spin.blend create mode 100644 assets/models/torus-spin.blend1 create mode 100644 assets/models/torus-spin.glb diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..fd0cd71 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,13 @@ +# GLTF Inspector + +## Staging area + +I want to have parallel "world" for each of my scenes so they're all in the same location in space, but not overlapping in the viewport. + +For example, we load scene A and B and we want to show independent previews of A and B. +The way this tends to be done is by moving the staged assets "out of the way" to some far off part of the world that the player can't see. +This works, but makes the game world grounded in reality more than it has to be. +In principle (these are just 1s and 0s) we can spawn these all in their own worlds and toggle between worlds in the "multiverse". + +There is [at least one] RFC for Bevy to support a "Universe" with multiple worlds, but nothing implemented. + diff --git a/assets/models/monkey-nod.blend b/assets/models/monkey-nod.blend new file mode 100644 index 0000000..9aaef6f --- /dev/null +++ b/assets/models/monkey-nod.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:def4955f5c77645590f9fbb601e0200e672cf762a14c4f061f3495e0f55009e4 +size 925392 diff --git a/assets/models/monkey-nod.glb b/assets/models/monkey-nod.glb new file mode 100644 index 0000000..dc3ca1f --- /dev/null +++ b/assets/models/monkey-nod.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12da5556f29cc19fc3ce28b1d07808e2721e6a518e61b3203a23954392203686 +size 70972 diff --git a/assets/models/torus-spin.blend b/assets/models/torus-spin.blend new file mode 100644 index 0000000..689e13c --- /dev/null +++ b/assets/models/torus-spin.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:742d8597564c55250a2fe9ffdf8ff67ee51cd28b0b06244881c2ebffd21cd7e3 +size 942756 diff --git a/assets/models/torus-spin.blend1 b/assets/models/torus-spin.blend1 new file mode 100644 index 0000000..139ca69 --- /dev/null +++ b/assets/models/torus-spin.blend1 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b91bb2590020530a77a861f46ff4933954477708b93ac902f52cffed35a5be8 +size 932644 diff --git a/assets/models/torus-spin.glb b/assets/models/torus-spin.glb new file mode 100644 index 0000000..c574506 --- /dev/null +++ b/assets/models/torus-spin.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c42e495ebc5a74512fd0327fcce5a2edf5601edd533f14a6198aa16627df082 +size 83584 diff --git a/bin/gltf-inspect.rs b/bin/gltf-inspect.rs index 53cca5a..776afa6 100644 --- a/bin/gltf-inspect.rs +++ b/bin/gltf-inspect.rs @@ -13,7 +13,6 @@ use bevy::{ render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, - view::RenderLayers, }, }; @@ -27,16 +26,17 @@ fn main() { }), ..default() })) - // Only run when window is active to reduce cpu cycles - // .insert_resource(WinitSettings::desktop_app()) + .add_event::() .add_startup_system(load_models) .add_startup_system(spawn_base_scene) + .add_startup_system(spawn_base_ui) .add_system(spawn_models) - // .add_system(control_ui) - .add_system(control_camera) + .add_system(spawn_ui) .add_system(rotate_model) .add_system(zoom_model) .add_system(scroll) + .add_system(select) + .add_system(manage_active) .run(); } @@ -61,6 +61,18 @@ struct PreviewCamera; #[derive(Component)] struct ScrollingList; +#[derive(Component)] +struct Preview(Handle); + +#[derive(Component)] +struct Container; + +#[derive(Component)] +struct Active; + +// Event +struct ManageActive(Option); + /// /// Load all GLTF models on startup fn load_models(mut commands: Commands, ass: Res) { @@ -88,41 +100,36 @@ fn spawn_base_scene(mut commands: Commands) { )); } -fn control_camera( - mut keyboard_evr: EventReader, - mut ui_camera: Query<&mut Camera, (With, Without)>, - mut scene_camera: Query<&mut Camera, (With, Without)>, -) { - for ev in keyboard_evr.iter() { - match ev { - KeyboardInput { - state: ButtonState::Pressed, - key_code: Some(KeyCode::Space), - .. - } => { - // Disable UI camera - let mut ui_cam = ui_camera.single_mut(); - info!("Toggling UI camera {}", !ui_cam.is_active); - ui_cam.is_active = !ui_cam.is_active; - - // Enable scene camera - let mut scene_cam = scene_camera - .iter_mut() - .nth(0) - .expect("Failed to get Scene camera"); - info!("Toggling Scene camera {}", !scene_cam.is_active); - scene_cam.is_active = !scene_cam.is_active; - } - KeyboardInput { - state: ButtonState::Released, - key_code: Some(KeyCode::Space), - .. - } => { - // No-Op - } - _ => {} // No-Op - } - } +fn spawn_base_ui(mut commands: Commands) { + commands + .spawn(( + NodeBundle { + style: Style { + justify_content: JustifyContent::Center, + size: Size::all(Val::Percent(90.0)), + overflow: Overflow::Hidden, + ..default() + }, + ..default() + }, + SelectionUI, + )) + .with_children(|parent| { + parent.spawn(( + NodeBundle { + style: Style { + flex_wrap: FlexWrap::Wrap, + flex_direction: FlexDirection::Row, + justify_content: JustifyContent::SpaceAround, + size: Size::AUTO, + max_size: Size::UNDEFINED, + ..default() + }, + ..default() + }, + ScrollingList, + )); + }); } /// @@ -136,13 +143,14 @@ fn spawn_models( if !scene_evr.is_empty() { info!("Spawning scenes"); } + for ev in scene_evr.iter() { match ev { AssetEvent::Created { handle } => { info!("Creating scene {:#?}", handle); // Preview image - { + let preview_image_handle = { info!("Creating preview"); let size = Extent3d { @@ -173,92 +181,53 @@ fn spawn_models( 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, - )); - - // UI container - commands - .spawn(NodeBundle { - style: Style t - { - justify_content: JustifyContent::Center, - size: Size::all(Val::Percent(90.0)), - overflow: Overflow::Hidden, - ..default() - }, - ..default() - }) - .with_children(|parent| { - parent - .spawn(( - NodeBundle { - style: Style { - flex_wrap: FlexWrap::Wrap, - flex_direction: FlexDirection::Row, - justify_content: JustifyContent::SpaceAround, - size: Size::AUTO, - max_size: Size::UNDEFINED, - ..default() - }, - ..default() - }, - ScrollingList, - )) - .with_children(|parent| { - // Preview Image - let colors = [ - Color::RED, - Color::GREEN, - Color::BLUE, - Color::YELLOW, - Color::PURPLE, - ]; - for i in 0..5 { - info!("Spawning image preview"); - parent.spawn(ImageBundle { - style: Style { - size: Size::all(Val::Px(256.0)), - padding: UiRect::all(Val::Px(5.0)), - ..default() - }, - background_color: BackgroundColor(colors[i]), - image: UiImage { - texture: image_handle.clone(), - ..default() - }, - ..default() - }); - } - }); - }); - } + image_handle + }; + + let local = { + // Get a unique number for this resource from the handle + let idx = handle.id().reflect_hash().unwrap(); + // Set the origin of this scene to [idx, idx, idx]; + let origin = Vec3::ONE * ((idx % 1000) as f32); + // Transform pointing at origin + Transform::from_translation(origin) + }; // Spawn the actual scene commands .spawn(( + SpatialBundle { + transform: local, + ..default() + }, Inspect, - VisibilityBundle::default(), - TransformBundle::default(), // TODO: Move to staging area - RenderLayers::layer(1), // SUS + Container, + Preview(preview_image_handle.clone()), )) .with_children(|builder| { + let camera_location = + Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y); + + // Spawn preview camera + builder.spawn(( + Camera3dBundle { + camera_3d: Camera3d { + clear_color: ClearColorConfig::Custom(Color::WHITE), + ..default() + }, + camera: Camera { + order: -1, + target: RenderTarget::Image(preview_image_handle.clone()), + ..default() + }, + transform: camera_location, + ..default() + }, + UiCameraConfig { show_ui: false }, + PreviewCamera, + )); + + // Spawn window camera builder.spawn(( Camera3dBundle { camera_3d: Camera3d { @@ -269,12 +238,11 @@ fn spawn_models( is_active: false, ..default() }, - transform: Transform::from_xyz(0.0, 0.0, 5.0) - .looking_at(Vec3::ZERO, Vec3::Y), + transform: camera_location, ..default() }, Inspect, - RenderLayers::layer(1), // SUS + Preview(preview_image_handle.clone()), )); builder.spawn(( @@ -292,7 +260,6 @@ fn spawn_models( ..default() }, Inspect, - RenderLayers::layer(1), // SUS )); builder.spawn(( @@ -301,7 +268,6 @@ fn spawn_models( ..default() }, Inspect, - RenderLayers::layer(1), // SUS )); }); } @@ -315,16 +281,50 @@ fn spawn_models( } } -// fn control_ui(mut commands: Commands, cameras: Query>) { -// todo!() -// } +fn spawn_ui( + mut commands: Commands, + query: Query>, + previews: Query<&Preview, (Added, Without, Without)>, +) { + // UI container + if let Ok(scrolling_list_container) = query.get_single() { + let mut entity_commands = commands.entity(scrolling_list_container); + entity_commands.with_children(|parent| { + for Preview(image_handle) in previews.iter() { + // Preview Image + info!("Spawning image preview"); + parent + .spawn(( + ButtonBundle { ..default() }, + SelectionUI, + Preview(image_handle.clone()), + )) + .with_children(|parent| { + parent.spawn(ImageBundle { + style: Style { + size: Size::all(Val::Px(256.0)), + padding: UiRect::all(Val::Px(5.0)), + ..default() + }, + image: UiImage { + texture: image_handle.clone(), + ..default() + }, + ..default() + }); + }); + } + }); + } +} /// /// Rotate a model as part of inspection +/// TODO: Only modify selected entities fn rotate_model( buttons: Res>, mut mouse_evr: EventReader, - mut transforms: Query<&mut Transform, (With, Without)>, + mut transforms: Query<&mut Transform, (With, With, Without)>, ) { if buttons.pressed(MouseButton::Left) { for MouseMotion { delta } in mouse_evr.iter() { @@ -340,10 +340,11 @@ fn rotate_model( /// /// Zoom in and out of the model +/// TODO: Only modify selected entities fn zoom_model( keys: Res>, mut wheel_evr: EventReader, - mut transforms: Query<&mut Transform, With>, + mut transforms: Query<&mut Transform, (With, With, Without)>, ) { if keys.pressed(KeyCode::LShift) { for ev in wheel_evr.iter() { @@ -368,3 +369,109 @@ fn scroll( } } } + +/// +/// Click a UI element to select +/// +/// This is a really ugly implementation. I'm not really happy with how we have to navigate the ECS +/// parent/child hierarchy. There should be a more direct way to correlate the scene with the +/// button. +fn select( + query: Query<(&Interaction, &Preview), (With, Changed)>, + mut selection_ui: Query<&mut Visibility, (With, Without)>, + mut ui_camera: Query<&mut Camera, (With, Without)>, + mut scene_camera: Query<(Entity, &mut Camera, &Preview), (With, Without)>, + mut key_evr: EventReader, + mut selected: Local>, // Active camera index + parent_search: Query<&Children>, + parents: Query, Without)>, // TODO: Constrain + mut events: EventWriter, +) { + for (interaction, selected_preview) in query.iter() { + if interaction == &Interaction::Clicked { + // Hide UI + let mut ui_vis = selection_ui.single_mut(); + *ui_vis = Visibility::Hidden; + + // Disable UI camera + let mut ui_cam = ui_camera.single_mut(); + ui_cam.is_active = false; + + // Determine selected scene + *selected = scene_camera + .iter() + .find(|(_, _, preview)| selected_preview.0 == preview.0) + .map(|(entity, _, _)| entity); + + // Enable scene camera + let (_, mut scene_cam, _) = scene_camera + .get_mut(selected.expect("Selected scene should be set")) + .expect("Failed to get Scene camera"); + scene_cam.is_active = true; + + // Set relevant entities active + let message = parents.iter() + .find(|&parent| parent_search.iter_descendants(parent) + .find(|&entity| Some(entity) == *selected) + .is_some() + ); + events.send(ManageActive(message)); + } + } + for ev in key_evr.iter() { + match ev { + KeyboardInput { + state: ButtonState::Pressed, + key_code: Some(KeyCode::Escape), + .. + } => { + if let Some(s) = *selected { + // Set all inactive + events.send(ManageActive(None)); + + // Disable scene camera + let (_, mut scene_cam, _) = + scene_camera.get_mut(s).expect("Failed to get Scene camera"); + scene_cam.is_active = false; + + // Enable UI camera + let mut ui_cam = ui_camera.single_mut(); + ui_cam.is_active = true; + + // Make UI visible + let mut ui_vis = selection_ui.single_mut(); + *ui_vis = Visibility::Inherited; + } + } + _ => (), + } + } +} + +fn manage_active( + mut commands: Commands, + mut events: EventReader, + query: Query<&Children>, + current: Query>, +) { + for event in events.iter() { + match event { + ManageActive(None) => { + for entity in current.iter() { + if let Some(mut entity_commands) = commands.get_entity(entity) { + entity_commands.remove::(); + } + } + } + ManageActive(Some(entity)) => { + for child in query.iter_descendants(*entity) { + if let Some(mut entity_commands) = commands.get_entity(child) { + info!("Setting active: {:?}", child); + entity_commands.log_components(); + entity_commands.insert(Active); + } + } + } + } + } +}