diff --git a/assets/README.md b/assets/README.md index e69de29..c7465d3 100644 --- a/assets/README.md +++ b/assets/README.md @@ -0,0 +1,17 @@ +# Assets + +## Custom Assets + +### `.entity` files + +Files ending in `.entity` store entity information. + +Note: This data is not auto-magically de/serialized by Bevy, we write our own parser using `nom`. +This custom reading/writing can be found in `src/save.rs`. + +All `.entity` files list one component per line. +Components include: +* `name ` +* `uuid ` +* `transform translation rotation scale ` +* `model "" ""` \ No newline at end of file diff --git a/assets/levels/00/entities/van.entity b/assets/levels/00/entities/van.entity index aaf1072..b74cf27 100644 --- a/assets/levels/00/entities/van.entity +++ b/assets/levels/00/entities/van.entity @@ -1,3 +1,4 @@ name van uuid 5c270e84-814c-4d51-9ccd-ab79d9e01f1d -transform translation 1.0 1.0 1.0 rotation 0.0 0.0 0.0 1.0 scale 0.5 0.5 0.5 \ No newline at end of file +transform translation 0.0 0.0 0.0 rotation 0.0 0.0 0.0 1.0 scale 1.0 1.0 1.0 +model "models/van.glb" "Scene" \ No newline at end of file diff --git a/src/prelude.rs b/src/prelude.rs index 82ff9e6..649297d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -11,6 +11,7 @@ pub(crate) use bevy::window::WindowCloseRequested; pub(crate) use bevy::window::WindowRef; pub(crate) use nom::bytes::complete::tag; pub(crate) use nom::bytes::complete::take; +pub(crate) use nom::bytes::complete::take_until1; pub(crate) use nom::number::complete::float; pub(crate) use nom::sequence::tuple; diff --git a/src/save.rs b/src/save.rs index d6fb91e..aa5c2de 100644 --- a/src/save.rs +++ b/src/save.rs @@ -1,4 +1,4 @@ -use bevy::{math::Vec3A, render::primitives::Aabb}; +use bevy::{gltf::Gltf, math::Vec3A, render::primitives::Aabb}; use nom::IResult; use crate::prelude::*; @@ -11,7 +11,10 @@ impl Plugin for SavePlugin { app.init_asset::() .init_asset_loader::() .add_systems(Startup, test_save_entity) - .add_systems(Update, check_loaded_entity_assets.run_if(on_event::>())); + .add_systems( + Update, + check_loaded_entity_assets.run_if(on_event::>()), + ); } } @@ -20,11 +23,15 @@ struct SaveEntity { transform: Option, name: Option, uuid: Option, + model: Option<(Handle, String)>, } impl SaveEntity { - fn parse(s: &str) -> Result { - let lines = s.split('\n'); + fn parse( + text: &str, + load_context: &mut LoadContext, + ) -> Result { + 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) { @@ -33,6 +40,9 @@ impl SaveEntity { entity.transform = Some(transform); } 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)); } }); Ok(entity) @@ -43,7 +53,7 @@ impl SaveEntity { struct EntityUuid(Uuid); mod parse { - use super::*; + use super::*; #[derive(Error, Debug)] pub(crate) enum SaveEntityParseError { @@ -87,6 +97,11 @@ mod parse { .map(|(s, (w, _, x, _, y, _, z))| (s, (w, x, y, z))) } + fn parse_string<'a>(i: &'a str) -> IResult<&'a str, &'a str> { + let (rem, (_, out, _)) = tuple((tag("\""), take_until1("\""), tag("\"")))(i)?; + Ok((rem, out)) + } + /// /// ``` /// 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(); @@ -119,9 +134,25 @@ mod parse { } } + // Assert there are no trailing characters on the line + debug_assert_eq!(curr, ""); + Ok(transform) } + #[test] + fn test_parse_transform() { + let line = "transform translation 1.0 2.0 3.0 rotation 0.1 0.2 0.3 1.0 scale 1.1 1.2 1.3"; + let parsed = parse_save_transform(line).unwrap(); + let expected = Transform { + translation: Vec3::new(1.0, 2.0, 3.0), + rotation: Quat::from_xyzw(0.1, 0.2, 0.3, 1.0), + scale: Vec3::new(1.1, 1.2, 1.3), + }; + + assert_eq!(parsed, expected); + } + /// /// ``` /// let parsed = parse_save_name("name asfd").unwrap(); @@ -141,6 +172,24 @@ mod parse { } } + #[test] + fn test_parse_name() { + { + let line = "name Van"; + let parsed = parse_save_name(line).unwrap(); + let expected = Name::new("Van"); + + assert_eq!(parsed, expected); + } + { + let line = "name Big Mike"; + let parsed = parse_save_name(line).unwrap(); + let expected = Name::new("Big Mike"); + + assert_eq!(parsed, expected); + } + } + /// /// ``` /// let parsed = parse_save_uuid("uuid 339dd18f-761a-4997-b515-ef57f4dc2447").unwrap(); @@ -155,49 +204,37 @@ mod parse { Ok(uuid) } - mod test { - use super::*; - - #[test] - fn test_parse_transform() { - let line = - "transform translation 1.0 2.0 3.0 rotation 0.1 0.2 0.3 1.0 scale 1.1 1.2 1.3"; - let parsed = parse_save_transform(line).unwrap(); - let expected = Transform { - translation: Vec3::new(1.0, 2.0, 3.0), - rotation: Quat::from_xyzw(0.1, 0.2, 0.3, 1.0), - scale: Vec3::new(1.1, 1.2, 1.3), - }; + #[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(); - assert_eq!(parsed, expected); - } + assert_eq!(parsed, expected); + } - #[test] - fn test_parse_name() { - { - let line = "name Van"; - let parsed = parse_save_name(line).unwrap(); - let expected = Name::new("Van"); + pub(crate) fn parse_save_model(line: &str) -> Result<(String, String), SaveEntityParseError> { + println!("{}", line); + let (rem, (_, _, gltf_name, _, scene_name)) = tuple(( + tag("model"), + take(1usize), + parse_string, + take(1usize), + parse_string, + ))(line)?; - assert_eq!(parsed, expected); - } - { - let line = "name Big Mike"; - let parsed = parse_save_name(line).unwrap(); - let expected = Name::new("Big Mike"); + debug_assert_eq!(rem, ""); - assert_eq!(parsed, expected); - } - } + Ok((String::from(gltf_name), String::from(scene_name))) + } - #[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(); + #[test] + fn test_parse_model() { + let line = "model \"models/foo.glb\" \"My Scene\""; + let parsed = parse_save_model(line).unwrap(); + let expected = (String::from("models/foo.glb"), String::from("My Scene")); - assert_eq!(parsed, expected); - } + assert_eq!(parsed, expected); } } @@ -221,14 +258,14 @@ impl AssetLoader for SaveEntityLoader { &'a self, reader: &'a mut Reader, _settings: &'a (), - _load_context: &'a mut LoadContext, + load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, Result> { Box::pin(async move { let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; let s = std::str::from_utf8(bytes.as_slice()).unwrap(); - Ok(SaveEntity::parse(s)?) + Ok(SaveEntity::parse(s, load_context)?) }) } @@ -239,24 +276,36 @@ impl AssetLoader for SaveEntityLoader { fn test_save_entity(loader: Res, mut commands: Commands) { let handle: Handle = loader.load("levels/00/entities/van.entity"); - commands.spawn((SpatialBundle { ..default() }, handle, ShowAabbGizmo { ..default() }, Aabb { center: Vec3A::ZERO, half_extents: Vec3A::ONE })); + commands.spawn(( + SpatialBundle { ..default() }, + handle, + ShowAabbGizmo { ..default() }, + Aabb { + center: Vec3A::ZERO, + half_extents: Vec3A::ONE, + }, + )); } fn check_loaded_entity_assets( query: Query<(Entity, &Handle)>, - mut events: EventReader>, - save_entities: Res>, + mut events: EventReader>, + save_entities: Res>, mut commands: Commands, + gltfs: Res>, ) { - events.read().for_each(|event| { - match event { - AssetEvent::LoadedWithDependencies { id } => { + events.read().for_each(|event| { + match event { + AssetEvent::LoadedWithDependencies { id } => { query .iter() .filter(|(_, handle)| handle.id() == *id) - .for_each(|(entity, _handle)|{ + .for_each(|(entity, _handle)| { let saved = save_entities.get(*id).unwrap(); - debug!("Updating entity {:?} ({:?}) because asset changed", saved.name, saved.uuid); + debug!( + "Updating entity {:?} ({:?}) because asset changed", + saved.name, saved.uuid + ); let mut e = commands.entity(entity); // Apply transform @@ -280,9 +329,18 @@ fn check_loaded_entity_assets( } 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).unwrap(); + e.insert(scene_handle.clone()); + } else { + e.remove::>(); + } }); - } - _ => () - } - }) -} \ No newline at end of file + } + _ => (), + } + }) +}