diff --git a/assets/editor/entities/camera.entity b/assets/editor/entities/camera.entity new file mode 100644 index 0000000..431e0d0 --- /dev/null +++ b/assets/editor/entities/camera.entity @@ -0,0 +1,6 @@ +uuid 739619d7-b8a1-42b4-8803-2b8cfedeebf1 +name "Editor Camera" +editor_tag +camera render_target window b90f31a2-34d9-42c2-9e53-646d9c9c6c70 +fly_camera +transform transform 10.0 10.0 10.0 \ No newline at end of file diff --git a/assets/editor/entities/ui_container.entity b/assets/editor/entities/ui_container.entity new file mode 100644 index 0000000..d773f03 --- /dev/null +++ b/assets/editor/entities/ui_container.entity @@ -0,0 +1,4 @@ +uuid 08e1a4d9-6c76-471d-af02-1aa7db1b435a +editor_tag +ui_node +target_camera 739619d7-b8a1-42b4-8803-2b8cfedeebf1 \ No newline at end of file diff --git a/assets/editor/entities/ui_title.entity b/assets/editor/entities/ui_title.entity new file mode 100644 index 0000000..c5e5b78 --- /dev/null +++ b/assets/editor/entities/ui_title.entity @@ -0,0 +1,4 @@ +uuid 0088bc7d-851e-402b-9d20-fadfe5cab106 +editor_tag +ui_text text "Welcome to the editor" color 1.0 1.0 1.0 1.0 +parent 08e1a4d9-6c76-471d-af02-1aa7db1b435a \ No newline at end of file diff --git a/assets/editor/entities/window.entity b/assets/editor/entities/window.entity new file mode 100644 index 0000000..f04551a --- /dev/null +++ b/assets/editor/entities/window.entity @@ -0,0 +1,4 @@ +uuid b90f31a2-34d9-42c2-9e53-646d9c9c6c70 +editor_tag +name "Editor Window" +window title "Editor" visible false \ No newline at end of file diff --git a/src/editor.rs b/src/editor.rs index 76c3606..d31f877 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,5 +1,3 @@ -use bevy::render::primitives::Aabb; - use crate::prelude::*; pub(crate) struct EditorPlugin; @@ -7,18 +5,12 @@ pub(crate) struct EditorPlugin; impl Plugin for EditorPlugin { fn build(&self, app: &mut App) { app.init_state::() - .add_systems(Startup, init_editor) + .add_systems(OnEnter(EditorState::Open), spawn_editor) + .add_systems(OnExit(EditorState::Open), despawn_editor) .add_systems( Update, toggle_editor_state.run_if(input_just_pressed(KeyCode::F3)), ) - .add_systems( - Update, - ( - toggle_editor_window, - toggle_entity_aabb, - ).run_if(state_changed::), - ) .add_systems( Update, ( @@ -64,57 +56,6 @@ impl std::convert::From<&EditorState> for bool { #[derive(Component)] struct EditorTag; -/// At startup initialize some editor components -fn init_editor(mut commands: Commands) { - // Editor window - let editor_window = commands - .spawn(( - EditorTag, - Window { - visible: false, - title: "Editor".into(), - ..default() - }, - )) - .id(); - - // Editor camera - let editor_camera = commands - .spawn(( - EditorTag, - FlyCamera, - Camera3dBundle { - camera: Camera { - target: RenderTarget::Window(WindowRef::Entity(editor_window)), - ..default() - }, - transform: Transform::from_xyz(10.0, 10.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), - ..default() - }, - )) - .id(); - - // Editor UI elements - commands - .spawn(( - EditorTag, - TargetCamera(editor_camera), - NodeBundle { ..default() }, - )) - .with_children(|parent| { - parent.spawn(( - EditorTag, - TextBundle::from_section( - "Welcome to the editor", - TextStyle { - color: WHITE.into(), - ..default() - }, - ), - )); - }); -} - fn toggle_editor_state( curr_state: Res>, mut next_state: ResMut>, @@ -132,18 +73,16 @@ fn toggle_editor_state( } } -fn toggle_editor_window( - state: Res>, - mut windows: Query<&mut Window, With>, - mut cameras: Query<&mut Camera, With>, +fn spawn_editor( + mut _commands: Commands, ) { - let curr = state.get().into(); - cameras.iter_mut().for_each(|mut camera| { - camera.is_active = curr; - }); - windows.iter_mut().for_each(|mut window| { - window.visible = curr; - }) + todo!("Spawn editor"); +} + +fn despawn_editor( + mut _comands: Commands, +) { + todo!("Despawn editor"); } /// Handles window close requests which are only weird because of the editor @@ -184,44 +123,4 @@ fn plane_gizmos(mut gizmos: Gizmos) { gizmos.arrow(Vec3::ZERO, Vec3::Y, DARK_GREEN); gizmos.arrow(Vec3::ZERO, Vec3::Z, BLUE); } -} - -fn toggle_entity_aabb( - state: Res>, - query: Query>>, - children: Query<&Children>, - aabbs: Query<(Entity, &Aabb)>, - mut commands: Commands, -) { - let add_component = match state.get() { - EditorState::Open => true, - EditorState::Closed => false, - }; - query.iter().for_each(|root| { - if add_component { - commands.entity(root).insert(ShowAabbGizmo { ..default() }); - } else { - commands.entity(root).remove::(); - } - - let mut child_has_bounding_box = false; - for child in children.iter_descendants(root) { - if add_component { - commands.entity(child).insert(ShowAabbGizmo { ..default() }); - } else { - commands.entity(child).remove::(); - } - if aabbs.contains(child) { - commands.entity(child).insert(Aabb { center: Vec3A::ZERO, half_extents: Vec3A::ONE }); - } else { - child_has_bounding_box = true; - } - } - - if !aabbs.contains(root) && !child_has_bounding_box { - commands.entity(root).insert(Aabb { center: Vec3A::ZERO, half_extents: Vec3A::ONE }); - } else { - commands.entity(root).remove::(); - } - }) } \ No newline at end of file diff --git a/src/prelude.rs b/src/prelude.rs index c0203ac..f1cfc9a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,14 +1,15 @@ pub(crate) use bevy::{ app::AppExit, asset::{io::Reader, AssetLoader, LoadContext, AsyncReadExt}, - color::palettes::css::{WHITE, GRAY, RED, DARK_GREEN, BLUE}, + color::palettes::css::{GRAY, RED, DARK_GREEN, BLUE}, gltf::Gltf, input::common_conditions::input_just_pressed, - math::Vec3A, prelude::*, render::camera::RenderTarget, window::{WindowRef, WindowCloseRequested, PrimaryWindow}, }; +pub(crate) use bevy::ecs::reflect::ReflectCommandExt; +pub(crate) use nom::bytes::complete::take; pub(crate) use nom::{ IResult, bytes::complete::{tag, take_until1}, @@ -20,7 +21,5 @@ pub(crate) use uuid::Uuid; pub(crate) use thiserror::Error; pub(crate) use crate::{ - camera::*, conditions::*, - save::*, }; diff --git a/src/save.rs b/src/save.rs index 338fe06..d67004a 100644 --- a/src/save.rs +++ b/src/save.rs @@ -5,7 +5,11 @@ pub(crate) struct SavePlugin; impl Plugin for SavePlugin { fn build(&self, app: &mut App) { - app.init_asset::() + app + .register_type::() + .register_type::() + .register_type::() + .init_asset::() .init_asset_loader::() .add_systems(Startup, test_save_entity) .add_systems( @@ -15,17 +19,16 @@ impl Plugin for SavePlugin { } } -#[derive(Asset, TypePath, Debug, Default)] +#[derive(Asset, TypePath, Default)] pub(crate) struct SaveEntity { - transform: Option, - name: Option, - uuid: Option, - model: Option<(Handle, String)>, - // TODO: Option feels like an antipattern as this is either Some(true) or None and never Some(false). - // Either encode more Camera data in this or replace it with Option<()> or just bool. - camera: Option, + components: Vec>, } +// TODO: SCALABILITY: SaveEntity should maybe be a HashSet or Vec of Box +// Then we tell the parse "For each line, run through each of these concrete parsers" +// If it matches, add it to the set of Box +// Ironically we basically want DynamicEntity: https://docs.rs/bevy/latest/bevy/scene/struct.DynamicEntity.html + impl SaveEntity { fn parse( text: &str, @@ -34,25 +37,32 @@ impl SaveEntity { let lines = text.split('\n'); let mut entity = SaveEntity { ..default() }; lines.into_iter().for_each(|line| { + if let Ok(name) = parse::parse_save_name(line) { - entity.name = Some(name); + entity.components.push(name.clone_value()); } else if let Ok(transform) = parse::parse_save_transform(line) { - entity.transform = Some(transform); + entity.components.push(transform.clone_value()); } else if let Ok(uuid) = parse::parse_save_uuid(line) { - entity.uuid = Some(EntityUuid(uuid)); - } else if let Ok((gltf_path, scene_name)) = parse::parse_save_model(line) { - let handle = load_context.load(gltf_path); - entity.model = Some((handle, scene_name)); - } else if let Ok(true) = parse::parse_save_camera(line) { - entity.camera = Some(true); + entity.components.push(uuid.clone_value()); + } else if let Ok((gltf_file, scene_name)) = parse::parse_save_model(line) { + let gltf_scene = GltfScene { gltf: load_context.load(gltf_file), name: scene_name }; + entity.components.push(gltf_scene.clone_value()); + } else if let Ok(target) = parse::parse_save_camera(line) { + entity.components.push(target.clone_value()); + } else { + error!("Failed to parse line component `{:?}`", line); } }); Ok(entity) } } -#[derive(Component, Clone, Debug)] -struct EntityUuid(Uuid); +#[derive(Component, Clone, Debug, Reflect, PartialEq)] +struct GltfScene { + gltf: Handle, + name: String, +} + mod parse { use super::*; @@ -67,6 +77,8 @@ mod parse { Transform, #[error("Failed to parse Entity")] Nom(nom::Err>>), + #[error("Failed to parse camera")] + Camera, } // Convert Nom error to parse error @@ -107,12 +119,6 @@ mod parse { } /// - /// ``` - /// let parsed = parse_save_transform("transform translation 1.0 2.0 3.0 rotation 1.0 0.0 0.0 0.0 scale 1.0 1.0 1.0").unwrap(); - /// let expected = Transform { translation: Vec3::new(1.0, 1.0, 1.0), rotation: Quat::from_xzyw(1.0, 0.0, 0.0, 0.0), scale: Vec3::ONE }; - /// - /// assert_eq!(parsed, expected); - /// ``` /// pub(crate) fn parse_save_transform(line: &str) -> Result { let (rem, _) = tag("transform")(line)?; @@ -121,7 +127,6 @@ mod parse { let mut curr = rem.trim_start(); for _ in 0..3 { - println!("Curr: {:?}", curr); if let Ok((rem, (_, _, (x, y, z)))) = tuple((parse_word("translation"), space1, parse_xyz))(curr.trim_start()) { transform.translation = Vec3::new(x, y, z); curr = rem.trim_start(); @@ -156,12 +161,6 @@ mod parse { } /// - /// ``` - /// let parsed = parse_save_name("name asfd").unwrap(); - /// let expected = Name::new("asdf"); - /// - /// assert_eq!(parsed, expected); - /// ``` /// pub(crate) fn parse_save_name(line: &str) -> Result { let (remainder, _) = tag("name")(line)?; @@ -192,31 +191,31 @@ mod parse { } } + + #[derive(Component, Clone, Debug, Reflect, PartialEq)] + pub(crate) struct EntityUuid(Uuid); + /// - /// ``` - /// let parsed = parse_save_uuid("uuid 339dd18f-761a-4997-b515-ef57f4dc2447").unwrap(); - /// let expected = Uuid::parse_str("339dd18f-761a-4997-b515-ef57f4dc2447").unwrap(); - /// - /// assert_eq!(parsed, expected); - /// ``` /// - pub(crate) fn parse_save_uuid(line: &str) -> Result { + pub(crate) fn parse_save_uuid(line: &str) -> Result { let (remainder, _) = tag("uuid")(line)?; let uuid = Uuid::try_parse(remainder.trim())?; - Ok(uuid) + Ok(EntityUuid(uuid)) } #[test] fn test_parse_uuid() { let line = "uuid 1c16ab9a-5f79-4340-8469-4086f69c64f2"; let parsed = parse_save_uuid(line).unwrap(); - let expected = Uuid::parse_str("1c16ab9a-5f79-4340-8469-4086f69c64f2").unwrap(); + let expected = EntityUuid(Uuid::parse_str("1c16ab9a-5f79-4340-8469-4086f69c64f2").unwrap()); assert_eq!(parsed, expected); } + /// + /// pub(crate) fn parse_save_model(line: &str) -> Result<(String, String), SaveEntityParseError> { - let (rem, (_, _, gltf_name, _, scene_name)) = tuple(( + let (rem, (_, _, gltf_path, _, scene_name)) = tuple(( tag("model"), space1, parse_string, @@ -224,9 +223,9 @@ mod parse { parse_string, ))(line)?; - debug_assert_eq!(rem, ""); + debug_assert!(rem == ""); - Ok((String::from(gltf_name), String::from(scene_name))) + Ok((gltf_path.into(), scene_name.into())) } #[test] @@ -238,9 +237,45 @@ mod parse { assert_eq!(parsed, expected); } - pub(crate) fn parse_save_camera(line: &str) -> Result { - let (_rem, cam) = tag("camera")(line)?; - Ok(!cam.is_empty()) + #[derive(Debug, Default, PartialEq, Reflect)] + pub(crate) enum SaveCameraRenderTarget { + #[default] + Default, + Window(Uuid), + } + + pub(crate) fn parse_save_camera(line: &str) -> Result { + if let Ok((rem, _camera)) = tag::<&str, &str, ()>("camera")(line) { + if let Ok((rem, (_, _target))) = tuple((space1::<&str, ()>, tag("target")))(rem) { + if let Ok((rem, (_, _window))) = tuple((space1::<&str, ()>, tag("window")))(rem) { + // Camera + target + window + UUID + if let Ok((rem, (_, uuid))) = tuple((space1::<&str, ()>, take(36usize)))(rem) { + debug_assert!(rem == ""); + + let parsed_uuid = Uuid::parse_str(uuid)?; + Ok(SaveCameraRenderTarget::Window(parsed_uuid)) + // Camera + target + widow + } else { + debug_assert!(rem == ""); + + Ok(SaveCameraRenderTarget::Default) + } + // camera + target + } else { + debug_assert!(rem == ""); + + Err(SaveEntityParseError::Camera) + } + // Just camera + } else { + debug_assert!(rem == ""); + + Ok(SaveCameraRenderTarget::Default) + } + // Nothing parsed well + } else { + Err(SaveEntityParseError::Camera) + } } #[test] @@ -248,7 +283,25 @@ mod parse { { let line = "camera"; let parsed = parse_save_camera(line).unwrap(); - let expected = true; + let expected = SaveCameraRenderTarget::Default; + assert_eq!(parsed, expected); + } + { + let line = "camera target window"; + let parsed = parse_save_camera(line).unwrap(); + let expected = SaveCameraRenderTarget::Default; + assert_eq!(parsed, expected); + } + { + let line = "camera target"; + let parsed = parse_save_camera(line); + assert!(parsed.is_err()); + } + { + let line = "camera target window 9a1367e0-71e5-4c79-b4ad-02aa44b68af0"; + let target_uuid = Uuid::parse_str("9a1367e0-71e5-4c79-b4ad-02aa44b68af0").unwrap(); + let parsed = parse_save_camera(line).unwrap(); + let expected = SaveCameraRenderTarget::Window(target_uuid); assert_eq!(parsed, expected); } { @@ -307,7 +360,6 @@ fn check_loaded_entity_assets( mut events: EventReader>, save_entities: Res>, mut commands: Commands, - gltfs: Res>, ) { events.read().for_each(|event| { if let AssetEvent::LoadedWithDependencies { id } = event { @@ -316,51 +368,11 @@ fn check_loaded_entity_assets( .filter(|(_, handle)| handle.id() == *id) .for_each(|(entity, _handle)| { let saved = save_entities.get(*id).unwrap(); - debug!( - "Updating entity {:?} ({:?}) because asset changed", - saved.name, saved.uuid - ); let mut e = commands.entity(entity); - // Apply camera - // Should be applied early to avoid clobbering transform - if let Some(is_camera) = &saved.camera { - if *is_camera { - e.insert(Camera3dBundle { ..default() }); - } - } else { - e.remove::<(Camera, Camera3d)>(); - } - // Apply transform - if let Some(transform) = &saved.transform { - // TODO: Only update if different - e.insert(*transform); - } else { - e.remove::(); - } - // Apply Name - if let Some(name) = &saved.name { - // TODO: Only update if different - e.insert(name.clone()); - } else { - e.remove::(); - } - // Apply Uuid - if let Some(uuid) = &saved.uuid { - // TODO: Only update if different - e.insert(uuid.clone()); - } else { - e.remove::(); - } - // Apply Model - if let Some((gltf_handle, scene_name)) = &saved.model { - // Find scene and update - let gltf = gltfs.get(gltf_handle).unwrap(); - let scene_handle = gltf.named_scenes.get(scene_name.as_str().into()).unwrap(); - e.insert(scene_handle.clone()); - } else { - e.remove::>(); - } + saved.components.iter().for_each(|dc| { + e.insert_reflect(dc.clone_value()); + }); }); } })