Compare commits
9 Commits
4bc704aff0
...
21e67e04ca
| Author | SHA1 | Date |
|---|---|---|
|
|
21e67e04ca | 1 year ago |
|
|
07bbbfa25b | 1 year ago |
|
|
9a83c06716 | 1 year ago |
|
|
a54255c363 | 1 year ago |
|
|
edb15ffe49 | 1 year ago |
|
|
84744a9397 | 1 year ago |
|
|
cfe3a9ce17 | 1 year ago |
|
|
8027af5303 | 1 year ago |
|
|
63fab851ac | 1 year ago |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,18 @@
|
|||||||
|
# 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 <string>` Human readable name for entity
|
||||||
|
* `uuid <UUID string>` Globally unique ID for entity
|
||||||
|
* `transform translation <f32> <f32> <f32> rotation <f32> <f32> <f32> <f32> scale <f32> <f32> <f32>` 3D Location, Rotation, and Scale for entity
|
||||||
|
* `model "<path string>" "<string>"` Declares the entity's 3d model
|
||||||
|
* `camera` Marks the entity as a camera
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
name camera
|
||||||
|
uuid 2e45b7e9-6722-4d50-8ea5-67f25b8b0f62
|
||||||
|
transform translation 2.0 2.0 0.0 rotation 0.0 0.0 0.0 1.0 scale 1.0 1.0 1.0
|
||||||
|
camera
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
name van
|
||||||
|
uuid 5c270e84-814c-4d51-9ccd-ab79d9e01f1d
|
||||||
|
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"
|
||||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,127 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// Menu Plugin; empty struct for Plugin impl
|
||||||
|
pub(crate) struct CameraPlugin;
|
||||||
|
|
||||||
|
impl Plugin for CameraPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
move_editor_fly_camera.run_if(any_with_component::<FlyCamera>),
|
||||||
|
);
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
rotate_editor_fly_camera.run_if(any_with_component::<FlyCamera>),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub(crate) struct FlyCamera;
|
||||||
|
|
||||||
|
/// Fly camera system for moving around like a drone
|
||||||
|
/// TODO: Only if key is pressed!
|
||||||
|
fn move_editor_fly_camera(
|
||||||
|
mut cameras: Query<(&Camera, &mut Transform), With<FlyCamera>>,
|
||||||
|
windows: Query<&Window>,
|
||||||
|
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||||
|
keys: Res<ButtonInput<KeyCode>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
(keys.any_pressed([
|
||||||
|
KeyCode::KeyW,
|
||||||
|
KeyCode::KeyS,
|
||||||
|
KeyCode::KeyA,
|
||||||
|
KeyCode::KeyD,
|
||||||
|
KeyCode::KeyQ,
|
||||||
|
KeyCode::KeyE,
|
||||||
|
]))
|
||||||
|
.then(|| {
|
||||||
|
// Iterate over all cameras
|
||||||
|
cameras.iter_mut().for_each(|(c, mut t)| {
|
||||||
|
// Determine which window this camera is attached to
|
||||||
|
let target_window = match c.target {
|
||||||
|
RenderTarget::Window(wr) => match wr {
|
||||||
|
WindowRef::Entity(e) => Some(e),
|
||||||
|
WindowRef::Primary => Some(primary_window.get_single().unwrap()),
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let window = windows.get(target_window.unwrap()).unwrap();
|
||||||
|
|
||||||
|
// If the target window is focused
|
||||||
|
window.focused.then(|| {
|
||||||
|
let move_speed = if keys.pressed(KeyCode::ShiftLeft) {
|
||||||
|
16.0
|
||||||
|
} else {
|
||||||
|
4.0
|
||||||
|
};
|
||||||
|
let mut delta = Vec3::ZERO;
|
||||||
|
if keys.pressed(KeyCode::KeyW) {
|
||||||
|
delta += t.forward() * move_speed * time.delta_seconds()
|
||||||
|
}
|
||||||
|
if keys.pressed(KeyCode::KeyS) {
|
||||||
|
delta += t.back() * move_speed * time.delta_seconds()
|
||||||
|
}
|
||||||
|
if keys.pressed(KeyCode::KeyA) {
|
||||||
|
delta += t.left() * move_speed * time.delta_seconds()
|
||||||
|
}
|
||||||
|
if keys.pressed(KeyCode::KeyD) {
|
||||||
|
delta += t.right() * move_speed * time.delta_seconds()
|
||||||
|
}
|
||||||
|
if keys.pressed(KeyCode::KeyE) {
|
||||||
|
delta += Vec3::Y * move_speed * time.delta_seconds()
|
||||||
|
}
|
||||||
|
if keys.pressed(KeyCode::KeyQ) {
|
||||||
|
delta += Vec3::NEG_Y * move_speed * time.delta_seconds()
|
||||||
|
}
|
||||||
|
t.translation += delta;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_editor_fly_camera(
|
||||||
|
mut cameras: Query<(&Camera, &mut Transform), With<FlyCamera>>,
|
||||||
|
windows: Query<&Window>,
|
||||||
|
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||||
|
mouse: Res<ButtonInput<MouseButton>>,
|
||||||
|
mut cursor_events: EventReader<CursorMoved>,
|
||||||
|
) {
|
||||||
|
(!cursor_events.is_empty()).then(|| {
|
||||||
|
// Iterate over all cameras
|
||||||
|
cameras.iter_mut().for_each(|(c, mut t)| {
|
||||||
|
// Determine which window this camera is attached to
|
||||||
|
let target_window = match c.target {
|
||||||
|
RenderTarget::Window(wr) => match wr {
|
||||||
|
WindowRef::Entity(e) => Some(e),
|
||||||
|
WindowRef::Primary => Some(primary_window.get_single().unwrap()),
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let window = windows.get(target_window.unwrap()).unwrap();
|
||||||
|
if mouse.pressed(MouseButton::Middle) {
|
||||||
|
cursor_events
|
||||||
|
.read()
|
||||||
|
.filter_map(|CursorMoved { delta, window, .. }| {
|
||||||
|
(*window == target_window.unwrap()).then_some(delta)
|
||||||
|
})
|
||||||
|
.for_each(|delta| {
|
||||||
|
if let Some(Vec2 { x, y }) = delta {
|
||||||
|
// Cribbing from bevy_flycam
|
||||||
|
// Link: https://github.com/sburris0/bevy_flycam/blob/baffe50e0961ad1491d467fa6ab5551f9f21db8f/src/lib.rs#L145-L151
|
||||||
|
let (mut yaw, mut pitch, _) = t.rotation.to_euler(EulerRot::YXZ);
|
||||||
|
let window_scale = window.height().min(window.width());
|
||||||
|
let sensitivity = 0.00012;
|
||||||
|
pitch -= (sensitivity * y * window_scale).to_radians();
|
||||||
|
yaw -= (sensitivity * x * window_scale).to_radians();
|
||||||
|
t.rotation = Quat::from_axis_angle(Vec3::Y, yaw)
|
||||||
|
* Quat::from_axis_angle(Vec3::X, pitch);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cursor_events.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,5 +1,8 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
pub(crate) fn any_component_added<T: Component>(q: Query<Entity, Added<T>>) -> bool {
|
///
|
||||||
|
/// ECS scheduler active when an entity has a given component added
|
||||||
|
///
|
||||||
|
pub(crate) fn any_component_added<C: Component>(q: Query<Entity, Added<C>>) -> bool {
|
||||||
!q.is_empty()
|
!q.is_empty()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,26 @@
|
|||||||
pub(crate) use bevy::app::AppExit;
|
pub(crate) use bevy::{
|
||||||
pub(crate) use bevy::input::common_conditions::input_just_pressed;
|
app::AppExit,
|
||||||
pub(crate) use bevy::prelude::*;
|
asset::{io::Reader, AssetLoader, LoadContext, AsyncReadExt},
|
||||||
pub(crate) use bevy::render::camera::RenderTarget;
|
color::palettes::css::{WHITE, GRAY, RED, DARK_GREEN, BLUE},
|
||||||
pub(crate) use bevy::window::PrimaryWindow;
|
gltf::Gltf,
|
||||||
pub(crate) use bevy::window::WindowCloseRequested;
|
input::common_conditions::input_just_pressed,
|
||||||
pub(crate) use bevy::window::WindowRef;
|
math::Vec3A,
|
||||||
|
prelude::*,
|
||||||
|
render::camera::RenderTarget,
|
||||||
|
window::{WindowRef, WindowCloseRequested, PrimaryWindow},
|
||||||
|
};
|
||||||
|
pub(crate) use nom::{
|
||||||
|
IResult,
|
||||||
|
bytes::complete::{tag, take_until1},
|
||||||
|
character::complete::space1,
|
||||||
|
number::complete::float,
|
||||||
|
sequence::tuple,
|
||||||
|
};
|
||||||
|
pub(crate) use uuid::Uuid;
|
||||||
|
pub(crate) use thiserror::Error;
|
||||||
|
|
||||||
pub(crate) use crate::conditions::*;
|
pub(crate) use crate::{
|
||||||
|
camera::*,
|
||||||
|
conditions::*,
|
||||||
|
save::*,
|
||||||
|
};
|
||||||
|
|||||||
@ -0,0 +1,367 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
/// Menu Plugin; empty struct for Plugin impl
|
||||||
|
pub(crate) struct SavePlugin;
|
||||||
|
|
||||||
|
impl Plugin for SavePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.init_asset::<SaveEntity>()
|
||||||
|
.init_asset_loader::<SaveEntityLoader>()
|
||||||
|
.add_systems(Startup, test_save_entity)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
check_loaded_entity_assets.run_if(on_event::<AssetEvent<SaveEntity>>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Asset, TypePath, Debug, Default)]
|
||||||
|
pub(crate) struct SaveEntity {
|
||||||
|
transform: Option<Transform>,
|
||||||
|
name: Option<Name>,
|
||||||
|
uuid: Option<EntityUuid>,
|
||||||
|
model: Option<(Handle<Gltf>, String)>,
|
||||||
|
// TODO: Option<bool> 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<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SaveEntity {
|
||||||
|
fn parse(
|
||||||
|
text: &str,
|
||||||
|
load_context: &mut LoadContext,
|
||||||
|
) -> Result<SaveEntity, parse::SaveEntityParseError> {
|
||||||
|
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);
|
||||||
|
} else if let Ok(transform) = parse::parse_save_transform(line) {
|
||||||
|
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));
|
||||||
|
} else if let Ok(true) = parse::parse_save_camera(line) {
|
||||||
|
entity.camera = Some(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Debug)]
|
||||||
|
struct EntityUuid(Uuid);
|
||||||
|
|
||||||
|
mod parse {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub(crate) enum SaveEntityParseError {
|
||||||
|
#[error("Failed to parse Uuid: {0}")]
|
||||||
|
Uuid(#[from] uuid::Error),
|
||||||
|
#[error("Failed to parse name")]
|
||||||
|
Name,
|
||||||
|
#[error("Failed to parse Transform")]
|
||||||
|
Transform,
|
||||||
|
#[error("Failed to parse Entity")]
|
||||||
|
Nom(nom::Err<nom::error::Error<Box<str>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Nom error to parse error
|
||||||
|
// https://stackoverflow.com/a/77974858/3096574
|
||||||
|
impl From<nom::Err<nom::error::Error<&str>>> for SaveEntityParseError {
|
||||||
|
fn from(err: nom::Err<nom::error::Error<&str>>) -> Self {
|
||||||
|
SaveEntityParseError::Nom(err.map_input(|input| input.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_word<'a>(w: &'a str) -> impl Fn(&'a str) -> IResult<&'a str, &'a str> {
|
||||||
|
move |i: &'a str| {
|
||||||
|
tag(w)(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_xyz(i: &str) -> IResult<&str, (f32, f32, f32)> {
|
||||||
|
tuple((float, space1, float, space1, float))(i)
|
||||||
|
.map(|(s, (x, _, y, _, z))| (s, (x, y, z)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_wxyz(i: &str) -> IResult<&str, (f32, f32, f32, f32)> {
|
||||||
|
tuple((
|
||||||
|
float,
|
||||||
|
space1,
|
||||||
|
float,
|
||||||
|
space1,
|
||||||
|
float,
|
||||||
|
space1,
|
||||||
|
float,
|
||||||
|
))(i)
|
||||||
|
.map(|(s, (w, _, x, _, y, _, z))| (s, (w, x, y, z)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_string(i: &str) -> IResult<&str, &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();
|
||||||
|
/// 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<Transform, SaveEntityParseError> {
|
||||||
|
let (rem, _) = tag("transform")(line)?;
|
||||||
|
|
||||||
|
let mut transform = Transform::default();
|
||||||
|
|
||||||
|
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();
|
||||||
|
} else if let Ok((rem, (_, _, (x, y, z, w)))) = tuple((parse_word("rotation"), space1, parse_wxyz))(curr.trim_start()) {
|
||||||
|
transform.rotation = Quat::from_xyzw(x, y, z, w);
|
||||||
|
curr = rem.trim_start();
|
||||||
|
} else if let Ok((rem, (_, _, (x, y, z)))) = tuple((parse_word("scale"), space1, parse_xyz))(curr.trim_start()) {
|
||||||
|
transform.scale = Vec3::new(x, y, z);
|
||||||
|
curr = rem.trim_start();
|
||||||
|
} else {
|
||||||
|
return Err(SaveEntityParseError::Transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
/// let expected = Name::new("asdf");
|
||||||
|
///
|
||||||
|
/// assert_eq!(parsed, expected);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub(crate) fn parse_save_name(line: &str) -> Result<Name, SaveEntityParseError> {
|
||||||
|
let (remainder, _) = tag("name")(line)?;
|
||||||
|
let n = remainder.trim().to_string();
|
||||||
|
if n.is_empty() {
|
||||||
|
Err(SaveEntityParseError::Name)
|
||||||
|
} else {
|
||||||
|
let name = Name::new(n);
|
||||||
|
Ok(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
/// let expected = Uuid::parse_str("339dd18f-761a-4997-b515-ef57f4dc2447").unwrap();
|
||||||
|
///
|
||||||
|
/// assert_eq!(parsed, expected);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub(crate) fn parse_save_uuid(line: &str) -> Result<Uuid, SaveEntityParseError> {
|
||||||
|
let (remainder, _) = tag("uuid")(line)?;
|
||||||
|
let uuid = Uuid::try_parse(remainder.trim())?;
|
||||||
|
Ok(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();
|
||||||
|
|
||||||
|
assert_eq!(parsed, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_save_model(line: &str) -> Result<(String, String), SaveEntityParseError> {
|
||||||
|
let (rem, (_, _, gltf_name, _, scene_name)) = tuple((
|
||||||
|
tag("model"),
|
||||||
|
space1,
|
||||||
|
parse_string,
|
||||||
|
space1,
|
||||||
|
parse_string,
|
||||||
|
))(line)?;
|
||||||
|
|
||||||
|
debug_assert_eq!(rem, "");
|
||||||
|
|
||||||
|
Ok((String::from(gltf_name), String::from(scene_name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_save_camera(line: &str) -> Result<bool, SaveEntityParseError> {
|
||||||
|
let (_rem, cam) = tag("camera")(line)?;
|
||||||
|
Ok(!cam.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_camera() {
|
||||||
|
{
|
||||||
|
let line = "camera";
|
||||||
|
let parsed = parse_save_camera(line).unwrap();
|
||||||
|
let expected = true;
|
||||||
|
assert_eq!(parsed, expected);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let line = "notcamera";
|
||||||
|
let parsed = parse_save_camera(line);
|
||||||
|
assert!(parsed.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct SaveEntityLoader;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
enum SaveEntityLoaderError {
|
||||||
|
#[error("Could not load asset: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("Could not parse entity: {0}")]
|
||||||
|
Parse(#[from] parse::SaveEntityParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetLoader for SaveEntityLoader {
|
||||||
|
type Asset = SaveEntity;
|
||||||
|
type Settings = ();
|
||||||
|
type Error = SaveEntityLoaderError;
|
||||||
|
|
||||||
|
async fn load<'a>(
|
||||||
|
&'a self,
|
||||||
|
reader: &'a mut Reader<'_>,
|
||||||
|
_settings: &'a (),
|
||||||
|
load_context: &'a mut LoadContext<'_>,
|
||||||
|
) -> Result<Self::Asset, Self::Error> {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
reader.read_to_end(&mut bytes).await?;
|
||||||
|
|
||||||
|
let s = std::str::from_utf8(bytes.as_slice()).unwrap();
|
||||||
|
let save_entity = SaveEntity::parse(s, load_context)?;
|
||||||
|
Ok(save_entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extensions(&self) -> &[&str] {
|
||||||
|
&["entity"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_save_entity(loader: Res<AssetServer>, mut commands: Commands) {
|
||||||
|
let handle: Handle<SaveEntity> = loader.load("levels/00/entities/van.entity");
|
||||||
|
commands.spawn((
|
||||||
|
SpatialBundle { ..default() },
|
||||||
|
handle,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_loaded_entity_assets(
|
||||||
|
query: Query<(Entity, &Handle<SaveEntity>)>,
|
||||||
|
mut events: EventReader<AssetEvent<SaveEntity>>,
|
||||||
|
save_entities: Res<Assets<SaveEntity>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
gltfs: Res<Assets<Gltf>>,
|
||||||
|
) {
|
||||||
|
events.read().for_each(|event| {
|
||||||
|
if let AssetEvent::LoadedWithDependencies { id } = event {
|
||||||
|
query
|
||||||
|
.iter()
|
||||||
|
.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::<Transform>();
|
||||||
|
}
|
||||||
|
// Apply Name
|
||||||
|
if let Some(name) = &saved.name {
|
||||||
|
// TODO: Only update if different
|
||||||
|
e.insert(name.clone());
|
||||||
|
} else {
|
||||||
|
e.remove::<Name>();
|
||||||
|
}
|
||||||
|
// Apply Uuid
|
||||||
|
if let Some(uuid) = &saved.uuid {
|
||||||
|
// TODO: Only update if different
|
||||||
|
e.insert(uuid.clone());
|
||||||
|
} else {
|
||||||
|
e.remove::<EntityUuid>();
|
||||||
|
}
|
||||||
|
// 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::<Handle<Scene>>();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue