Compare commits

...

9 Commits

Author SHA1 Message Date
Elijah C. Voigt 21e67e04ca Upgrade to bevy 0.14 1 year ago
Elijah C. Voigt 07bbbfa25b Successfully parsing camera component
Now need to get the game to actually use the camera...
1 year ago
Elijah C. Voigt 9a83c06716 clippy fix 1 year ago
Elijah C. Voigt a54255c363 Adding camera component for saved entity 1 year ago
Elijah C. Voigt edb15ffe49 Basic model loading works!
Going to look into camera and light entity loading next
1 year ago
Elijah C. Voigt 84744a9397 Entity asset loading kinda sorta works... 1 year ago
Elijah C. Voigt cfe3a9ce17 Move parsing functions to submodule 1 year ago
Elijah C. Voigt 8027af5303 Very basic entity file parsing
Implements basic parsing for name, uuid, and transform.
Eventually will add Gltf Scene and Parent attribution.
Next need to convert these loaded values to entity components.

Pretty sure it doesn't work, just that it compiles...
1 year ago
Elijah C. Voigt 63fab851ac Editor fly camera 1 year ago

@ -25,10 +25,6 @@ rustflags = [
[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe" # Use LLD Linker
rustflags = [
"-Zshare-generics=n",
"-Zthreads=0", # (Nightly) Use improved multithreading with the recommended amount of threads.
]
[target.wasm32-unknown-unknown]
runner = "wasm-server-runner"

1089
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -4,10 +4,23 @@ version = "0.1.0"
edition = "2021"
[dependencies]
bevy = "0.13"
bevy = { version = "0.14", features = ["file_watcher", "dynamic_linking"] }
wee_alloc = "*"
bevy_mod_picking = "0.18"
bevy_mod_picking = "0.20"
thiserror = "1"
nom = "7"
# Entity Uuid parsing
uuid = "1.7.0"
# Enable a small amount of optimization in debug mode
[profile.dev]
opt-level = 1
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
[profile.dev.package."*"]
opt-level = 3
# Prioritize binary size for wasm
[profile.wasm-release]
inherits = "release"
opt-level = "z"

@ -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::*;
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()
}

@ -1,3 +1,5 @@
use bevy::render::primitives::Aabb;
use crate::prelude::*;
pub(crate) struct EditorPlugin;
@ -12,14 +14,16 @@ impl Plugin for EditorPlugin {
)
.add_systems(
Update,
toggle_editor_window.run_if(state_changed::<EditorState>),
(
toggle_editor_window,
toggle_entity_aabb,
).run_if(state_changed::<EditorState>),
)
.add_systems(
Update,
(
handle_window_close.run_if(on_event::<WindowCloseRequested>()),
plane_gizmos,
// fly_camera,
)
.run_if(in_state(EditorState::Open)),
);
@ -28,7 +32,7 @@ impl Plugin for EditorPlugin {
/// Tracking the open/closed state of the editor
#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default, Component)]
enum EditorState {
pub(crate) enum EditorState {
/// The editor is closed => the editor window is disabled
#[default]
Closed,
@ -78,6 +82,7 @@ fn init_editor(mut commands: Commands) {
let editor_camera = commands
.spawn((
EditorTag,
FlyCamera,
Camera3dBundle {
camera: Camera {
target: RenderTarget::Window(WindowRef::Entity(editor_window)),
@ -102,7 +107,7 @@ fn init_editor(mut commands: Commands) {
TextBundle::from_section(
"Welcome to the editor",
TextStyle {
color: Color::WHITE,
color: WHITE.into(),
..default()
},
),
@ -162,25 +167,61 @@ fn plane_gizmos(mut gizmos: Gizmos) {
let r = 20.0;
let start = -r as i8;
let end = r as i8;
(start..=end).into_iter().for_each(|n| {
(start..=end).for_each(|n| {
{
let offset = Vec3::Z * (n as f32);
gizmos.line(Vec3::NEG_X * r + offset, Vec3::X * r + offset, Color::GRAY);
gizmos.line(Vec3::NEG_X * r + offset, Vec3::X * r + offset, GRAY);
}
{
let offset = Vec3::X * (n as f32);
gizmos.line(Vec3::NEG_Z * r + offset, Vec3::Z * r + offset, Color::GRAY);
gizmos.line(Vec3::NEG_Z * r + offset, Vec3::Z * r + offset, GRAY);
}
});
// World origin arrows
{
gizmos.arrow(Vec3::ZERO, Vec3::X, Color::RED);
gizmos.arrow(Vec3::ZERO, Vec3::Y, Color::DARK_GREEN);
gizmos.arrow(Vec3::ZERO, Vec3::Z, Color::BLUE);
gizmos.arrow(Vec3::ZERO, Vec3::X, RED);
gizmos.arrow(Vec3::ZERO, Vec3::Y, DARK_GREEN);
gizmos.arrow(Vec3::ZERO, Vec3::Z, BLUE);
}
}
fn toggle_entity_aabb(
state: Res<State<EditorState>>,
query: Query<Entity, With<Handle<SaveEntity>>>,
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::<ShowAabbGizmo>();
}
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::<ShowAabbGizmo>();
}
if aabbs.contains(child) {
commands.entity(child).insert(Aabb { center: Vec3A::ZERO, half_extents: Vec3A::ONE });
} else {
child_has_bounding_box = true;
}
}
fn _fly_camera(mut _camera: Query<&mut Transform, (With<Camera>, With<EditorTag>)>) {
todo!()
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::<Aabb>();
}
})
}

@ -1,3 +1,5 @@
/// Camera controller
pub(crate) mod camera;
/// ECS scheduling `run_if` conditions
pub(crate) mod conditions;
/// Editor: debugging and run-time modifications to the game
@ -6,6 +8,8 @@ pub(crate) mod editor;
pub(crate) mod menu;
/// Helper module containing common imports across the project
pub(crate) mod prelude;
/// Level saving/loading logic
pub(crate) mod save;
/// Window handling
pub(crate) mod window;
@ -19,8 +23,10 @@ fn main() {
exit_condition: bevy::window::ExitCondition::OnPrimaryClosed,
..default()
}))
.add_plugins(menu::MenuPlugin)
.add_plugins(camera::CameraPlugin)
.add_plugins(editor::EditorPlugin)
.add_plugins(menu::MenuPlugin)
.add_plugins(save::SavePlugin)
.add_plugins(window::WindowPlugin)
.run();
}

@ -9,7 +9,7 @@ impl Plugin for MenuPlugin {
}
fn init_menu(mut commands: Commands) {
commands.spawn(Camera3dBundle { ..default() });
// commands.spawn(Camera3dBundle { ..default() });
commands
.spawn(NodeBundle {
style: Style {

@ -1,9 +1,26 @@
pub(crate) use bevy::app::AppExit;
pub(crate) use bevy::input::common_conditions::input_just_pressed;
pub(crate) use bevy::prelude::*;
pub(crate) use bevy::render::camera::RenderTarget;
pub(crate) use bevy::window::PrimaryWindow;
pub(crate) use bevy::window::WindowCloseRequested;
pub(crate) use bevy::window::WindowRef;
pub(crate) use bevy::{
app::AppExit,
asset::{io::Reader, AssetLoader, LoadContext, AsyncReadExt},
color::palettes::css::{WHITE, 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 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>>();
}
});
}
})
}

@ -36,6 +36,6 @@ fn handle_window_close(
.filter_map(|WindowCloseRequested { window }| primary.get(*window).ok())
// If this was the primary window, send an AppExit
.for_each(|_| {
exit.send(AppExit);
exit.send(AppExit::Success);
});
}

Loading…
Cancel
Save