diff --git a/Cargo.lock b/Cargo.lock index 5c1b8f4..a5f233e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "approx" @@ -346,47 +346,19 @@ dependencies = [ "bevy_internal", ] -[[package]] -name = "bevy-inspector-egui" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65b98d6fca1209c36c4d403c377f303aad22d940281fe1a9e431217516f0622" -dependencies = [ - "bevy-inspector-egui-derive 0.21.0", - "bevy_app", - "bevy_asset", - "bevy_core", - "bevy_core_pipeline", - "bevy_ecs", - "bevy_egui 0.23.0", - "bevy_hierarchy", - "bevy_log", - "bevy_math", - "bevy_pbr", - "bevy_reflect", - "bevy_render", - "bevy_utils", - "bevy_window", - "egui 0.23.0", - "image", - "once_cell", - "pretty-type-name", - "smallvec", -] - [[package]] name = "bevy-inspector-egui" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c3efee9165f9be9c6a180c6a9bbb4bd39ee0a98a594f4cb7127704e15415cdd" dependencies = [ - "bevy-inspector-egui-derive 0.22.0", + "bevy-inspector-egui-derive", "bevy_app", "bevy_asset", "bevy_core", "bevy_core_pipeline", "bevy_ecs", - "bevy_egui 0.24.0", + "bevy_egui", "bevy_hierarchy", "bevy_log", "bevy_math", @@ -396,24 +368,13 @@ dependencies = [ "bevy_time", "bevy_utils", "bevy_window", - "egui 0.24.1", + "egui", "image", "once_cell", "pretty-type-name", "smallvec", ] -[[package]] -name = "bevy-inspector-egui-derive" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec800b7cf98151b5dbff80f0eb6dffcb4bcfceef6e457888b395ead4eb7e75ba" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", -] - [[package]] name = "bevy-inspector-egui-derive" version = "0.22.0" @@ -658,19 +619,6 @@ dependencies = [ "syn 2.0.37", ] -[[package]] -name = "bevy_egui" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85450af551b7e1cb766f710763b60a12a82ffd6323945a8f776c6334c59ccdc1" -dependencies = [ - "arboard", - "bevy", - "egui 0.23.0", - "thread_local", - "webbrowser", -] - [[package]] name = "bevy_egui" version = "0.24.0" @@ -679,7 +627,7 @@ checksum = "c90c01202dbcebc03315a01ea71553b35e1f20b0da6b1cc8c2605344032a3d96" dependencies = [ "arboard", "bevy", - "egui 0.24.1", + "egui", "thread_local", "webbrowser", ] @@ -2087,15 +2035,6 @@ dependencies = [ "proc-macro-error", ] -[[package]] -name = "ecolor" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf4e52dbbb615cfd30cf5a5265335c217b5fd8d669593cea74a517d9c605af" -dependencies = [ - "bytemuck", -] - [[package]] name = "ecolor" version = "0.24.1" @@ -2105,17 +2044,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "egui" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd69fed5fcf4fbb8225b24e80ea6193b61e17a625db105ef0c4d71dde6eb8b7" -dependencies = [ - "ahash", - "epaint 0.23.0", - "nohash-hasher", -] - [[package]] name = "egui" version = "0.24.1" @@ -2123,7 +2051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c55bcb864b764eb889515a38b8924757657a250738ad15126637ee2df291ee6b" dependencies = [ "ahash", - "epaint 0.24.1", + "epaint", "nohash-hasher", ] @@ -2133,7 +2061,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2880830f1b9a0ea618472c989e7ee758f9abc54352846e4feab1be278e69316b" dependencies = [ - "egui 0.24.1", + "egui", "glam", "mint", ] @@ -2145,7 +2073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a6cbf38560cdcf75f955b1f2f39be6339a3f2958d06997ce70b7b4aa3f57d" dependencies = [ "duplicate", - "egui 0.24.1", + "egui", "paste", ] @@ -2154,7 +2082,7 @@ name = "egui_file" version = "0.13.0" source = "git+https://github.com/Barugon/egui_file.git?rev=de5f8f41df160d67be328ab50974966ceb74e70f#de5f8f41df160d67be328ab50974966ceb74e70f" dependencies = [ - "egui 0.24.1", + "egui", ] [[package]] @@ -2163,15 +2091,6 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" -[[package]] -name = "emath" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef2b29de53074e575c18b694167ccbe6e5191f7b25fe65175a0d905a32eeec0" -dependencies = [ - "bytemuck", -] - [[package]] name = "emath" version = "0.24.1" @@ -2222,21 +2141,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "epaint" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58067b840d009143934d91d8dcb8ded054d8301d7c11a517ace0a99bb1e1595e" -dependencies = [ - "ab_glyph", - "ahash", - "bytemuck", - "ecolor 0.23.0", - "emath 0.23.0", - "nohash-hasher", - "parking_lot", -] - [[package]] name = "epaint" version = "0.24.1" @@ -2246,8 +2150,8 @@ dependencies = [ "ab_glyph", "ahash", "bytemuck", - "ecolor 0.24.1", - "emath 0.24.1", + "ecolor", + "emath", "nohash-hasher", "parking_lot", ] @@ -3032,9 +2936,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.14" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295c17e837573c8c821dbaeb3cceb3d745ad082f7572191409e69cbc1b3fd050" +checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" dependencies = [ "cc", "libc", @@ -3562,9 +3466,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.98" +version = "0.9.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" dependencies = [ "cc", "libc", @@ -3676,18 +3580,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", @@ -4176,7 +4080,7 @@ dependencies = [ [[package]] name = "space_bevy_xpbd_plugin" version = "0.3.1" -source = "git+https://github.com/rewin123/space_editor?rev=e150ecaf0647de6532d2967928982abf2558bfbc#e150ecaf0647de6532d2967928982abf2558bfbc" +source = "git+https://github.com/rewin123/space_editor?rev=ce83f86dc902787ab16213f3209276a784654df6#ce83f86dc902787ab16213f3209276a784654df6" dependencies = [ "bevy", "bevy_xpbd_3d", @@ -4186,7 +4090,7 @@ dependencies = [ [[package]] name = "space_editor" version = "0.3.1" -source = "git+https://github.com/rewin123/space_editor?rev=e150ecaf0647de6532d2967928982abf2558bfbc#e150ecaf0647de6532d2967928982abf2558bfbc" +source = "git+https://github.com/rewin123/space_editor?rev=ce83f86dc902787ab16213f3209276a784654df6#ce83f86dc902787ab16213f3209276a784654df6" dependencies = [ "bevy", "space_bevy_xpbd_plugin", @@ -4197,7 +4101,7 @@ dependencies = [ [[package]] name = "space_editor_core" version = "0.3.1" -source = "git+https://github.com/rewin123/space_editor?rev=e150ecaf0647de6532d2967928982abf2558bfbc#e150ecaf0647de6532d2967928982abf2558bfbc" +source = "git+https://github.com/rewin123/space_editor?rev=ce83f86dc902787ab16213f3209276a784654df6#ce83f86dc902787ab16213f3209276a784654df6" dependencies = [ "bevy", "space_persistence", @@ -4209,11 +4113,11 @@ dependencies = [ [[package]] name = "space_editor_ui" version = "0.3.1" -source = "git+https://github.com/rewin123/space_editor?rev=e150ecaf0647de6532d2967928982abf2558bfbc#e150ecaf0647de6532d2967928982abf2558bfbc" +source = "git+https://github.com/rewin123/space_editor?rev=ce83f86dc902787ab16213f3209276a784654df6#ce83f86dc902787ab16213f3209276a784654df6" dependencies = [ "bevy", "bevy_debug_grid", - "bevy_egui 0.24.0", + "bevy_egui", "bevy_mod_picking", "bevy_panorbit_camera", "convert_case 0.6.0", @@ -4230,7 +4134,7 @@ dependencies = [ [[package]] name = "space_persistence" version = "0.3.1" -source = "git+https://github.com/rewin123/space_editor?rev=e150ecaf0647de6532d2967928982abf2558bfbc#e150ecaf0647de6532d2967928982abf2558bfbc" +source = "git+https://github.com/rewin123/space_editor?rev=ce83f86dc902787ab16213f3209276a784654df6#ce83f86dc902787ab16213f3209276a784654df6" dependencies = [ "bevy", "ron", @@ -4240,10 +4144,10 @@ dependencies = [ [[package]] name = "space_prefab" version = "0.3.1" -source = "git+https://github.com/rewin123/space_editor?rev=e150ecaf0647de6532d2967928982abf2558bfbc#e150ecaf0647de6532d2967928982abf2558bfbc" +source = "git+https://github.com/rewin123/space_editor?rev=ce83f86dc902787ab16213f3209276a784654df6#ce83f86dc902787ab16213f3209276a784654df6" dependencies = [ "bevy", - "bevy-inspector-egui 0.22.1", + "bevy-inspector-egui", "bevy-scene-hook", "ron", "serde", @@ -4254,21 +4158,20 @@ dependencies = [ [[package]] name = "space_shared" version = "0.3.1" -source = "git+https://github.com/rewin123/space_editor?rev=e150ecaf0647de6532d2967928982abf2558bfbc#e150ecaf0647de6532d2967928982abf2558bfbc" +source = "git+https://github.com/rewin123/space_editor?rev=ce83f86dc902787ab16213f3209276a784654df6#ce83f86dc902787ab16213f3209276a784654df6" dependencies = [ "bevy", - "bevy-inspector-egui 0.22.1", + "bevy-inspector-egui", "egui_file", ] [[package]] name = "space_undo" version = "0.3.1" -source = "git+https://github.com/rewin123/space_editor?rev=e150ecaf0647de6532d2967928982abf2558bfbc#e150ecaf0647de6532d2967928982abf2558bfbc" +source = "git+https://github.com/rewin123/space_editor?rev=ce83f86dc902787ab16213f3209276a784654df6#ce83f86dc902787ab16213f3209276a784654df6" dependencies = [ "bevy", "pretty-type-name", - "space_shared", ] [[package]] @@ -4310,7 +4213,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bevy", - "bevy-inspector-egui 0.21.0", + "bevy-inspector-egui", "bevy_mod_outline", "bevy_mod_picking", "bevy_mod_raycast", diff --git a/editor.ron b/editor.ron new file mode 100644 index 0000000..2f5962d --- /dev/null +++ b/editor.ron @@ -0,0 +1,6 @@ +{ + "space_undo::ChangeChainSettings": "{\"space_undo::ChangeChainSettings\":(max_change_chain_size:200)}", + "space_persistence::PersistenceSettings": "{\"space_persistence::PersistenceSettings\":(load_on_startup:true,save_on_close:true)}", + "space_editor_core::hotkeys::HotkeySet": "{\"space_editor_core::hotkeys::HotkeySet\":(bindings:{Rotate:[R],Translate:[G],Scale:[S],Multiple:[ShiftLeft],Clone:[AltLeft],Delete:[X]},name:\"GizmoHotkey\")}", + "space_editor_ui::settings::NewWindowSettings": "{\"space_editor_ui::settings::NewWindowSettings\":(new_tab:SameNode)}", +} \ No newline at end of file diff --git a/studio/Cargo.toml b/studio/Cargo.toml index e01809d..4ad8e00 100644 --- a/studio/Cargo.toml +++ b/studio/Cargo.toml @@ -13,11 +13,11 @@ name = "studio-standalone" path = "src/main.rs" [dependencies] -anyhow = { workspace = true } -bevy-inspector-egui = "0.21.0" +bevy-inspector-egui = "0.22.0" bevy_mod_raycast = "0.16.0" bevy_rapier3d = "0.23.0" bevy_mod_outline = "0.6.0" +bevy_web_asset = "0.7.0" bitflags = "2.4.0" console_error_panic_hook = { version = "0.1.7", optional = true } js-sys = { version = "0.3.65", optional = true } @@ -34,8 +34,8 @@ web-sys = { version = "0.3.65", optional = true, features = [ "HtmlElement", "Window", ] } -space_editor = {git = "https://github.com/rewin123/space_editor", rev = "e150ecaf0647de6532d2967928982abf2558bfbc"} -bevy_web_asset = "0.7.0" +space_editor = {git = "https://github.com/rewin123/space_editor", rev = "ce83f86dc902787ab16213f3209276a784654df6"} +anyhow.workspace = true [features] wasm = [ diff --git a/studio/assets/gltf/sample-room.glb b/studio/assets/gltf/sample-room.glb new file mode 100644 index 0000000..f4f2217 Binary files /dev/null and b/studio/assets/gltf/sample-room.glb differ diff --git a/studio/src/camera_controller.rs b/studio/src/camera_controller.rs index 6c94020..186f69d 100644 --- a/studio/src/camera_controller.rs +++ b/studio/src/camera_controller.rs @@ -179,6 +179,8 @@ fn process_focus_inputs( // Zero out any existing camera velocity or interpolation **velocity = Vec3::ZERO; + info!("Setting focus to {:?}", target); + if q_interp_vel.get(ent).is_ok() { cmd.entity(ent).remove::(); } diff --git a/studio/src/debug.rs b/studio/src/debug.rs index cad2735..14a76f8 100644 --- a/studio/src/debug.rs +++ b/studio/src/debug.rs @@ -1,8 +1,16 @@ use std::fmt; use bevy::{prelude::*, window::PrimaryWindow}; -use bevy_inspector_egui::quick::WorldInspectorPlugin; +use bevy_inspector_egui::{bevy_egui::EguiPlugin, DefaultInspectorConfigPlugin}; use bevy_mod_picking::debug::DebugPickingMode; +use space_editor::{ + prelude::EditorCameraMarker, + space_editor_ui::{ + bottom_menu::BottomMenuPlugin, tools::gizmo::GizmoToolPlugin, EditorDefaultCameraPlugin, + EditorGizmoConfigPlugin, EditorPickingPlugin, EditorPluginGroup, EditorUiCore, + ShowEditorUi, + }, +}; use crate::{ camera_controller::{CameraController, CameraMotionSystem}, @@ -25,7 +33,6 @@ pub struct DebugPlugin; impl Plugin for DebugPlugin { fn build(&self, app: &mut App) { app.insert_resource(DebugMode(false)) - .add_plugins(WorldInspectorPlugin::new().run_if(is_debug_mode)) .insert_resource(State::new(DebugPickingMode::Disabled)) .insert_resource(GizmoConfig { enabled: true, @@ -47,6 +54,33 @@ impl Plugin for DebugPlugin { .run_if(is_debug_mode), ), ); + + //space_editor stuff + // app.add_plugins(UndoPlugin); + app.add_plugins( + EditorPluginGroup + .build() + .disable::() + .add_before::(EditorUiCore { + disable_no_editor_cams: false, + }) + .disable::() + .disable::() + .disable::() + .disable::() + .disable::() + .disable::() + .disable::() + .set(space_editor::prelude::SelectedPlugin { + show_selected_wireframe: false, + }), + ); + + app.add_systems(Update, sync_editor_camera); + app.add_systems( + Update, + update_editor_ui_mode.run_if(resource_changed::()), + ); } } @@ -59,6 +93,16 @@ fn toggle_debug_mode(mut input: EventReader, mut r_debug: ResMut, + mut s_show_editor_ui: ResMut>, +) { + s_show_editor_ui.set(match **r_debug { + true => ShowEditorUi::Show, + false => ShowEditorUi::Hide, + }); +} + fn update_debug_picking_mode( r_debug: Res, mut s_debug_picking: ResMut>, @@ -157,3 +201,12 @@ pub fn print_mat3(m: &Mat3, label: impl fmt::Display) -> fmt::Result { Ok(()) } + +fn sync_editor_camera( + mut cmd: Commands, + mut q_cam: Query, Without)>, +) { + for ent in q_cam.iter_mut() { + cmd.entity(ent).insert(EditorCameraMarker); + } +} diff --git a/studio/src/input.rs b/studio/src/input.rs index f507afc..30add81 100644 --- a/studio/src/input.rs +++ b/studio/src/input.rs @@ -320,8 +320,19 @@ fn read_mouse_delta( // TODO: Make it configurable fn send_undo_event(mut events: EventWriter, keyboard: Res>) { - if keyboard.pressed(KeyCode::ControlLeft) && keyboard.just_pressed(KeyCode::Z) { + if keyboard.pressed(KeyCode::ControlLeft) + && keyboard.just_pressed(KeyCode::Z) + && !keyboard.pressed(KeyCode::ShiftLeft) + { events.send(UndoRedo::Undo); info!("Undo event sent"); } + + if keyboard.pressed(KeyCode::ControlLeft) + && keyboard.just_pressed(KeyCode::Z) + && keyboard.pressed(KeyCode::ShiftLeft) + { + events.send(UndoRedo::Redo); + info!("Redo event sent"); + } } diff --git a/studio/src/interaction/gizmos/camera.rs b/studio/src/interaction/gizmos/camera.rs index 43d1be1..4b6fde6 100644 --- a/studio/src/interaction/gizmos/camera.rs +++ b/studio/src/interaction/gizmos/camera.rs @@ -3,13 +3,14 @@ use bevy::{ prelude::*, render::view::RenderLayers, }; +use space_editor::space_editor_ui::NotShowCamera; use crate::MainCamera; use super::GIZMO_RENDER_LAYER; #[derive(Component)] -pub(super) struct GizmoCamera; +pub struct GizmoCamera; pub(super) fn spawn_gizmo_camera( mut cmd: Commands, @@ -35,6 +36,22 @@ pub(super) fn spawn_gizmo_camera( }, RenderLayers::layer(GIZMO_RENDER_LAYER), Fxaa::default(), + NotShowCamera, )); }); } + +pub(super) fn sync_gizmo_and_main_cameras( + q_main_camera: Query<&Camera, (With, Without)>, + mut q_gizmo_camera: Query<&mut Camera, (With, Without)>, +) { + let Ok(main_cam) = q_main_camera.get_single() else { + return; + }; + + let Ok(mut gizmo_cam) = q_gizmo_camera.get_single_mut() else { + return; + }; + + gizmo_cam.viewport = main_cam.viewport.clone(); +} diff --git a/studio/src/interaction/gizmos/mod.rs b/studio/src/interaction/gizmos/mod.rs index b4009d5..53e9b14 100644 --- a/studio/src/interaction/gizmos/mod.rs +++ b/studio/src/interaction/gizmos/mod.rs @@ -13,7 +13,7 @@ use self::{ use super::{Selection, TransformMode, TransformType}; -mod camera; +pub mod camera; mod combo; mod common; mod resources; @@ -62,6 +62,10 @@ impl Plugin for GizmoPlugin { normalize_gizmo_scale.after(CameraMotionSystem), ), ); + + //sync camera viewports + app.add_systems(Update, camera::sync_gizmo_and_main_cameras); + app.add_systems( PostUpdate, update_ss_ring_rotation.after(TransformSystem::TransformPropagate), @@ -139,10 +143,10 @@ fn spawn_gizmo( fn sync_gizmo_transform( mut cmd: Commands, - mut q_gizmo: Query<(&mut Transform, &TransformGizmo)>, + mut q_gizmo: Query<(Entity, &mut Transform, &TransformGizmo)>, q_targets: Query<&GlobalTransform>, ) { - for (mut transform, gizmo) in q_gizmo.iter_mut() { + for (ent, mut transform, gizmo) in q_gizmo.iter_mut() { if let Ok(target) = q_targets.get(gizmo.target) { match gizmo.transform_space { super::TransformSpace::Local => { @@ -155,7 +159,7 @@ fn sync_gizmo_transform( } } } else { - cmd.entity(gizmo.target).despawn_recursive(); + cmd.entity(ent).despawn_recursive(); } } } diff --git a/studio/src/interaction/mod.rs b/studio/src/interaction/mod.rs index a438ce4..dd37e9f 100644 --- a/studio/src/interaction/mod.rs +++ b/studio/src/interaction/mod.rs @@ -1,6 +1,6 @@ use bevy::prelude::*; -mod gizmos; +pub mod gizmos; pub mod selection; pub mod transformation; diff --git a/studio/src/interaction/selection.rs b/studio/src/interaction/selection.rs index 5500677..f73a138 100644 --- a/studio/src/interaction/selection.rs +++ b/studio/src/interaction/selection.rs @@ -25,6 +25,8 @@ impl Plugin for SelectionPlugin { .after(SceneSpawnerSystem), ); + app.add_systems(Update, tag_scene_elements); + #[cfg(feature = "wasm")] app.add_systems( Update, @@ -36,6 +38,8 @@ impl Plugin for SelectionPlugin { .map(bevy::utils::error) .run_if(resource_changed_or_removed::()), ); + + app.add_systems(Update, sync_editor_selected); } } @@ -48,6 +52,34 @@ pub struct Selected; #[derive(Resource, Deref, DerefMut, Debug)] pub struct Selection(Entity); +fn sync_editor_selected( + mut cmd: Commands, + q_new_selected: Query, Without)>, + q_editor_selected: Query>, + q_selected: Query>, + q_added_editor_selected: Query< + Entity, + (Without, Added), + >, +) { + for ent in q_new_selected.iter() { + for ed_ent in q_editor_selected.iter() { + cmd.entity(ed_ent) + .remove::(); + } + cmd.entity(ent).insert(space_editor::prelude::Selected); + } + + for ent in q_added_editor_selected.iter() { + cmd.entity(ent).insert(Selected); + cmd.insert_resource(Selection(ent)); + + for ent in q_selected.iter() { + cmd.entity(ent).remove::(); + } + } +} + fn tag_scene_elements( mut cmd: Commands, q_scene_elements: Query<(Entity, &Children), (With, Without)>, diff --git a/studio/src/lib.rs b/studio/src/lib.rs index 313179d..e69b20e 100644 --- a/studio/src/lib.rs +++ b/studio/src/lib.rs @@ -2,9 +2,9 @@ use bevy::{app::PluginGroupBuilder, prelude::*}; use bevy_mod_picking::DefaultPickingPlugins; -use bevy_web_asset::WebAssetPlugin; -use space_editor::prelude::space_undo::UndoPlugin; +use bevy_web_asset::WebAssetPlugin; +use bvh::BvhPlugin; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; @@ -16,28 +16,27 @@ mod input; mod inspector; mod interaction; mod math; +mod persistance; mod scene; mod skybox; mod ui; -mod viewport; +pub mod viewport; mod world_grid; -use crate::{ - bvh::BvhPlugin, +pub use crate::{ camera_controller::CameraControllerPlugin, debug::DebugPlugin, input::StudioInputPlugin, inspector::InspectorPlugin, interaction::InteractionPlugin, math::InterpolationPlugin, - scene::{LoadSceneEvent, ScenePlugin, TestBvh}, + scene::{LoadSceneEvent, ScenePlugin}, skybox::{SkyboxId, SkyboxPlugin}, ui::UiLayerPlugin, + viewport::ViewportSize, world_grid::WorldGridPlugin, }; -pub use crate::viewport::ViewportSize; - #[cfg(feature = "wasm")] mod wasm; @@ -61,7 +60,6 @@ struct CommonPlugins; impl PluginGroup for CommonPlugins { fn build(self) -> PluginGroupBuilder { PluginGroupBuilder::start::() - .add(BvhPlugin) .add(CameraControllerPlugin) .add(DebugPlugin) .add(InspectorPlugin) @@ -71,6 +69,8 @@ impl PluginGroup for CommonPlugins { .add(StudioInputPlugin) .add(UiLayerPlugin) .add(WorldGridPlugin) + .add(persistance::PersistancePlugin) + .add(BvhPlugin) } } @@ -79,41 +79,10 @@ impl PluginGroup for CommonPlugins { struct EditorPlugins; impl PluginGroup for EditorPlugins { fn build(self) -> PluginGroupBuilder { - PluginGroupBuilder::start::() - .add(InteractionPlugin) - .add(UndoPlugin) + PluginGroupBuilder::start::().add(InteractionPlugin) } } -#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startBvhViewer))] -pub fn start_bvh_viewer( - bvh_path: &str, - skybox_id: &str, - canvas_selector: &str, - canvas_alt: &str, - viewport_size: ViewportSize, -) { - let mut app = start_internal( - "Mannequin.gltf", - skybox_id, - canvas_selector, - canvas_alt, - viewport_size, - StudioMode::Viewer, - ); - - let bvh_path = bvh_path.to_string(); - - app.add_systems( - Startup, - move |mut cmd: Commands, r_assets: Res| { - cmd.insert_resource(TestBvh(r_assets.load(bvh_path.clone()))); - }, - ); - - app.run(); -} - #[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startViewer))] /// Start the application as a read-only viewer for a single library object. /// @@ -161,9 +130,35 @@ pub fn start_editor( .run(); } -// TODO: These `start_` functiions should be refactored to better support the -// needs of the "reaodnly viewer" vs "full studio editor" use cases. See -// notes in `studio-web/src/lib/viewer.element.ts` for more info. +#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startBvhViewer))] +pub fn start_bvh_viewer( + bvh_path: &str, + skybox_id: &str, + canvas_selector: &str, + canvas_alt: &str, + viewport_size: ViewportSize, +) { + let mut app = start_internal( + "Mannequin.gltf", + skybox_id, + canvas_selector, + canvas_alt, + viewport_size, + StudioMode::Viewer, + ); + + let bvh_path = bvh_path.to_string(); + + app.add_systems( + Startup, + move |mut cmd: Commands, r_assets: Res| { + cmd.insert_resource(scene::TestBvh(r_assets.load(bvh_path.clone()))); + }, + ); + + app.run(); +} + #[cfg_attr(feature = "wasm", wasm_bindgen)] pub fn start( object_id: &str, @@ -184,6 +179,10 @@ pub fn start( .run(); } +// TODO: These `start_` functiions should be refactored to better support the +// needs of the "reaodnly viewer" vs "full studio editor" use cases. See +// notes in `studio-web/src/lib/viewer.element.ts` for more info. + pub fn start_internal( object_id: &str, skybox_id: &str, diff --git a/studio/src/main.rs b/studio/src/main.rs index 1c51ae6..073d958 100644 --- a/studio/src/main.rs +++ b/studio/src/main.rs @@ -4,7 +4,7 @@ use studio::{StudioMode, ViewportSize}; fn main() { // Edit the arguments here to change the default startup scene or skybox let mut app = studio::start_internal( - "Mannequin.gltf", + "bookcase.gltf", "gum_trees_4k", "", // ignored for non-WASM "Storyteller Studio", diff --git a/studio/src/persistance/mod.rs b/studio/src/persistance/mod.rs new file mode 100644 index 0000000..6f5842c --- /dev/null +++ b/studio/src/persistance/mod.rs @@ -0,0 +1,45 @@ +use bevy::prelude::*; +use space_editor::prelude::*; + +pub struct PersistancePlugin; + +impl Plugin for PersistancePlugin { + fn build(&self, app: &mut App) { + info!("PersistancePlugin::build"); + app.editor_tab_by_trait( + EditorTabName::Other("Persistance".to_string()), + DebugPersistanceTab::default(), + ); + } +} + +#[derive(Resource, Default)] +pub struct DebugPersistanceTab {} + +impl EditorTab for DebugPersistanceTab { + fn ui( + &mut self, + ui: &mut bevy_inspector_egui::egui::Ui, + commands: &mut Commands, + world: &mut World, + ) { + if ui.button("Save").clicked() { + world.send_event(EditorEvent::Save(EditorPrefabPath::MemoryCahce)); + } + + if ui.button("Load").clicked() { + world.send_event(EditorEvent::Load(EditorPrefabPath::MemoryCahce)); + } + + if ui.button("Clear level").clicked() { + let mut query = world.query_filtered::>(); + for e in query.iter(world) { + commands.entity(e).despawn_recursive(); + } + } + } + + fn title(&self) -> bevy_inspector_egui::egui::WidgetText { + "Persistance".into() + } +} diff --git a/studio/src/scene.rs b/studio/src/scene.rs index a5aa086..7754b36 100644 --- a/studio/src/scene.rs +++ b/studio/src/scene.rs @@ -1,19 +1,22 @@ use bevy::{ - asset::AssetPath, + asset::{DependencyLoadState, LoadState, RecursiveDependencyLoadState}, core_pipeline::{ bloom::{BloomCompositeMode, BloomPrefilterSettings, BloomSettings}, fxaa::Fxaa, tonemapping::Tonemapping, }, gltf::Gltf, - math::{vec2, vec3, Affine3A}, + math::{vec2, Affine3A}, pbr::ShadowFilteringMethod, prelude::*, render::{mesh::skinning::SkinnedMesh, primitives::Aabb, view::NoFrustumCulling}, scene::InstanceId, }; -use space_editor::prelude::{space_undo::OneFrameUndoIgnore, PrefabMarker}; +use space_editor::{ + prelude::PrefabMarker, + space_prefab::{component::SceneAutoChild, editor_registry::EditorRegistryExt}, +}; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::wasm_bindgen; @@ -42,29 +45,36 @@ impl Plugin for ScenePlugin { ( spawn_test_bvh.run_if(resource_exists::()), // TODO init_scene_loading, - tick_scene_loading.run_if(in_state(SceneState::Loading)), - tick_scene_spawning.run_if(in_state(SceneState::Spawning)), ) .in_set(SceneSpawnerSystem), ) - .add_systems( - OnEnter(SceneState::Loading), - despawn_previous_scene.in_set(SceneSpawnerSystem), - ) - .add_systems( - OnEnter(SceneState::Spawning), - spawn_next_scene.in_set(SceneSpawnerSystem), - ) .add_systems( OnEnter(SceneState::Active), - ( - tag_scene_elements, - spawn_main_scene_light, - frame_scene_in_view, - ) - .in_set(SceneSpawnerSystem), + (tag_scene_elements, frame_scene_in_view).in_set(SceneSpawnerSystem), ); + app.add_systems(Update, tag_scene_elements); + + //Auto loading screen + //Register handles on which we will look to show loading screen + app.init_resource::(); + app.add_systems( + PostUpdate, + ( + check_loading, + auto_add_handle::, + auto_add_handle::, + auto_add_handle::, + ), + ); + + //setup default light + app.add_systems(Startup, minimal_scene_setup); + + app.editor_registry::(); + app.editor_registry::(); + app.editor_registry::(); + #[cfg(feature = "wasm")] app.add_systems( Update, @@ -78,19 +88,28 @@ impl Plugin for ScenePlugin { } } +fn minimal_scene_setup(mut cmd: Commands) { + cmd.spawn(DirectionalLightBundle { + directional_light: DirectionalLight { + shadows_enabled: false, + illuminance: 20000.0, + ..default() + }, + transform: Transform::default().looking_at(Vec3::NEG_ONE, Vec3::Y), + ..default() + }) + .insert(PrefabMarker); +} + #[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive(States, Clone, Copy, Default, Debug, PartialEq, Eq, Hash)] pub enum SceneState { #[default] None, Loading, - Spawning, Active, } -#[derive(Resource, Deref, DerefMut)] -struct PendingScene(pub Handle); - #[derive(Resource, Deref)] pub struct ActiveScene { #[deref] @@ -98,6 +117,62 @@ pub struct ActiveScene { pub source: Handle, } +#[derive(Resource, Default)] +pub struct LoadingQueue { + pub queue: Vec, +} + +fn check_loading( + mut loading_queue: ResMut, + mut st_next: ResMut>, + asset_server: Res, +) { + if !loading_queue.queue.is_empty() { + //filter queue to only include notloaded assets + + let mut not_loaded_count = 0; + + loading_queue.queue.retain(|h| { + if let Some((load_state, dep_load_state, rec_load_state)) = + asset_server.get_load_states(h.id()) + { + let is_loaded = dep_load_state == DependencyLoadState::Loaded; + + let is_failed = dep_load_state == DependencyLoadState::Failed + || rec_load_state == RecursiveDependencyLoadState::Failed + || load_state == LoadState::Failed; + + if load_state == LoadState::NotLoaded { + not_loaded_count += 1; + } + + !(is_failed || is_loaded) + } else { + false + } + }); + + info!("Loading {} assets", loading_queue.queue.len()); + + if loading_queue.queue.len() - not_loaded_count == 0 { + //not change state + } else { + st_next.set(SceneState::Loading); + } + } else { + st_next.set(SceneState::Active); + } +} + +fn auto_add_handle( + mut queue: ResMut, + query: Query<&Handle, Added>>, +) { + for h in query.iter() { + queue.queue.push(h.clone().untyped()); + } +} + #[derive(Event, Deref)] pub struct LoadSceneEvent(pub String); @@ -110,25 +185,32 @@ pub struct LoadSceneEvent(pub String); #[derive(Component)] pub struct SceneElement; +#[derive(Component)] +pub struct SceneData { + pub scene_path: String, +} + #[derive(Component)] pub struct SkeletalJoint(pub Color); #[derive(Component)] pub struct SceneRoot; - #[derive(Resource, Deref, DerefMut)] pub struct TestBvh(pub Handle); // TODO -#[derive(Component, Clone, Copy, Debug, Deref, DerefMut)] +#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)] +#[reflect(Component)] pub struct BindPoseLocal(pub Transform); -#[derive(Component, Clone, Copy, Debug, Deref, DerefMut)] +#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)] +#[reflect(Component)] pub struct BindPoseGlobal(pub Affine3A); -#[derive(Component, Clone, Copy, Debug, Deref, DerefMut)] +#[derive(Component, Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)] +#[reflect(Component)] pub struct InvBindPose(pub Affine3A); -fn initialize(mut cmd: Commands, r_assets: Res) { +fn initialize(mut cmd: Commands) { // Camera, graphics settings let focus = Vec3::ZERO; let camera_xform = Transform::from_xyz(-2.5, 4.5, 5.0).looking_at(focus, Vec3::Y); @@ -177,104 +259,29 @@ fn spawn_test_bvh( } } -fn init_scene_loading( - mut cmd: Commands, - assets: Res, - r_active_scene: Option>, - mut er: EventReader, - mut st_next: ResMut>, -) { +fn init_scene_loading(mut cmd: Commands, mut er: EventReader) { if let Some(id) = er.read().last() { - let name = &**id; - let path = format!("gltf/{name}"); - - // Avoid trying to re-load this scene if it's already active - if let Some(active_scene_path) = - r_active_scene.and_then(|scene| scene.source.path().cloned()) - { - if active_scene_path == AssetPath::parse(&path) { - return; - } - } - - cmd.insert_resource(PendingScene(assets.load(path))); - st_next.set(SceneState::Loading); - } -} - -fn despawn_previous_scene(mut cmd: Commands, q_scene_root: Query>) { - if let Ok(scene_root) = q_scene_root.get_single() { - cmd.entity(scene_root).despawn_recursive(); - } -} - -fn tick_scene_loading( - r_pending_scene: Res, - ra_gltfs: Res>, - mut er_scene_events: EventReader>, - mut st_next: ResMut>, -) { - for event in er_scene_events.read() { - if let AssetEvent::LoadedWithDependencies { id } = event { - let gltf = ra_gltfs.get(&**r_pending_scene).unwrap(); - if gltf.default_scene.is_some() && gltf.default_scene.as_ref().unwrap().id() == *id { - st_next.set(SceneState::Spawning); - break; - } - } - } -} - -fn spawn_next_scene( - mut cmd: Commands, - ra_gltfs: Res>, - r_pending_scene: Res, - mut r_scene_spawner: ResMut, -) { - let gltf = ra_gltfs.get(&**r_pending_scene).unwrap(); - let scene = gltf - .default_scene - .as_ref() - .expect("Expected glTF to include a default scene"); - - let parent = cmd - .spawn(( - Name::new("Scene"), - SceneRoot, - SceneElement, + info!("Loading scene: {}", id.0); + cmd.spawn(( SpatialBundle::default(), + space_editor::prelude::GltfPrefab { + path: format!("gltf/{}", id.0), + scene: "Scene0".to_string(), + }, PrefabMarker, - OneFrameUndoIgnore::default(), //To prevert undo try to register add event - )) - .id(); - - let instance = r_scene_spawner.spawn_as_child(scene.clone(), parent); - - cmd.remove_resource::(); - cmd.insert_resource(ActiveScene { - instance, - source: r_pending_scene.clone(), - }); -} - -fn tick_scene_spawning( - r_scene_spawner: Res, - r_active_scene: Res, - mut st_next: ResMut>, -) { - // TODO: The instance is considered "ready" even if shaders still need to be - // compiled, so this event isn't really sufficient for controlling a - // loading screen. - if r_scene_spawner.instance_is_ready(**r_active_scene) { - st_next.set(SceneState::Active); + SceneRoot, + )); } } +#[derive(Component)] +pub struct NoMeshElement; + +#[derive(Component)] +pub struct InitedKointSkinnedMesh; + fn tag_scene_elements( mut cmd: Commands, - r_scene_spawner: Res, - r_active_scene: Res, - ra_gltfs: Res>, // A "glTF mesh" is actually a container for one or more "glTF primitives", // which is basically a bundle of vertices paired with the material used to // shade them. It's those "sub-meshes" that carry the actual mesh handles, @@ -282,32 +289,39 @@ fn tag_scene_elements( // because they often consist of stuff like a single eyeball, or just the // leafy parts of a plant. Here we only want to tag the elements that a user // might want to individually address when arranging or animating a scene. - q_named_non_primitives: Query<&Name, Without>>, - q_skinned_meshes: Query<&SkinnedMesh>, + q_named_non_primitives: Query< + (Entity, &Children), + ( + With, + With, + Without>, + Without, + Without, + ), + >, + q_skinned_meshes: Query<(Entity, &SkinnedMesh), Without>, mut q_xforms: ParamSet<(TransformHelper, Query<&Transform>)>, + mesh_entities: Query>>, ) { - let gltf = ra_gltfs.get(&r_active_scene.source).unwrap(); - - for (ent, name) in r_scene_spawner - .iter_instance_entities(**r_active_scene) - .filter_map(|ent| match q_named_non_primitives.get(ent) { - Ok(name) => Some((ent, name)), - _ => None, - }) - { - if gltf.named_nodes.contains_key(&**name) { - cmd.entity(ent).insert(SceneElement).insert(PrefabMarker); + for (e, children) in q_named_non_primitives.iter() { + let mut has_mesh = false; + for child in children { + if mesh_entities.contains(*child) { + has_mesh = true; + break; + } + } + if has_mesh { + cmd.entity(e).insert(SceneElement); + } else { + cmd.entity(e).insert(NoMeshElement); } } - for (ent, skinned_mesh) in r_scene_spawner - .iter_instance_entities(**r_active_scene) - .filter_map(|ent| match q_skinned_meshes.get(ent) { - Ok(skinned_mesh) => Some((ent, skinned_mesh)), - _ => None, - }) - { - cmd.entity(ent).insert(NoFrustumCulling); + for (ent, skinned_mesh) in q_skinned_meshes.iter() { + cmd.entity(ent) + .insert(NoFrustumCulling) + .insert(InitedKointSkinnedMesh); for &joint in skinned_mesh.joints.iter() { let global_bind_pose = q_xforms.p0().compute_global_transform(joint).unwrap(); @@ -323,76 +337,17 @@ fn tag_scene_elements( } } -fn spawn_main_scene_light( - mut cmd: Commands, - r_scene_spawner: Res, - r_active_scene: Res, - q_aabbs: Query<(&Aabb, &GlobalTransform)>, - q_scene_root: Query>, -) { - let Ok(scene_root) = q_scene_root.get_single() else { - return; - }; - - let Some(aabb) = Aabb::combine( - r_scene_spawner - .iter_instance_entities(**r_active_scene) - .filter_map(|ent| q_aabbs.get(ent).ok()), - ) else { - return; - }; - - let light_offset_base = vec3(0.5, 1., 1.).normalize(); - let size = f32::max( - f32::max(aabb.half_extents.x, aabb.half_extents.y), - aabb.half_extents.z, - ) * 2.; - let light_pos = Vec3::from(aabb.center) + light_offset_base * size * 2.; - - cmd.spawn(DebugPoint { - point: light_pos, - color: Color::WHITE, - }); - cmd.entity(scene_root).with_children(|cmd| { - cmd.spawn(( - Name::new("Light"), - SceneElement, - SpotLightBundle { - spot_light: SpotLight { - color: Color::WHITE, - // TODO: This doesn't scale super well to very large objects - intensity: 200. * size, - radius: 0.1, - shadows_enabled: true, - shadow_normal_bias: 0.2 * size, - outer_angle: f32::to_radians(150.), - inner_angle: f32::to_radians(45.), - ..default() - }, - transform: Transform::from_translation(light_pos) - .looking_at(aabb.center.into(), Vec3::Y), - ..default() - }, - )); - }); -} - fn frame_scene_in_view( mut cmd: Commands, - r_scene_spawner: Res, - r_active_scene: Res, - q_aabbs: Query<(&Aabb, &GlobalTransform)>, + q_aabbs: Query<(&Aabb, &GlobalTransform), With>, mut q_camera: Query<(&mut CameraController, &mut Transform, &Projection)>, ) { let Ok((mut cam, mut cam_xform, cam_proj)) = q_camera.get_single_mut() else { + error!("Failed to get camera controller"); return; }; - let Some(aabb) = Aabb::combine( - r_scene_spawner - .iter_instance_entities(**r_active_scene) - .filter_map(|ent| q_aabbs.get(ent).ok()), - ) else { + let Some(aabb) = Aabb::combine(q_aabbs.iter()) else { return; };