kitgit

tirbofish/dropbear · commit

bdfa398b7eeb8e00847e36a5805570b94899187a

changed to a new scene saving format instead of the old one because the use of components was too limiting and not much possibilities. also am using a new type called EntityTransform combines the local and world into a "propagated Transform" type. eh... its still broken also

Unverified

tk <4tkbytes@pm.me> · 2025-11-18 13:45

view full diff

diff --git a/Cargo.toml b/Cargo.toml
index da6298c..91d35fa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ package.repository = "https://github.com/4tkbytes/dropbear"
 package.readme = "README.md"
 
 resolver = "3"
-members = [ "dropbear-engine", "dropbear-shader", "dropbear_future-queue", "eucalyptus-core", "eucalyptus-editor", "magna-carta", "redback-runtime"]
+members = [ "dropbear-engine", "dropbear-macro", "dropbear-shader", "dropbear-traits", "dropbear_future-queue", "eucalyptus-core", "eucalyptus-editor", "magna-carta", "redback-runtime"]
 
 [workspace.dependencies]
 anyhow = { version = "1.0", features = ["backtrace"] }
@@ -64,6 +64,9 @@ dashmap = "6.1"
 open = "5.3"
 egui_plot = "0.34"
 memory-stats = "1.2"
+typetag = "0.2"
+syn = { version = "2.0", features = ["full"] }
+quote = "1.0"
 
 [workspace.dependencies.image]
 version = "0.25"
@@ -79,4 +82,4 @@ lto = false
 
 # makes the debug builds so much more faster
 [profile.dev.package."*"]
-opt-level = 3
\ No newline at end of file
+opt-level = 3
diff --git a/dropbear-engine/Cargo.toml b/dropbear-engine/Cargo.toml
index a608343..9d4992d 100644
--- a/dropbear-engine/Cargo.toml
+++ b/dropbear-engine/Cargo.toml
@@ -11,6 +11,8 @@ readme.workspace = true
 [dependencies]
 dropbear_future-queue = { path = "../dropbear_future-queue" }
 dropbear-shader = { path = "../dropbear-shader" }
+dropbear-traits = { path = "../dropbear-traits" }
+dropbear-macro = { path = "../dropbear-macro" }
 
 anyhow.workspace = true
 app_dirs2.workspace = true
@@ -33,7 +35,6 @@ wgpu.workspace = true
 winit.workspace = true
 hecs.workspace = true
 parking_lot.workspace = true
-lazy_static.workspace = true
 gltf.workspace = true
 rayon.workspace = true
 backtrace.workspace = true
@@ -41,6 +42,7 @@ os_info.workspace = true
 rustc_version_runtime.workspace = true
 ron.workspace = true
 dashmap.workspace = true
+typetag.workspace = true
 
 [target.'cfg(not(target_os = "android"))'.dependencies]
 rfd.workspace = true
diff --git a/dropbear-engine/src/entity.rs b/dropbear-engine/src/entity.rs
index a8f3911..0d3e1de 100644
--- a/dropbear-engine/src/entity.rs
+++ b/dropbear-engine/src/entity.rs
@@ -1,3 +1,4 @@
+use dropbear_traits::SerializableComponent;
 use glam::{DMat4, DQuat, DVec3, Mat4};
 use parking_lot::Mutex;
 use serde::{Deserialize, Serialize};
@@ -14,6 +15,44 @@ use crate::{
     utils::ResourceReference,
 };
 use anyhow::anyhow;
+use dropbear_macro::SerializableComponent;
+
+/// A type of transform that is attached to all entities. It contains the local and world transforms.
+#[derive(Default, Debug, Deserialize, Serialize, Copy, PartialEq, Clone, SerializableComponent)]
+pub struct EntityTransform {
+    local: Transform,
+    world: Transform,
+}
+
+impl EntityTransform {
+    pub fn new(local: Transform, world: Transform) -> Self {
+        Self { local, world }
+    }
+
+    /// Gets a reference to the local transform
+    pub fn local(&self) -> &Transform {
+        &self.local
+    }
+
+    /// Gets a reference to the world transform
+    pub fn world(&self) -> &Transform {
+        &self.world
+    }
+
+    /// Combines both transforms into one, propagating the local transform
+    /// to the world transform and returning a uniform [Transform]
+    pub fn sync(&self) -> Transform {
+        let scaled_pos = self.local.position * self.world.scale;
+        let rotated_pos = self.world.rotation * scaled_pos;
+        let position = self.world.position + rotated_pos;
+
+        Transform {
+            position,
+            rotation: self.world.rotation * self.local.rotation,
+            scale: self.world.scale * self.local.scale,
+        }
+    }
+}
 
 /// A type that represents a position, rotation and scale of an entity
 ///
@@ -32,9 +71,9 @@ pub struct Transform {
 impl Default for Transform {
     fn default() -> Self {
         Self {
-            position: DVec3::new(0.0, 0.0, 0.0),
+            position: DVec3::ZERO,
             rotation: DQuat::IDENTITY,
-            scale: DVec3::new(1.0, 1.0, 1.0),
+            scale: DVec3::ONE,
         }
     }
 }
diff --git a/dropbear-engine/src/lighting.rs b/dropbear-engine/src/lighting.rs
index 0f33225..bda5959 100644
--- a/dropbear-engine/src/lighting.rs
+++ b/dropbear-engine/src/lighting.rs
@@ -1,11 +1,13 @@
+use dropbear_traits::SerializableComponent;
 use glam::{DMat4, DQuat, DVec3};
 use std::fmt::{Display, Formatter};
 use std::sync::Arc;
+use serde::{Deserialize, Serialize};
 use wgpu::{
     BindGroup, BindGroupLayout, Buffer, BufferAddress, CompareFunction, DepthBiasState,
     RenderPipeline, StencilState, VertexBufferLayout, util::DeviceExt,
 };
-
+use dropbear_macro::SerializableComponent;
 use crate::attenuation::{Attenuation, RANGE_50};
 use crate::graphics::SharedGraphicsContext;
 use crate::shader::Shader;
@@ -118,7 +120,7 @@ impl From<LightType> for u32 {
     }
 }
 
-#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, SerializableComponent)]
 pub struct LightComponent {
     pub position: DVec3,          // point, spot
     pub direction: DVec3,         // directional, spot
diff --git a/dropbear-engine/src/utils.rs b/dropbear-engine/src/utils.rs
index aa91fa7..cec6908 100644
--- a/dropbear-engine/src/utils.rs
+++ b/dropbear-engine/src/utils.rs
@@ -100,6 +100,9 @@ pub enum ResourceReferenceType {
     /// In specifics, the plane (as from [`crate::procedural::plane::PlaneBuilder`]) is a model that
     /// has meshes and a textured material, but is created "in house" (during runtime instead of loaded).
     Plane,
+    
+    /// A cube.
+    Cube,
 }
 
 impl Default for ResourceReferenceType {
diff --git a/dropbear-macro/Cargo.toml b/dropbear-macro/Cargo.toml
new file mode 100644
index 0000000..a4c06a3
--- /dev/null
+++ b/dropbear-macro/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "dropbear-macro"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+readme = "README.md"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn.workspace = true
+quote.workspace = true
diff --git a/dropbear-macro/README.md b/dropbear-macro/README.md
new file mode 100644
index 0000000..612c70f
--- /dev/null
+++ b/dropbear-macro/README.md
@@ -0,0 +1,3 @@
+# dropbear-macro
+
+Contains the dropbear macros that would be used by other crates, such as derive macros. 
\ No newline at end of file
diff --git a/dropbear-macro/src/lib.rs b/dropbear-macro/src/lib.rs
new file mode 100644
index 0000000..e89f4bf
--- /dev/null
+++ b/dropbear-macro/src/lib.rs
@@ -0,0 +1,48 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, DeriveInput};
+
+/// A `derive` macro that converts a struct to a usable [SerializableComponent].
+///
+/// You have to implement `serde::Serialize`, `serde::Deserialize` and `Clone` for the
+/// struct to be usable (it will throw errors anyway).
+///
+/// # Usage
+/// ```
+/// use dropbear_macro::SerializableComponent;
+///
+/// #[derive(Serialize, Deserialize, Clone, SerializableComponent)] // required to be implemented
+/// struct MyComponent {
+///     value1: String,
+///     value2: i32,
+/// }
+/// ```
+#[proc_macro_derive(SerializableComponent)]
+pub fn derive_component(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    let name = &input.ident;
+    let name_str = name.to_string();
+
+    let expanded = quote! {
+        #[typetag::serde]
+        impl SerializableComponent for #name {
+            fn as_any(&self) -> &dyn std::any::Any {
+                self
+            }
+
+            fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
+                self
+            }
+
+            fn clone_boxed(&self) -> Box<dyn SerializableComponent> {
+                Box::new(self.clone())
+            }
+
+            fn type_name(&self) -> &'static str {
+                #name_str
+            }
+        }
+    };
+
+    TokenStream::from(expanded)
+}
\ No newline at end of file
diff --git a/dropbear-traits/Cargo.toml b/dropbear-traits/Cargo.toml
new file mode 100644
index 0000000..f869fb8
--- /dev/null
+++ b/dropbear-traits/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "dropbear-traits"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+readme = "README.md"
+
+[dependencies]
+typetag.workspace = true
diff --git a/dropbear-traits/README.md b/dropbear-traits/README.md
new file mode 100644
index 0000000..018c5f7
--- /dev/null
+++ b/dropbear-traits/README.md
@@ -0,0 +1,3 @@
+# dropbear-traits
+
+Contains all the traits that other crates would use. 
\ No newline at end of file
diff --git a/dropbear-traits/src/lib.rs b/dropbear-traits/src/lib.rs
new file mode 100644
index 0000000..02df2c5
--- /dev/null
+++ b/dropbear-traits/src/lib.rs
@@ -0,0 +1,21 @@
+use std::any::Any;
+use std::fmt::Debug;
+
+/// A type of component that gets serialized and deserialized into a scene config file.
+#[typetag::serde(tag = "type")]
+pub trait SerializableComponent: Send + Sync + Debug {
+    /// Converts a [SerializableComponent] to an [Any] type.
+    fn as_any(&self) -> &dyn Any;
+    /// Converts a [SerializableComponent] to a mutable [Any] type
+    fn as_any_mut(&mut self) -> &mut dyn Any;
+    /// Fetches the type name of that component
+    fn type_name(&self) -> &'static str;
+    /// Allows you to clone the dynamic object. 
+    fn clone_boxed(&self) -> Box<dyn SerializableComponent>;
+}
+
+impl Clone for Box<dyn SerializableComponent> {
+    fn clone(&self) -> Self {
+        self.clone_boxed()
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/Cargo.toml b/eucalyptus-core/Cargo.toml
index d6945c8..429ede5 100644
--- a/eucalyptus-core/Cargo.toml
+++ b/eucalyptus-core/Cargo.toml
@@ -10,6 +10,9 @@ readme = "README.md"
 crate-type = ["rlib", "dylib"]
 
 [dependencies]
+dropbear-traits = { path = "../dropbear-traits" }
+dropbear-macro = { path = "../dropbear-macro" }
+
 anyhow.workspace = true
 bincode.workspace = true
 chrono.workspace = true
@@ -34,6 +37,7 @@ crossbeam-channel.workspace = true
 app_dirs2.workspace = true
 log-once.workspace = true
 rfd = { workspace = true, optional = true }
+typetag.workspace = true
 
 [features]
 # editor only stuff
diff --git a/eucalyptus-core/src/camera.rs b/eucalyptus-core/src/camera.rs
index c05419f..aa72280 100644
--- a/eucalyptus-core/src/camera.rs
+++ b/eucalyptus-core/src/camera.rs
@@ -1,8 +1,11 @@
-use dropbear_engine::camera::{Camera, CameraSettings};
+use crate::traits::SerializableComponent;
+use dropbear_engine::camera::{Camera, CameraBuilder, CameraSettings};
 use glam::DVec3;
 use serde::{Deserialize, Serialize};
+use dropbear_macro::SerializableComponent;
+use crate::states::CameraConfig;
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize, Deserialize, SerializableComponent)]
 pub struct CameraComponent {
     pub settings: CameraSettings,
     pub camera_type: CameraType,
@@ -27,9 +30,35 @@ impl CameraComponent {
     pub fn update(&mut self, camera: &mut Camera) {
         camera.settings = self.settings;
     }
+}
+
+impl From<CameraConfig> for CameraBuilder {
+    fn from(value: CameraConfig) -> Self {
+        Self {
+            eye: value.eye.into(),
+            target: value.target.into(),
+            up: value.up.into(),
+            aspect: value.aspect,
+            znear: value.near as f64,
+            zfar: value.far as f64,
+            settings: CameraSettings {
+                speed: value.speed as f64,
+                sensitivity: value.sensitivity as f64,
+                fov_y: value.fov as f64,
+            },
+        }
+    }
+}
 
-    // setting camera offset is just adding the CameraFollowTarget struct
-    // to the ecs system
+impl From<CameraConfig> for CameraComponent {
+    fn from(value: CameraConfig) -> Self {
+        let settings = CameraSettings::new(value.speed as f64, value.sensitivity as f64, value.fov as f64);
+        Self {
+            settings,
+            camera_type: value.camera_type,
+            starting_camera: value.starting_camera,
+        }
+    }
 }
 
 pub struct PlayerCamera;
diff --git a/eucalyptus-core/src/component.rs b/eucalyptus-core/src/component.rs
new file mode 100644
index 0000000..d896a6f
--- /dev/null
+++ b/eucalyptus-core/src/component.rs
@@ -0,0 +1,93 @@
+/// Get a component and execute a closure if it exists
+///
+/// # Usage
+/// ```
+/// use eucalyptus_core::with_component;
+///
+/// with_component!(scene_entity, Transform, /*mut*/ |transform| {
+///     transform.position.x += 1.0;
+/// });
+#[macro_export]
+macro_rules! with_component {
+    // immutable
+    ($entity:expr, $comp_type:ty, $closure:expr) => {
+        if let Some(comp) = $entity.get_component::<$comp_type>() {
+            $closure(comp)
+        }
+    };
+
+    // mutable
+    ($entity:expr, $comp_type:ty, mut $closure:expr) => {
+        if let Some(comp) = $entity.get_component_mut::<$comp_type>() {
+            $closure(comp)
+        }
+    };
+}
+
+/// Get a component or return early from the function
+///
+/// # Usage
+/// ```
+/// use dropbear_engine::entity::MeshRenderer;
+/// use eucalyptus_core::get_component;
+///
+/// fn process_entity(entity: &SceneEntity) {
+///     let renderer = get_component!(entity, MeshRenderer);
+///     println!("Processing mesh: {:?}", renderer.handle);
+/// }
+#[macro_export]
+macro_rules! get_component {
+    ($entity:expr, $comp_type:ty) => {
+        match $entity.get_component::<$comp_type>() {
+            Some(comp) => comp,
+            None => return,
+        }
+    };
+
+    ($entity:expr, $comp_type:ty, mut) => {
+        match $entity.get_component_mut::<$comp_type>() {
+            Some(comp) => comp,
+            None => return,
+        }
+    };
+}
+
+/// Try to get a component, or execute else block
+///
+/// # Usage
+/// ```
+/// if_component!(scene_entity, Transform, |/*mut*/ transform| {
+///     transform.position = Vec3::new(1.0, 2.0, 3.0);
+/// } else {
+///     println!("No transform found");
+/// });
+#[macro_export]
+macro_rules! if_component {
+    ($entity:expr, $comp_type:ty, |$comp:ident| $then:block else $else:block) => {
+        if let Some($comp) = $entity.get_component::<$comp_type>() {
+            $then
+        } else {
+            $else
+        }
+    };
+
+    ($entity:expr, $comp_type:ty, |$comp:ident| $then:block) => {
+        if let Some($comp) = $entity.get_component::<$comp_type>() {
+            $then
+        }
+    };
+
+    ($entity:expr, $comp_type:ty, |mut $comp:ident| $then:block else $else:block) => {
+        if let Some(mut $comp) = $entity.get_component_mut::<$comp_type>() {
+            $then
+        } else {
+            $else
+        }
+    };
+
+    ($entity:expr, $comp_type:ty, |mut $comp:ident| $then:block) => {
+        if let Some(mut $comp) = $entity.get_component_mut::<$comp_type>() {
+            $then
+        }
+    };
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/hierarchy.rs b/eucalyptus-core/src/hierarchy.rs
index 3788d20..e230c80 100644
--- a/eucalyptus-core/src/hierarchy.rs
+++ b/eucalyptus-core/src/hierarchy.rs
@@ -1,3 +1,8 @@
+use std::collections::HashMap;
+use serde::{Deserialize, Serialize};
+use dropbear_engine::entity::{EntityTransform, Transform};
+use crate::states::Label;
+
 /// A component that is added to all entities to show all child entities
 #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
 pub struct Parent(Vec<hecs::Entity>);
@@ -33,3 +38,132 @@ impl Parent {
         self.0.is_empty()
     }
 }
+
+/// A component that points to the parent entity of an entity.
+#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
+pub struct Child(hecs::Entity);
+
+impl Child {
+    /// Creates a new child component with the provided parent entity.
+    pub fn new(parent: hecs::Entity) -> Self {
+        Self(parent)
+    }
+
+    /// Returns the parent entity of this child component.
+    pub fn parent(&self) -> hecs::Entity {
+        self.0
+    }
+}
+
+/// An extension trait for [EntityTransform] that allows for propagation of entities into a target transform.
+pub trait EntityTransformExt {
+    /// Walks up the [`hecs::World`] and calculates the final [Transform] for the entity based off its parents.
+    fn propagate(&self, world: &hecs::World, target_entity: hecs::Entity) -> Transform;
+}
+
+impl EntityTransformExt for EntityTransform {
+    fn propagate(&self, world: &hecs::World, target_entity: hecs::Entity) -> Transform {
+        let mut result = self.local().clone();
+
+        let mut current = target_entity;
+        while let Ok(child) = world.get::<&Child>(current) {
+            let parent_entity = child.parent();
+
+            if let Ok(parent_transform) = world.get::<&EntityTransform>(parent_entity) {
+                let parent_world = parent_transform.world();
+
+                result = Transform {
+                    position: parent_world.position + parent_world.rotation * (result.position * parent_world.scale),
+                    rotation: parent_world.rotation * result.rotation,
+                    scale: parent_world.scale * result.scale,
+                };
+            }
+
+            current = parent_entity;
+        }
+
+        result
+    }
+}
+
+#[derive(Default, Serialize, Deserialize, Clone, Debug)]
+pub struct SceneHierarchy {
+    /// Maps entity labels to their parent label
+    parent_map: HashMap<Label, Label>,
+    /// Maps entity labels to their children labels
+    children_map: HashMap<Label, Vec<Label>>,
+}
+
+impl SceneHierarchy {
+    pub fn new() -> Self {
+        Self {
+            parent_map: HashMap::new(),
+            children_map: HashMap::new(),
+        }
+    }
+
+    /// Set the parent of an entity
+    pub fn set_parent(&mut self, child: Label, parent: Label) {
+        if let Some(old_parent) = self.parent_map.get(&child) {
+            if let Some(children) = self.children_map.get_mut(old_parent) {
+                children.retain(|c| c != &child);
+            }
+        }
+
+        self.parent_map.insert(child.clone(), parent.clone());
+
+        self.children_map
+            .entry(parent)
+            .or_insert_with(Vec::new)
+            .push(child);
+    }
+
+    /// Remove parent relationship
+    pub fn remove_parent(&mut self, child: &Label) {
+        if let Some(parent) = self.parent_map.remove(child) {
+            if let Some(children) = self.children_map.get_mut(&parent) {
+                children.retain(|c| c != child);
+            }
+        }
+    }
+
+    /// Get the parent of an entity
+    pub fn get_parent(&self, child: &Label) -> Option<&Label> {
+        self.parent_map.get(child)
+    }
+
+    /// Get the children of an entity
+    pub fn get_children(&self, parent: &Label) -> &[Label] {
+        self.children_map
+            .get(parent)
+            .map(|v| v.as_slice())
+            .unwrap_or(&[])
+    }
+
+    /// Get all ancestors of an entity (parent, grandparent, etc.)
+    pub fn get_ancestors(&self, entity: &Label) -> Vec<Label> {
+        let mut ancestors = Vec::new();
+        let mut current = entity.clone();
+
+        while let Some(parent) = self.parent_map.get(&current) {
+            ancestors.push(parent.clone());
+            current = parent.clone();
+        }
+
+        ancestors
+    }
+
+    /// Check if an entity is a descendant of another
+    pub fn is_descendant_of(&self, entity: &Label, potential_ancestor: &Label) -> bool {
+        let mut current = entity.clone();
+
+        while let Some(parent) = self.parent_map.get(&current) {
+            if parent == potential_ancestor {
+                return true;
+            }
+            current = parent.clone();
+        }
+
+        false
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/lib.rs b/eucalyptus-core/src/lib.rs
index 0b4c2ae..b0ef223 100644
--- a/eucalyptus-core/src/lib.rs
+++ b/eucalyptus-core/src/lib.rs
@@ -10,6 +10,11 @@ pub mod spawn;
 pub mod states;
 pub mod utils;
 pub mod window;
+pub mod scene;
+mod component;
+
+pub use dropbear_traits as traits;
+pub use dropbear_macro as macros;
 
 pub use egui;
 
diff --git a/eucalyptus-core/src/runtime.rs b/eucalyptus-core/src/runtime.rs
index 74da07a..9343baf 100644
--- a/eucalyptus-core/src/runtime.rs
+++ b/eucalyptus-core/src/runtime.rs
@@ -1,4 +1,4 @@
-use crate::states::SceneConfig;
+use crate::scene::SceneConfig;
 use std::collections::HashMap;
 // #[derive(bincode::Decode, bincode::Encode, serde::Serialize, serde::Deserialize, Debug)]
 // pub struct RuntimeData {
diff --git a/eucalyptus-core/src/scene.rs b/eucalyptus-core/src/scene.rs
new file mode 100644
index 0000000..71a7b81
--- /dev/null
+++ b/eucalyptus-core/src/scene.rs
@@ -0,0 +1,497 @@
+use std::collections::HashMap;
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+use serde::{Deserialize, Serialize};
+use ron::ser::PrettyConfig;
+use dropbear_engine::graphics::SharedGraphicsContext;
+use tokio::sync::mpsc::UnboundedSender;
+use dropbear_engine::camera::{Camera, CameraBuilder};
+use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
+use glam::{DQuat, DVec3};
+use dropbear_engine::lighting::{Light, LightComponent};
+use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
+use dropbear_engine::asset::ASSET_REGISTRY;
+use dropbear_engine::model::Model;
+use dropbear_engine::utils::ResourceReferenceType;
+use crate::camera::{CameraComponent, CameraType};
+use crate::hierarchy::{Parent, SceneHierarchy};
+use crate::states::{CameraConfig, Label, LightConfig, ModelProperties, ScriptComponent, SerializedMeshRenderer, WorldLoadingStatus, PROJECT};
+use crate::utils::ResolveReference;
+
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
+pub struct SceneEntity {
+    #[serde(default)]
+    pub label: Label,
+    #[serde(default)]
+    pub components: Vec<Box<dyn dropbear_traits::SerializableComponent>>,
+
+    #[serde(skip)]
+    pub entity_id: Option<hecs::Entity>,
+}
+
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
+pub struct SceneSettings { /* *crickets* */ }
+
+impl SceneSettings {
+    pub fn new() -> Self {
+        Self {}
+    }
+}
+
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
+pub struct SceneConfig {
+    #[serde(default)]
+    pub scene_name: String,
+
+    #[serde(default)]
+    pub entities: Vec<SceneEntity>,
+
+    #[serde(default)]
+    pub hierarchy_map: SceneHierarchy,
+
+    #[serde(default)]
+    pub settings: SceneSettings,
+
+    #[serde(skip)]
+    pub path: PathBuf,
+}
+
+impl SceneConfig {
+    /// Creates a new instance of the scene config
+    pub fn new(scene_name: String, path: impl AsRef<Path>) -> Self {
+        Self {
+            scene_name,
+            path: path.as_ref().to_path_buf(),
+            entities: Vec::new(),
+            hierarchy_map: SceneHierarchy::new(),
+            settings: SceneSettings::new(),
+        }
+    }
+
+    /// Helper function to load a component and add it to the entity builder
+    async fn load_component(
+        component: Box<dyn dropbear_traits::SerializableComponent>,
+        builder: &mut hecs::EntityBuilder,
+        graphics: Arc<SharedGraphicsContext>,
+        label: &str,
+    ) -> anyhow::Result<()> {
+        if let Some(transform) = component.as_any().downcast_ref::<EntityTransform>() {
+            builder.add(*transform);
+        } else if let Some(renderer) = component.as_any().downcast_ref::<SerializedMeshRenderer>() {
+            let renderer = renderer.clone();
+            let mut model = match &renderer.handle.ref_type {
+                ResourceReferenceType::None => {
+                    log::error!("Resource reference type is None for entity '{}', not supported, skipping", label);
+                    return Ok(());
+                }
+                ResourceReferenceType::Plane => {
+                    log::error!("Resource reference type is Plane for entity '{}', not supported (being remade), skipping", label);
+                    return Ok(());
+                }
+                ResourceReferenceType::File(reference) => {
+                    let path = &renderer.handle.resolve()?;
+
+                    log::debug!(
+                        "Path for entity {} is {} from reference {}",
+                        label,
+                        path.display(),
+                        reference
+                    );
+
+                    MeshRenderer::from_path(graphics.clone(), &path, Some(label)).await?
+                }
+                ResourceReferenceType::Bytes(bytes) => {
+                    log::info!("Loading entity from bytes [Len: {}]", bytes.len());
+
+                    let model = Model::load_from_memory(
+                        graphics.clone(),
+                        bytes.clone(),
+                        Some(label),
+                    ).await?;
+                    MeshRenderer::from_handle(model)
+                }
+                ResourceReferenceType::Cube => {
+                    log::info!("Loading entity from cube");
+
+                    let model = Model::load_from_memory(
+                        graphics.clone(),
+                        include_bytes!("../../resources/models/cube.glb"),
+                        Some(label),
+                    ).await?;
+                    MeshRenderer::from_handle(model)
+                }
+            };
+
+            if !renderer.material_override.is_empty() {
+                for override_entry in &renderer.material_override {
+                    if ASSET_REGISTRY
+                        .model_handle_from_reference(&override_entry.source_model)
+                        .is_none()
+                    {
+                        if matches!(
+                            override_entry.source_model.ref_type,
+                            ResourceReferenceType::File(_)
+                        ) {
+                            let source_path = override_entry.source_model.resolve()?;
+                            let label_hint = override_entry.source_model.as_uri();
+                            Model::load(graphics.clone(), &source_path, label_hint).await?;
+                        } else {
+                            log::warn!(
+                                "Material override for '{}' references unsupported resource {:?}",
+                                label,
+                                override_entry.source_model
+                            );
+                            continue;
+                        }
+                    }
+
+                    if let Err(err) = model.apply_material_override(
+                        &override_entry.target_material,
+                        override_entry.source_model.clone(),
+                        &override_entry.source_material,
+                    ) {
+                        log::warn!(
+                            "Failed to apply material override '{}' on '{}': {}",
+                            override_entry.target_material,
+                            label,
+                            err
+                        );
+                    }
+                }
+            }
+            
+            builder.add(model);
+        } else if let Some(props) = component.as_any().downcast_ref::<ModelProperties>() {
+            builder.add(props.clone());
+        } else if let Some(camera_comp) = component.as_any().downcast_ref::<CameraConfig>() {
+            let cam_builder = CameraBuilder::from(camera_comp.clone());
+            let comp = CameraComponent::from(camera_comp.clone());
+            let camera = Camera::new(graphics.clone(), cam_builder, Some(label));
+            builder.add_bundle((camera, comp));
+        } else if let Some(light_conf) = component.as_any().downcast_ref::<LightConfig>() {
+            let light = Light::new(
+                graphics.clone(),
+                light_conf.light_component.clone(),
+                light_conf.transform,
+                Some(label)
+            ).await;
+            builder.add_bundle((light_conf.light_component.clone(), light));
+        } else if let Some(script) = component.as_any().downcast_ref::<ScriptComponent>() {
+            builder.add(script.clone());
+        } else if component.as_any().downcast_ref::<Parent>().is_some() {
+            log::debug!("Skipping Parent component for '{}' - will be rebuilt from hierarchy_map", label);
+        } else {
+            log::warn!(
+                "Unknown component type '{}' for entity '{}' - skipping",
+                component.type_name(),
+                label
+            );
+        }
+        
+        Ok(())
+    }
+
+    /// Write the scene config to a .eucs file
+    pub fn write_to(&self, project_path: impl AsRef<Path>) -> anyhow::Result<()> {
+        let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default())
+            .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?;
+
+        let scenes_dir = project_path.as_ref().join("scenes");
+        fs::create_dir_all(&scenes_dir)?;
+
+        let config_path = scenes_dir.join(format!("{}.eucs", self.scene_name));
+        fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
+        Ok(())
+    }
+
+    /// Read a scene config from a .eucs file
+    pub fn read_from(scene_path: impl AsRef<Path>) -> anyhow::Result<Self> {
+        let ron_str = fs::read_to_string(scene_path.as_ref())?;
+        let mut config: SceneConfig = ron::de::from_str(&ron_str)
+            .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?;
+
+        config.path = scene_path.as_ref().to_path_buf();
+        Ok(config)
+    }
+
+    pub async fn load_into_world(
+        &self,
+        world: &mut hecs::World,
+        graphics: Arc<SharedGraphicsContext>,
+        progress_sender: Option<UnboundedSender<WorldLoadingStatus>>,
+    ) -> anyhow::Result<hecs::Entity> {
+        if let Some(ref s) = progress_sender {
+            let _ = s.send(WorldLoadingStatus::Idle);
+        }
+
+        log::info!(
+            "Loading scene [{}], clearing world with {} entities",
+            self.scene_name,
+            world.len()
+        );
+        world.clear();
+
+        #[allow(unused_variables)]
+        let project_config = if cfg!(feature = "editor") {
+            let cfg = PROJECT.read();
+            cfg.project_path.clone()
+        } else {
+            log::debug!("Not using the editor feature, returning empty pathbuffer");
+            PathBuf::new()
+        };
+
+        log::info!("World cleared, now has {} entities", world.len());
+
+        let entity_configs: Vec<(usize, SceneEntity)> = {
+            let cloned = self.entities.clone();
+            cloned
+                .into_par_iter()
+                .enumerate()
+                .map(|(i, e)| (i, e))
+                .collect()
+        };
+
+        let mut label_to_entity: HashMap<Label, hecs::Entity> = HashMap::new();
+
+        for (index, entity_config) in entity_configs {
+            let SceneEntity {
+                label, components, entity_id: _,
+            } = entity_config;
+
+            let label_for_map = label.clone();
+            let label_for_logs = label_for_map.to_string();
+
+            log::debug!("Loading entity: {}", label_for_logs);
+
+            let total = self.entities.len();
+
+            if let Some(ref s) = progress_sender {
+                let _ = s.send(WorldLoadingStatus::LoadingEntity {
+                    index,
+                    name: label_for_logs.clone(),
+                    total,
+                });
+            }
+
+            let mut builder = hecs::EntityBuilder::new();
+
+            builder.add(label_for_map.clone());
+            
+            let mut has_entity_transform = false;
+            
+            for component in components {
+                if component.as_any().downcast_ref::<EntityTransform>().is_some() {
+                    has_entity_transform = true;
+                }
+                
+                Self::load_component(component, &mut builder, graphics.clone(), &label_for_logs).await?;
+            }
+
+            let entity = world.spawn(builder.build());
+            
+            if has_entity_transform {
+                if let Ok(mut query) = world.query_one::<(&EntityTransform, Option<&mut MeshRenderer>, Option<&mut Light>, Option<&mut LightComponent>)>(entity) {
+                    if let Some((entity_transform, renderer_opt, light_opt, light_comp_opt)) = query.get() {
+                        let transform = entity_transform.sync();
+                        
+                        if let Some(renderer) = renderer_opt {
+                            renderer.update(&transform);
+                            log::debug!("Updated renderer transform for '{}'", label_for_logs);
+                        }
+                        
+                        if let (Some(light), Some(light_comp)) = (light_opt, light_comp_opt) {
+                            light.update(light_comp, &transform);
+                            log::debug!("Updated light transform for '{}'", label_for_logs);
+                        }
+                    }
+                }
+            }
+
+            if let Some(previous) = label_to_entity.insert(label_for_map.clone(), entity) {
+                log::warn!(
+                    "Duplicate entity label '{}' detected; previous entity {:?} will be overwritten in hierarchy mapping",
+                    label_for_logs,
+                    previous
+                );
+            }
+
+            log::debug!("Loaded entity '{}'", label_for_logs);
+        }
+
+        let mut parent_children_map: HashMap<Label, Vec<Label>> = HashMap::new();
+        
+        for entity_label in label_to_entity.keys() {
+            let children: Vec<Label> = self.hierarchy_map.get_children(entity_label).to_vec();
+            if !children.is_empty() {
+                parent_children_map.insert(entity_label.clone(), children);
+            }
+        }
+        
+        for (parent_label, child_labels) in parent_children_map {
+            let Some(&parent_entity) = label_to_entity.get(&parent_label) else {
+                log::warn!(
+                    "Unable to resolve parent entity '{}' while rebuilding hierarchy",
+                    parent_label
+                );
+                continue;
+            };
+
+            let mut resolved_children = Vec::new();
+            for child_label in child_labels {
+                if let Some(&child_entity) = label_to_entity.get(&child_label) {
+                    resolved_children.push(child_entity);
+                } else {
+                    log::warn!(
+                        "Unable to resolve child '{}' for parent '{}'",
+                        child_label,
+                        parent_label
+                    );
+                }
+            }
+
+            if resolved_children.is_empty() {
+                continue;
+            }
+
+            let mut local_insert_one: Option<hecs::Entity> = None;
+
+            match world.query_one::<&mut Parent>(parent_entity) {
+                Ok(mut parent_query) => {
+                    if let Some(parent_component) = parent_query.get() {
+                        parent_component.clear();
+                        parent_component
+                            .children_mut()
+                            .extend(resolved_children.iter().copied());
+                    } else {
+                        local_insert_one = Some(parent_entity);
+                    }
+                }
+                Err(e) => {
+                    log::warn!(
+                        "Failed to query Parent component for entity {:?}: {}",
+                        parent_entity,
+                        e
+                    );
+                    local_insert_one = Some(parent_entity);
+                }
+            }
+
+            if let Some(parent_entity) = local_insert_one
+                && let Err(e) = world.insert_one(parent_entity, Parent::new(resolved_children))
+            {
+                log::error!(
+                    "Failed to attach Parent component to entity {:?}: {}",
+                    parent_entity,
+                    e
+                );
+            }
+        }
+
+        {
+            let mut has_light = false;
+            if world
+                .query::<(&LightComponent, &Light)>()
+                .iter()
+                .next()
+                .is_some()
+            {
+                has_light = true;
+            }
+
+            if !has_light {
+                log::info!("No lights in scene, spawning default light");
+                if let Some(ref s) = progress_sender {
+                    let _ = s.send(WorldLoadingStatus::LoadingEntity {
+                        index: 0,
+                        name: String::from("Default Light"),
+                        total: 1,
+                    });
+                }
+                let comp = LightComponent::directional(glam::DVec3::ONE, 1.0);
+                let light_direction = LightComponent::default_direction();
+                let rotation =
+                    DQuat::from_rotation_arc(DVec3::new(0.0, 0.0, -1.0), light_direction);
+                let trans = Transform {
+                    position: glam::DVec3::new(2.0, 4.0, 2.0),
+                    rotation,
+                    ..Default::default()
+                };
+                let entity_trans = EntityTransform::new(trans, Transform::default());
+                let light =
+                    Light::new(graphics.clone(), comp.clone(), trans, Some("Default Light")).await;
+
+                {
+                    world.spawn((
+                        Label::from("Default Light"),
+                        comp,
+                        entity_trans,
+                        light,
+                        ModelProperties::default(),
+                    ));
+                }
+            }
+        }
+
+        log::info!(
+            "Loaded {} entities from scene",
+            self.entities.len()
+        );
+        #[cfg(feature = "editor")]
+        {
+            let debug_camera = {
+                world
+                    .query::<(&Camera, &CameraComponent)>()
+                    .iter()
+                    .find_map(|(entity, (_, component))| {
+                        if matches!(component.camera_type, CameraType::Debug) {
+                            Some(entity)
+                        } else {
+                            None
+                        }
+                    })
+            };
+
+            {
+                if let Some(camera_entity) = debug_camera {
+                    log::info!("Using existing debug camera for editor");
+                    Ok(camera_entity)
+                } else {
+                    log::info!("No debug camera found, creating viewport camera for editor");
+                    if let Some(ref s) = progress_sender {
+                        let _ = s.send(WorldLoadingStatus::LoadingEntity {
+                            index: 0,
+                            name: String::from("Viewport Camera"),
+                            total: 1,
+                        });
+                    }
+                    let camera = Camera::predetermined(graphics.clone(), Some("Viewport Camera"));
+                    let component = crate::camera::DebugCamera::new();
+                    let camera_entity = { world.spawn((camera, component)) };
+                    Ok(camera_entity)
+                }
+            }
+        }
+
+        #[cfg(not(feature = "editor"))]
+        {
+            let player_camera = world
+                .query::<(&Camera, &CameraComponent)>()
+                .iter()
+                .find_map(|(entity, (_, component))| {
+                    if matches!(component.camera_type, CameraType::Player) {
+                        Some(entity)
+                    } else {
+                        None
+                    }
+                });
+
+            if let Some(camera_entity) = player_camera {
+                log::info!("Using player camera for runtime");
+                Ok(camera_entity)
+            } else {
+                panic!("Runtime mode requires a player camera, but none was found in the scene!");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/eucalyptus-core/src/scripting/jni/exports.rs b/eucalyptus-core/src/scripting/jni/exports.rs
index dd15cfc..e005467 100644
--- a/eucalyptus-core/src/scripting/jni/exports.rs
+++ b/eucalyptus-core/src/scripting/jni/exports.rs
@@ -14,7 +14,7 @@ use crate::{convert_jlong_to_entity, convert_jstring, convert_ptr};
 use dropbear_engine::asset::PointerKind::Const;
 use dropbear_engine::asset::{ASSET_REGISTRY, AssetHandle, AssetRegistry};
 use dropbear_engine::camera::Camera;
-use dropbear_engine::entity::{MeshRenderer, Transform};
+use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
 use dropbear_engine::model::Model;
 use dropbear_engine::utils::ResourceReference;
 use glam::{DQuat, DVec3};
@@ -95,40 +95,89 @@ pub fn Java_com_dropbear_ffi_JNINative_getTransform(
 
     let entity = convert_jlong_to_entity!(entity_id);
 
-    if let Ok(mut q) = world.query_one::<(&MeshRenderer, &Transform)>(entity)
-        && let Some((_, transform)) = q.get()
+    if let Ok(mut q) = world.query_one::<(&EntityTransform)>(entity)
+        && let Some(transform) = q.get()
     {
-        let new_transform = *transform;
+        let wt = *transform.world();
 
         let transform_class = match env.find_class("com/dropbear/math/Transform") {
             Ok(c) => c,
             Err(_) => return JObject::null(),
         };
 
-        return match env.new_object(
+        let world_transform_java = match env.new_object(
             &transform_class,
             "(DDDDDDDDDD)V",
             &[
-                new_transform.position.x.into(),
-                new_transform.position.y.into(),
-                new_transform.position.z.into(),
-                new_transform.rotation.x.into(),
-                new_transform.rotation.y.into(),
-                new_transform.rotation.z.into(),
-                new_transform.rotation.w.into(),
-                new_transform.scale.x.into(),
-                new_transform.scale.y.into(),
-                new_transform.scale.z.into(),
+                wt.position.x.into(),
+                wt.position.y.into(),
+                wt.position.z.into(),
+                wt.rotation.x.into(),
+                wt.rotation.y.into(),
+                wt.rotation.z.into(),
+                wt.rotation.w.into(),
+                wt.scale.x.into(),
+                wt.scale.y.into(),
+                wt.scale.z.into(),
             ],
         ) {
             Ok(java_transform) => java_transform,
             Err(_) => {
                 println!(
+                    "[Java_com_dropbear_ffi_JNINative_getTransform] [ERROR] Failed to create world transform object"
+                );
+                return JObject::null();
+            }
+        };
+
+        let lt = *transform.local();
+
+        let local_transform_java = match env.new_object(
+            &transform_class,
+            "(DDDDDDDDDD)V",
+            &[
+                lt.position.x.into(),
+                lt.position.y.into(),
+                lt.position.z.into(),
+                lt.rotation.x.into(),
+                lt.rotation.y.into(),
+                lt.rotation.z.into(),
+                lt.rotation.w.into(),
+                lt.scale.x.into(),
+                lt.scale.y.into(),
+                lt.scale.z.into(),
+            ],
+        ) {
+            Ok(java_transform) => java_transform,
+            Err(_) => {
+                println!(
+                    "[Java_com_dropbear_ffi_JNINative_getTransform] [ERROR] Failed to create local transform object"
+                );
+                return JObject::null();
+            }
+        };
+
+        let entity_transform_class = match env.find_class("com/dropbear/EntityTransform") {
+            Ok(c) => c,
+            Err(_) => return JObject::null(),
+        };
+
+        return match env.new_object(
+            &entity_transform_class,
+            "(Lcom/dropbear/math/Transform;Lcom/dropbear/math/Transform)V",
+            &[
+                JValue::Object(&local_transform_java),
+                JValue::Object(&world_transform_java),
+            ]
+        ) {
+            Ok(java_transform) => java_transform,
+            Err(_) => {
+                println!(
                     "[Java_com_dropbear_ffi_JNINative_getTransform] [ERROR] Failed to create Transform object"
                 );
                 JObject::null()
             }
-        };
+        }
     }
 
     println!(
diff --git a/eucalyptus-core/src/spawn.rs b/eucalyptus-core/src/spawn.rs
index d7e26ed..3065738 100644
--- a/eucalyptus-core/src/spawn.rs
+++ b/eucalyptus-core/src/spawn.rs
@@ -1,5 +1,5 @@
 use crate::states::ModelProperties;
-use dropbear_engine::entity::Transform;
+use dropbear_engine::entity::{EntityTransform};
 use dropbear_engine::future::{FutureHandle, FutureQueue};
 use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::utils::ResourceReference;
@@ -17,8 +17,8 @@ pub struct PendingSpawn {
     pub asset_path: ResourceReference,
     /// The name/label of the asset
     pub asset_name: String,
-    /// The [`Transform`] properties (position)
-    pub transform: Transform,
+    /// The [`EntityTransform`] properties (position)
+    pub transform: EntityTransform,
     /// The properties of a model, as specified in [`ModelProperties`]
     pub properties: ModelProperties,
     /// An optional future handle to an object.
diff --git a/eucalyptus-core/src/states.rs b/eucalyptus-core/src/states.rs
index 8afb486..716ad25 100644
--- a/eucalyptus-core/src/states.rs
+++ b/eucalyptus-core/src/states.rs
@@ -1,6 +1,7 @@
+use crate::traits::SerializableComponent;
 use crate::camera::{CameraComponent, CameraType};
-use crate::hierarchy::Parent;
-use crate::utils::{PROTO_TEXTURE, ResolveReference};
+use crate::hierarchy::{Parent, SceneHierarchy};
+use crate::utils::{ResolveReference, PROTO_TEXTURE};
 use chrono::Utc;
 use dropbear_engine::asset::ASSET_REGISTRY;
 use dropbear_engine::camera::{Camera, CameraBuilder, CameraSettings};
@@ -26,6 +27,8 @@ use std::path::{Path, PathBuf};
 use std::sync::Arc;
 use std::{fmt, fs};
 use tokio::sync::mpsc::UnboundedSender;
+use dropbear_macro::SerializableComponent;
+use crate::scene::SceneConfig;
 
 pub static PROJECT: Lazy<RwLock<ProjectConfig>> =
     Lazy::new(|| RwLock::new(ProjectConfig::default()));
@@ -574,7 +577,7 @@ pub enum EntityNode {
     },
 }
 
-#[derive(Default, Debug, Serialize, Deserialize, Clone)]
+#[derive(Default, Debug, Serialize, Deserialize, Clone, SerializableComponent)]
 pub struct ScriptComponent {
     pub tags: Vec<String>,
 }
@@ -692,12 +695,12 @@ impl EntityNode {
     }
 }
 
-#[derive(Debug, Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone, SerializableComponent)]
 pub struct CameraConfig {
     pub label: String,
     pub camera_type: CameraType,
 
-    pub position: [f64; 3],
+    pub eye: [f64; 3],
     pub target: [f64; 3],
     pub up: [f64; 3],
     pub aspect: f64,
@@ -708,8 +711,6 @@ pub struct CameraConfig {
     pub speed: f32,
     pub sensitivity: f32,
 
-    // pub follow_target_entity_label: Option<String>,
-    // pub follow_offset: Option<[f64; 3]>,
     pub starting_camera: bool,
 }
 
@@ -717,15 +718,13 @@ impl Default for CameraConfig {
     fn default() -> Self {
         let default = CameraComponent::new();
         Self {
-            position: [0.0, 1.0, 2.0],
+            eye: [0.0, 1.0, 2.0],
             target: [0.0, 0.0, 0.0],
             up: [0.0, 1.0, 0.0],
             aspect: 16.0 / 9.0,
             fov: 45.0,
             near: 0.1,
             far: 100.0,
-            // follow_target_entity_label: None,
-            // follow_offset: None,
             label: String::new(),
             camera_type: CameraType::Normal,
             speed: default.settings.speed as f32,
@@ -742,7 +741,7 @@ impl CameraConfig {
         // follow_target: Option<&CameraFollowTarget>,
     ) -> Self {
         Self {
-            position: camera.position().to_array(),
+            eye: camera.position().to_array(),
             target: camera.target.to_array(),
             label: camera.label.clone(),
             camera_type: component.camera_type,
@@ -753,38 +752,12 @@ impl CameraConfig {
             far: camera.zfar as f32,
             speed: component.settings.speed as f32,
             sensitivity: component.settings.sensitivity as f32,
-            // follow_target_entity_label: follow_target.map(|target| target.follow_target.clone()),
-            // follow_offset: follow_target.map(|target| target.offset.to_array()),
             starting_camera: component.starting_camera,
         }
     }
 }
 
-#[derive(Default, Debug, Serialize, Deserialize, Clone)]
-pub struct SceneEntity {
-    #[serde(default)]
-    pub model_path: ResourceReference,
-    #[serde(default)]
-    pub label: Label,
-    #[serde(default)]
-    pub transform: Transform,
-    #[serde(default)]
-    pub properties: ModelProperties,
-    #[serde(default)]
-    pub script: Option<ScriptComponent>,
-    #[serde(default)]
-    pub camera: Option<CameraConfig>,
-    #[serde(default)]
-    pub children: Option<Vec<Label>>,
-    #[serde(default)]
-    pub material_overrides: Vec<MaterialOverride>,
-
-    #[serde(skip)]
-    #[allow(dead_code)]
-    pub entity_id: Option<hecs::Entity>,
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
+#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, SerializableComponent)]
 pub struct ModelProperties {
     pub custom_properties: Vec<Property>,
     pub next_id: u64,
@@ -813,7 +786,7 @@ impl Default for Value {
 }
 
 impl Display for Value {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
         let string: String = match self {
             Value::String(_) => "String".into(),
             Value::Int(_) => "Int".into(),
@@ -923,540 +896,7 @@ impl Default for ModelProperties {
     }
 }
 
-#[derive(Default, Debug, Serialize, Deserialize, Clone)]
-pub struct SceneConfig {
-    #[serde(default)]
-    pub scene_name: String,
-    #[serde(default)]
-    pub entities: Vec<SceneEntity>,
-    #[serde(default)]
-    pub cameras: Vec<CameraConfig>,
-    #[serde(default)]
-    pub lights: Vec<LightConfig>,
-    #[serde(default)]
-    // todo later
-    // pub settings: SceneSettings,
-    #[serde(skip)]
-    pub path: PathBuf,
-}
-
-impl SceneConfig {
-    /// Creates a new instance of the scene config
-    pub fn new(scene_name: String, path: impl AsRef<Path>) -> Self {
-        Self {
-            scene_name,
-            path: path.as_ref().to_path_buf(),
-            entities: Vec::new(),
-            cameras: Vec::new(),
-            lights: Vec::new(),
-        }
-    }
-
-    /// Write the scene config to a .eucs file
-    pub fn write_to(&self, project_path: impl AsRef<Path>) -> anyhow::Result<()> {
-        let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default())
-            .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?;
-
-        let scenes_dir = project_path.as_ref().join("scenes");
-        fs::create_dir_all(&scenes_dir)?;
-
-        let config_path = scenes_dir.join(format!("{}.eucs", self.scene_name));
-        fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?;
-        Ok(())
-    }
-
-    /// Read a scene config from a .eucs file
-    pub fn read_from(scene_path: impl AsRef<Path>) -> anyhow::Result<Self> {
-        let ron_str = fs::read_to_string(scene_path.as_ref())?;
-        let mut config: SceneConfig = ron::de::from_str(&ron_str)
-            .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?;
-
-        config.path = scene_path.as_ref().to_path_buf();
-        Ok(config)
-    }
-
-    pub async fn load_into_world(
-        &self,
-        world: &mut hecs::World,
-        graphics: Arc<SharedGraphicsContext>,
-        progress_sender: Option<UnboundedSender<WorldLoadingStatus>>,
-    ) -> anyhow::Result<hecs::Entity> {
-        if let Some(ref s) = progress_sender {
-            let _ = s.send(WorldLoadingStatus::Idle);
-        }
-
-        log::info!(
-            "Loading scene [{}], clearing world with {} entities",
-            self.scene_name,
-            world.len()
-        );
-        {
-            world.clear();
-        }
-
-        #[allow(unused_variables)]
-        let project_config = if cfg!(feature = "editor") {
-            let cfg = PROJECT.read();
-            cfg.project_path.clone()
-        } else {
-            log::debug!("Not using the editor feature, returning empty pathbuffer");
-            PathBuf::new()
-        };
-
-        log::info!("World cleared, now has {} entities", world.len());
-
-        let entity_configs: Vec<(usize, SceneEntity)> = {
-            let cloned = self.entities.clone();
-            cloned
-                .into_par_iter()
-                .enumerate()
-                .map(|(i, e)| (i, e))
-                .collect()
-        };
-
-        let mut label_to_entity: HashMap<Label, hecs::Entity> = HashMap::new();
-        let mut pending_parent_links: Vec<(Label, Vec<Label>)> = Vec::new();
-
-        for (index, entity_config) in entity_configs {
-            let SceneEntity {
-                model_path,
-                label,
-                transform,
-                properties,
-                script,
-                camera,
-                children,
-                material_overrides,
-                ..
-            } = entity_config;
-
-            let label_for_map = label.clone();
-            let label_for_logs = label_for_map.to_string();
-
-            log::debug!("Loading entity: {}", label_for_logs);
-
-            let total = self.entities.len();
-
-            if let Some(ref s) = progress_sender {
-                let _ = s.send(WorldLoadingStatus::LoadingEntity {
-                    index,
-                    name: label_for_logs.clone(),
-                    total,
-                });
-            }
-
-            let mut renderer = match &model_path.ref_type {
-                ResourceReferenceType::File(reference) => {
-                    let path = model_path.resolve()?;
-
-                    log::debug!(
-                        "Path for entity {} is {} from reference {}",
-                        label_for_logs,
-                        path.display(),
-                        reference
-                    );
-
-                    MeshRenderer::from_path(graphics.clone(), &path, Some(label_for_map.as_str()))
-                        .await?
-                }
-                ResourceReferenceType::Bytes(bytes) => {
-                    log::info!("Loading entity from bytes [Len: {}]", bytes.len());
-
-                    let model = Model::load_from_memory(
-                        graphics.clone(),
-                        bytes.clone(),
-                        Some(label_for_map.as_str()),
-                    )
-                    .await?;
-                    MeshRenderer::from_handle(model)
-                }
-                ResourceReferenceType::Plane => {
-                    let width = properties.get_float("width").ok_or_else(|| {
-                        anyhow::anyhow!(
-                            "Entity '{}' has no width property or it's not a float",
-                            label_for_logs
-                        )
-                    })?;
-
-                    let height = properties.get_float("height").ok_or_else(|| {
-                        anyhow::anyhow!(
-                            "Entity '{}' has no height property or it's not a float",
-                            label_for_logs
-                        )
-                    })?;
-
-                    let tiles_x = properties.get_int("tiles_x").ok_or_else(|| {
-                        anyhow::anyhow!(
-                            "Entity '{}' has no tiles_x property or it's not an int",
-                            label_for_logs
-                        )
-                    })?;
-
-                    let tiles_z = properties.get_int("tiles_z").ok_or_else(|| {
-                        anyhow::anyhow!(
-                            "Entity '{}' has no tiles_z property or it's not an int",
-                            label_for_logs
-                        )
-                    })?;
-
-                    PlaneBuilder::new()
-                        .with_size(width as f32, height as f32)
-                        .with_tiles(tiles_x as u32, tiles_z as u32)
-                        .build(
-                            graphics.clone(),
-                            PROTO_TEXTURE,
-                            Some(label_for_map.as_str()),
-                        )
-                        .await?
-                }
-                ResourceReferenceType::None => {
-                    anyhow::bail!(
-                        "Entity '{}' has a resource reference of None, which cannot be loaded or referenced",
-                        label_for_logs
-                    );
-                }
-            };
-
-            if !material_overrides.is_empty() {
-                for override_entry in &material_overrides {
-                    if ASSET_REGISTRY
-                        .model_handle_from_reference(&override_entry.source_model)
-                        .is_none()
-                    {
-                        if matches!(
-                            override_entry.source_model.ref_type,
-                            ResourceReferenceType::File(_)
-                        ) {
-                            let source_path = override_entry.source_model.resolve()?;
-                            let label_hint = override_entry.source_model.as_uri();
-                            Model::load(graphics.clone(), &source_path, label_hint).await?;
-                        } else {
-                            log::warn!(
-                                "Material override for '{}' references unsupported resource {:?}",
-                                label_for_logs,
-                                override_entry.source_model
-                            );
-                            continue;
-                        }
-                    }
-
-                    if let Err(err) = renderer.apply_material_override(
-                        &override_entry.target_material,
-                        override_entry.source_model.clone(),
-                        &override_entry.source_material,
-                    ) {
-                        log::warn!(
-                            "Failed to apply material override '{}' on '{}': {}",
-                            override_entry.target_material,
-                            label_for_logs,
-                            err
-                        );
-                    }
-                }
-            }
-
-            renderer.update(&transform);
-
-            let entity = world.spawn((label, renderer, transform, properties));
-
-            if let Some(script_component) = script {
-                world.insert_one(entity, script_component)?;
-            }
-
-            if let Some(camera_config) = camera {
-                let camera = Camera::new(
-                    graphics.clone(),
-                    CameraBuilder {
-                        eye: DVec3::from_array(camera_config.position),
-                        target: DVec3::from_array(camera_config.target),
-                        up: DVec3::from_array(camera_config.up),
-                        aspect: camera_config.aspect,
-                        znear: camera_config.near as f64,
-                        zfar: camera_config.far as f64,
-                        settings: CameraSettings::new(
-                            camera_config.speed as f64,
-                            camera_config.sensitivity as f64,
-                            camera_config.fov as f64,
-                        ),
-                    },
-                    Some(&camera_config.label),
-                );
-
-                let camera_component = CameraComponent {
-                    settings: CameraSettings::new(
-                        camera_config.speed as f64,
-                        camera_config.sensitivity as f64,
-                        camera_config.fov as f64,
-                    ),
-                    camera_type: camera_config.camera_type,
-                    starting_camera: camera_config.starting_camera,
-                };
-
-                world.insert(entity, (camera, camera_component))?;
-            }
-
-            if let Some(children_labels) = children {
-                if !children_labels.is_empty() {
-                    pending_parent_links.push((label_for_map.clone(), children_labels));
-                }
-            }
-
-            if let Some(previous) = label_to_entity.insert(label_for_map.clone(), entity) {
-                log::warn!(
-                    "Duplicate entity label '{}' detected; previous entity {:?} will be overwritten in hierarchy mapping",
-                    label_for_logs,
-                    previous
-                );
-            }
-
-            log::debug!("Loaded entity '{}'", label_for_logs);
-        }
-
-        for (parent_label, child_labels) in pending_parent_links {
-            let Some(&parent_entity) = label_to_entity.get(&parent_label) else {
-                log::warn!(
-                    "Unable to resolve parent entity '{}' while rebuilding hierarchy",
-                    parent_label
-                );
-                continue;
-            };
-
-            let mut resolved_children = Vec::new();
-            for child_label in child_labels {
-                if let Some(&child_entity) = label_to_entity.get(&child_label) {
-                    resolved_children.push(child_entity);
-                } else {
-                    log::warn!(
-                        "Unable to resolve child '{}' for parent '{}'",
-                        child_label,
-                        parent_label
-                    );
-                }
-            }
-
-            if resolved_children.is_empty() {
-                continue;
-            }
-
-            let mut local_insert_one: Option<hecs::Entity> = None;
-
-            match world.query_one::<&mut Parent>(parent_entity) {
-                Ok(mut parent_query) => {
-                    if let Some(parent_component) = parent_query.get() {
-                        parent_component.clear();
-                        parent_component
-                            .children_mut()
-                            .extend(resolved_children.iter().copied());
-                    } else {
-                        local_insert_one = Some(parent_entity);
-                    }
-                }
-                Err(e) => {
-                    log::warn!(
-                        "Failed to query Parent component for entity {:?}: {}",
-                        parent_entity,
-                        e
-                    );
-                    local_insert_one = Some(parent_entity);
-                }
-            }
-
-            if let Some(parent_entity) = local_insert_one {
-                if let Err(e) = world.insert_one(parent_entity, Parent::new(resolved_children)) {
-                    log::error!(
-                        "Failed to attach Parent component to entity {:?}: {}",
-                        parent_entity,
-                        e
-                    );
-                }
-            }
-        }
-
-        let total = self.lights.len();
-
-        for (index, light_config) in self.lights.iter().enumerate() {
-            log::debug!("Loading light: {}", light_config.label);
-            if let Some(ref s) = progress_sender {
-                let _ = s.send(WorldLoadingStatus::LoadingLight {
-                    index,
-                    name: light_config.label.clone(),
-                    total,
-                });
-            }
-
-            let light = Light::new(
-                graphics.clone(),
-                light_config.light_component.clone(),
-                light_config.transform,
-                Some(&light_config.label),
-            )
-            .await;
-            {
-                world.spawn((
-                    Label::from(light_config.label.clone()),
-                    light_config.light_component.clone(),
-                    light_config.transform,
-                    light,
-                    ModelProperties::default(),
-                ));
-            }
-        }
-
-        let total = self.cameras.len();
-        for (index, camera_config) in self.cameras.iter().enumerate() {
-            log::debug!(
-                "Loading camera {} of type {:?}",
-                camera_config.label,
-                camera_config.camera_type
-            );
-            if let Some(ref s) = progress_sender {
-                let _ = s.send(WorldLoadingStatus::LoadingCamera {
-                    index,
-                    name: camera_config.label.clone(),
-                    total,
-                });
-            }
-
-            let camera = Camera::new(
-                graphics.clone(),
-                CameraBuilder {
-                    eye: DVec3::from_array(camera_config.position),
-                    target: DVec3::from_array(camera_config.target),
-                    up: DVec3::from_array(camera_config.up),
-                    aspect: camera_config.aspect,
-                    znear: camera_config.near as f64,
-                    zfar: camera_config.far as f64,
-                    settings: CameraSettings::new(
-                        camera_config.speed as f64,
-                        camera_config.sensitivity as f64,
-                        camera_config.fov as f64,
-                    ),
-                },
-                Some(&camera_config.label),
-            );
-
-            let component = CameraComponent {
-                settings: CameraSettings::new(
-                    camera_config.speed as f64,
-                    camera_config.sensitivity as f64,
-                    camera_config.fov as f64,
-                ),
-                camera_type: camera_config.camera_type,
-                starting_camera: camera_config.starting_camera,
-            };
-            world.spawn((Label::from(camera_config.label.clone()), camera, component));
-        }
-
-        {
-            let mut is_none = false;
-            if world
-                .query::<(&LightComponent, &Light)>()
-                .iter()
-                .next()
-                .is_none()
-            {
-                log::info!("No lights in scene, spawning default light");
-                is_none = true;
-            }
-
-            if is_none {
-                if let Some(ref s) = progress_sender {
-                    let _ = s.send(WorldLoadingStatus::LoadingLight {
-                        index: 0,
-                        name: String::from("Default Light"),
-                        total: 1,
-                    });
-                }
-                let comp = LightComponent::directional(glam::DVec3::ONE, 1.0);
-                let light_direction = LightComponent::default_direction();
-                let rotation =
-                    DQuat::from_rotation_arc(DVec3::new(0.0, 0.0, -1.0), light_direction);
-                let trans = Transform {
-                    position: glam::DVec3::new(2.0, 4.0, 2.0),
-                    rotation,
-                    ..Default::default()
-                };
-                let light =
-                    Light::new(graphics.clone(), comp.clone(), trans, Some("Default Light")).await;
-
-                {
-                    world.spawn((
-                        Label::from("Default Light"),
-                        comp,
-                        trans,
-                        light,
-                        ModelProperties::default(),
-                    ));
-                }
-            }
-        }
-
-        log::info!(
-            "Loaded {} entities, {} lights and {} cameras",
-            self.entities.len(),
-            self.lights.len(),
-            self.cameras.len()
-        );
-        #[cfg(feature = "editor")]
-        {
-            let debug_camera = {
-                world
-                    .query::<(&Camera, &CameraComponent)>()
-                    .iter()
-                    .find_map(|(entity, (_, component))| {
-                        if matches!(component.camera_type, CameraType::Debug) {
-                            Some(entity)
-                        } else {
-                            None
-                        }
-                    })
-            };
-
-            {
-                if let Some(camera_entity) = debug_camera {
-                    log::info!("Using existing debug camera for editor");
-                    Ok(camera_entity)
-                } else {
-                    log::info!("No debug camera found, creating viewport camera for editor");
-                    if let Some(ref s) = progress_sender {
-                        let _ = s.send(WorldLoadingStatus::LoadingCamera {
-                            index: 0,
-                            name: String::from("Viewport Camera"),
-                            total: 1,
-                        });
-                    }
-                    let camera = Camera::predetermined(graphics.clone(), Some("Viewport Camera"));
-                    let component = crate::camera::DebugCamera::new();
-                    let camera_entity = { world.spawn((camera, component)) };
-                    Ok(camera_entity)
-                }
-            }
-        }
-
-        #[cfg(not(feature = "editor"))]
-        {
-            let player_camera = world
-                .query::<(&Camera, &CameraComponent)>()
-                .iter()
-                .find_map(|(entity, (_, component))| {
-                    if matches!(component.camera_type, CameraType::Player) {
-                        Some(entity)
-                    } else {
-                        None
-                    }
-                });
-
-            if let Some(camera_entity) = player_camera {
-                log::info!("Using player camera for runtime");
-                Ok(camera_entity)
-            } else {
-                panic!("Runtime mode requires a player camera, but none was found in the scene!");
-            }
-        }
-    }
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone, SerializableComponent)]
 pub struct LightConfig {
     pub label: String,
     pub transform: Transform,
@@ -1510,16 +950,6 @@ pub enum WorldLoadingStatus {
         name: String,
         total: usize,
     },
-    LoadingLight {
-        index: usize,
-        name: String,
-        total: usize,
-    },
-    LoadingCamera {
-        index: usize,
-        name: String,
-        total: usize,
-    },
     Completed,
 }
 
@@ -1535,7 +965,7 @@ pub struct RuntimeData {
     pub scripts: HashMap<String, String>,
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone, SerializableComponent)]
 pub struct Label(String);
 
 impl Default for Label {
@@ -1619,3 +1049,9 @@ impl DerefMut for Label {
         self.as_mut_string()
     }
 }
+
+#[derive(Default, Debug, Clone, Serialize, Deserialize, SerializableComponent)]
+pub struct SerializedMeshRenderer {
+    pub handle: ResourceReference,
+    pub material_override: Vec<MaterialOverride>,
+}
\ No newline at end of file
diff --git a/eucalyptus-editor/src/editor/mod.rs b/eucalyptus-editor/src/editor/mod.rs
index 696864f..9ac0e29 100644
--- a/eucalyptus-editor/src/editor/mod.rs
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -21,14 +21,14 @@ use dropbear_engine::{
     future::FutureHandle,
     graphics::{RenderContext, SharedGraphicsContext},
     lighting::{Light, LightManager},
-    model::{MODEL_CACHE, ModelId},
+    model::{ModelId, MODEL_CACHE},
     scene::SceneCommand,
 };
 use egui::{self, Context};
 use egui_dock::{DockArea, DockState, NodeIndex, Style};
 use eucalyptus_core::APP_INFO;
-use eucalyptus_core::hierarchy::Parent;
-use eucalyptus_core::states::Label;
+use eucalyptus_core::hierarchy::{Parent, SceneHierarchy};
+use eucalyptus_core::states::{Label, SerializedMeshRenderer};
 use eucalyptus_core::{
     camera::{CameraComponent, CameraType, DebugCamera},
     fatal, info,
@@ -37,8 +37,8 @@ use eucalyptus_core::{
     scripting::{BuildStatus, ScriptManager, ScriptTarget},
     states,
     states::{
-        CameraConfig, EditorTab, EntityNode, LightConfig, ModelProperties, PROJECT, SCENES,
-        SceneConfig, SceneEntity, ScriptComponent, WorldLoadingStatus,
+        CameraConfig, EditorTab, EntityNode, LightConfig, ModelProperties, ScriptComponent, WorldLoadingStatus,
+        PROJECT, SCENES,
     },
     success, success_without_console,
     utils::ViewportMode,
@@ -62,6 +62,10 @@ use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode};
 use wgpu::{Color, Extent3d, RenderPipeline};
 use winit::window::CursorGrabMode;
 use winit::{keyboard::KeyCode, window::Window};
+use dropbear_engine::entity::EntityTransform;
+use dropbear_engine::lighting::LightComponent;
+use eucalyptus_core::scene::{SceneConfig, SceneEntity};
+use eucalyptus_core::traits::SerializableComponent;
 
 pub struct Editor {
     pub scene_command: SceneCommand,
@@ -269,44 +273,66 @@ impl Editor {
             .iter_mut()
             .find(|scene| scene.scene_name == target_scene_name)
             .ok_or_else(|| anyhow::anyhow!("Active scene '{}' is not loaded", target_scene_name))?;
+        
         scene.entities.clear();
-        scene.lights.clear();
-        scene.cameras.clear();
+        scene.hierarchy_map = SceneHierarchy::new();
 
-        for (id, (label, renderer, transform, properties, script, parent)) in self
-            .world
-            .query::<(
-                &Label,
-                &MeshRenderer,
-                Option<&Transform>,
-                &ModelProperties,
-                Option<&ScriptComponent>,
-                Option<&Parent>,
-            )>()
-            .iter()
-        {
-            let transform = *transform.unwrap_or(&Transform::default());
+        let labels = self.world.query::<&Label>().iter().map(|(e, l)| (e, l.clone())).collect::<Vec<_>>();
+
+        for (id, label) in labels {
             let entity_label = label.clone();
+            let mut components: Vec<Box<dyn SerializableComponent>> = Vec::new();
 
-            let camera_config = if let Ok(mut camera_query) =
-                self.world.query_one::<(&Camera, &CameraComponent)>(id)
+            if let Ok(transform) = self.world.get::<&EntityTransform>(id) {
+                components.push(Box::new(*transform));
+            }
+
+            if let Ok(renderer) = self.world.get::<&MeshRenderer>(id) {
+                let serialized = SerializedMeshRenderer {
+                    handle: renderer.handle().path.clone(),
+                    material_override: renderer.material_overrides().to_vec(),
+                };
+                components.push(Box::new(serialized));
+            }
+
+            if let Ok(mut q) = self.world.query_one::<&ModelProperties>(id) && let Some(properties) = q.get() {
+                components.push(Box::new(properties.clone()));
+            }
+
+            if let Ok(mut camera_query) = self.world.query_one::<(&Camera, &CameraComponent)>(id)
+                && let Some((camera, component)) = camera_query.get()
             {
-                if let Some((camera, component)) = camera_query.get() {
-                    Some(CameraConfig::from_ecs_camera(camera, component))
-                } else {
-                    None
+                let camera_config = CameraConfig::from_ecs_camera(camera, component);
+                components.push(Box::new(camera_config));
+            }
+
+            if let Ok(mut light_query) = self.world.query_one::<(&Light, &LightComponent, &EntityTransform)>(id) {
+                if let Some((light, light_component, entity_transform)) = light_query.get() {
+                    let light_config = LightConfig {
+                        label: light.cube_model.label.to_string(),
+                        transform: entity_transform.sync(),
+                        light_component: light_component.clone(),
+                        enabled: light_component.enabled,
+                        entity_id: Some(id),
+                    };
+                    components.push(Box::new(light_config));
                 }
-            } else {
-                None
-            };
+            }
 
-            let children = if let Some(parent_component) = parent {
-                let mut collected = Vec::new();
+            if let Ok(mut q) = self.world.query_one::<&ScriptComponent>(id) && let Some(script) = q.get() {
+                components.push(Box::new(script.clone()));
+            }
+
+            let mut parent_component = None;
+
+            if let Ok(comp) = self.world.query_one_mut::<&Parent>(id) {
+                parent_component = Some(comp.clone());
+            }
+
+            if let Some(parent_component) = parent_component {
                 for &child_entity in parent_component.children() {
-                    if let Ok(mut child_query) = self.world.query_one::<&Label>(child_entity)
-                        && let Some(child_label) = child_query.get()
-                    {
-                        collected.push(child_label.clone());
+                    if let Ok(child_label) = self.world.query_one_mut::<&Label>(child_entity) {
+                        scene.hierarchy_map.set_parent(child_label.clone(), entity_label.clone());
                     } else {
                         log::warn!(
                             "Unable to resolve child entity {:?} for parent '{}' when saving scene",
@@ -315,69 +341,27 @@ impl Editor {
                         );
                     }
                 }
-
-                if collected.is_empty() {
-                    None
-                } else {
-                    Some(collected)
-                }
-            } else {
-                None
-            };
+            }
 
             let scene_entity = SceneEntity {
-                model_path: renderer.handle().path.clone(),
                 label: entity_label.clone(),
-                transform,
-                properties: properties.clone(),
-                script: script.cloned(),
-                camera: camera_config,
-                children,
-                material_overrides: renderer.material_overrides().to_vec(),
+                components,
                 entity_id: Some(id),
             };
 
             scene.entities.push(scene_entity);
-            log::debug!("Pushed entity: {}", entity_label);
-        }
-
-        for (id, (light_component, transform, light)) in self
-            .world
-            .query::<(
-                &dropbear_engine::lighting::LightComponent,
-                &Transform,
-                &Light,
-            )>()
-            .iter()
-        {
-            let light_config = LightConfig {
-                label: light.cube_model.label.to_string(),
-                transform: *transform,
-                light_component: light_component.clone(),
-                enabled: light_component.enabled,
-                entity_id: Some(id),
-            };
-
-            scene.lights.push(light_config);
-            log::debug!("Pushed light into lights: {}", light.cube_model.label);
-        }
-
-        for (id, (camera, component)) in self.world.query::<(&Camera, &CameraComponent)>().iter() {
-            if self.world.get::<&MeshRenderer>(id).is_err() {
-                let camera_config = CameraConfig::from_ecs_camera(camera, component);
-                scene.cameras.push(camera_config);
-                log::debug!("Pushed standalone camera into cameras: {}", camera.label);
-            }
+            log::debug!("Saved entity: {}", entity_label);
         }
 
         log::info!(
-            "Saved {} entities and camera configs to scene '{}'",
+            "Saved {} entities to scene '{}'",
             scene.entities.len(),
             scene.scene_name
         );
 
         Ok(())
     }
+
     fn persist_active_scene_to_disk(&self) -> anyhow::Result<()> {
         let target_scene_name = self.current_scene_name.clone().or_else(|| {
             let scenes = SCENES.read();
@@ -441,16 +425,6 @@ impl Editor {
                         self.current_state =
                             WorldLoadingStatus::LoadingEntity { index, name, total };
                     }
-                    WorldLoadingStatus::LoadingLight { index, name, total } => {
-                        log::debug!("Loading light: {} ({}/{})", name, index + 1, total);
-                        self.current_state =
-                            WorldLoadingStatus::LoadingLight { index, name, total };
-                    }
-                    WorldLoadingStatus::LoadingCamera { index, name, total } => {
-                        log::debug!("Loading camera: {} ({}/{})", name, index + 1, total);
-                        self.current_state =
-                            WorldLoadingStatus::LoadingCamera { index, name, total };
-                    }
                     WorldLoadingStatus::Completed => {
                         log::debug!(
                             "Received WorldLoadingStatus::Completed - project loading finished"
@@ -494,12 +468,6 @@ impl Editor {
                         WorldLoadingStatus::LoadingEntity { name, .. } => {
                             ui.label(format!("Loading entity: {}", name));
                         }
-                        WorldLoadingStatus::LoadingLight { name, .. } => {
-                            ui.label(format!("Loading light: {}", name));
-                        }
-                        WorldLoadingStatus::LoadingCamera { name, .. } => {
-                            ui.label(format!("Loading camera: {}", name));
-                        }
                         WorldLoadingStatus::Completed => {
                             ui.label("Done!");
                         }
@@ -560,9 +528,8 @@ impl Editor {
                 *a_c = Some(cam);
 
                 log::info!(
-                    "Successfully loaded scene with {} entities and {} camera configs",
+                    "Successfully loaded scene with {} entities",
                     first_scene.entities.len(),
-                    first_scene.cameras.len(),
                 );
             } else {
                 let existing_debug_camera = {
@@ -906,20 +873,26 @@ impl Editor {
                             let query = self.world.query_one::<(
                                 &Label,
                                 &MeshRenderer,
-                                &Transform,
+                                &EntityTransform,
                                 &ModelProperties,
                             )>(*entity);
                             if let Ok(mut q) = query {
-                                if let Some((entity_label, e, t, props)) = q.get() {
-                                    let s_entity = states::SceneEntity {
-                                        model_path: e.handle().path.clone(),
+                                if let Some((entity_label, renderer, transform, props)) = q.get() {
+                                    let mut components: Vec<Box<dyn SerializableComponent>> = Vec::new();
+                                    
+                                    components.push(Box::new(*transform));
+                                    
+                                    let serialized_renderer = SerializedMeshRenderer {
+                                        handle: renderer.handle().path.clone(),
+                                        material_override: renderer.material_overrides().to_vec(),
+                                    };
+                                    components.push(Box::new(serialized_renderer));
+                                    
+                                    components.push(Box::new(props.clone()));
+                                    
+                                    let s_entity = SceneEntity {
                                         label: entity_label.clone(),
-                                        transform: *t,
-                                        properties: props.clone(),
-                                        script: None,
-                                        camera: None,
-                                        children: None,
-                                        material_overrides: e.material_overrides().to_vec(),
+                                        components,
                                         entity_id: None,
                                     };
                                     self.signal = Signal::Copy(s_entity);
@@ -1783,7 +1756,7 @@ pub enum Signal {
     RemoveComponent(hecs::Entity, Box<ComponentType>),
     CreateEntity,
     LogEntities,
-    Spawn(PendingSpawn2),
+    Spawn(PendingSpawnType),
 }
 
 #[derive(Debug)]
@@ -1817,7 +1790,7 @@ struct PendingSceneLoad {
     scene: SceneConfig,
 }
 
-pub enum PendingSpawn2 {
+pub enum PendingSpawnType {
     Light,
     Plane,
     Cube,
diff --git a/eucalyptus-editor/src/signal.rs b/eucalyptus-editor/src/signal.rs
index fee6368..ba905cf 100644
--- a/eucalyptus-editor/src/signal.rs
+++ b/eucalyptus-editor/src/signal.rs
@@ -1,5 +1,5 @@
 use crate::editor::{
-    ComponentType, Editor, EditorState, EntityType, PendingSpawn2, Signal, UndoableAction,
+    ComponentType, Editor, EditorState, EntityType, PendingSpawnType, Signal, UndoableAction,
 };
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{MeshRenderer, Transform};
@@ -667,22 +667,22 @@ impl SignalController for Editor {
 
                         if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Light")).clicked() {
                             log::debug!("Creating new lighting");
-                            self.signal = Signal::Spawn(PendingSpawn2::Light);
+                            self.signal = Signal::Spawn(PendingSpawnType::Light);
                         }
 
                         if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Plane")).clicked() {
                             log::debug!("Creating new plane");
-                            self.signal = Signal::Spawn(PendingSpawn2::Plane);
+                            self.signal = Signal::Spawn(PendingSpawnType::Plane);
                         }
 
                         if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Cube")).clicked() {
                             log::debug!("Creating new cube");
-                            self.signal = Signal::Spawn(PendingSpawn2::Cube);
+                            self.signal = Signal::Spawn(PendingSpawnType::Cube);
                         }
 
                         if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Camera")).clicked() {
                             log::debug!("Creating new cube");
-                            self.signal = Signal::Spawn(PendingSpawn2::Camera);
+                            self.signal = Signal::Spawn(PendingSpawnType::Camera);
                         }
                     });
                 if !show {
@@ -768,7 +768,7 @@ impl SignalController for Editor {
             }
             Signal::Spawn(entity_type) => {
                 match entity_type {
-                    crate::editor::PendingSpawn2::Light => {
+                    crate::editor::PendingSpawnType::Light => {
                         let light = Light::new(
                             graphics.clone(),
                             LightComponent::default(),
@@ -779,7 +779,7 @@ impl SignalController for Editor {
                         self.light_spawn_queue.push(handle);
                         success!("Pushed light to queue");
                     }
-                    crate::editor::PendingSpawn2::Plane => {
+                    crate::editor::PendingSpawnType::Plane => {
                         let transform = Transform::new();
                         let mut props = ModelProperties::new();
                         props.add_property("width".to_string(), Value::Float(500.0));
@@ -798,7 +798,7 @@ impl SignalController for Editor {
                         });
                         success!("Pushed plane to queue");
                     }
-                    PendingSpawn2::Cube => {
+                    PendingSpawnType::Cube => {
                         let pending = PendingSpawn {
                             asset_path: ResourceReference::from_bytes(include_bytes!(
                                 "../../resources/models/cube.glb"
@@ -811,7 +811,7 @@ impl SignalController for Editor {
                         push_pending_spawn(pending);
                         success!("Pushed cube to queue");
                     }
-                    PendingSpawn2::Camera => {
+                    PendingSpawnType::Camera => {
                         let camera = Camera::predetermined(graphics.clone(), None);
                         let component = CameraComponent::new();
                         {
diff --git a/eucalyptus-editor/src/spawn.rs b/eucalyptus-editor/src/spawn.rs
index 408e66a..59680a0 100644
--- a/eucalyptus-editor/src/spawn.rs
+++ b/eucalyptus-editor/src/spawn.rs
@@ -33,7 +33,6 @@ impl PendingSpawnController for Editor {
                 let graphics_clone = graphics.clone();
                 let asset_name = spawn.asset_name.clone();
                 let asset_path = spawn.asset_path.ref_type.clone();
-                let properties = spawn.properties.clone();
 
                 let func = async move {
                     match asset_path {
@@ -60,44 +59,48 @@ impl PendingSpawnController for Editor {
                             Ok(MeshRenderer::from_handle(model))
                         }
                         ResourceReferenceType::Plane => {
-                            let get_float = |key: &str| -> anyhow::Result<f32> {
-                                let val = properties
-                                    .custom_properties
-                                    .iter()
-                                    .find(|p| p.key == key)
-                                    .ok_or_else(|| {
-                                        anyhow::anyhow!("Entity has no {} property", key)
-                                    })?;
-                                match val.value {
-                                    Value::Float(f) => Ok(f as f32),
-                                    _ => Err(anyhow::anyhow!("{} is not a float", key)),
-                                }
-                            };
-
-                            let get_int = |key: &str| -> anyhow::Result<u32> {
-                                let val = properties
-                                    .custom_properties
-                                    .iter()
-                                    .find(|p| p.key == key)
-                                    .ok_or_else(|| {
-                                        anyhow::anyhow!("Entity has no {} property", key)
-                                    })?;
-                                match val.value {
-                                    Value::Int(i) => Ok(i as u32),
-                                    _ => Err(anyhow::anyhow!("{} is not an int", key)),
-                                }
-                            };
-
-                            let width = get_float("width")?;
-                            let height = get_float("height")?;
-                            let tiles_x = get_int("tiles_x")?;
-                            let tiles_z = get_int("tiles_z")?;
-
-                            PlaneBuilder::new()
-                                .with_size(width, height)
-                                .with_tiles(tiles_x, tiles_z)
-                                .build(graphics_clone, PROTO_TEXTURE, Some(&asset_name))
-                                .await
+                            anyhow::bail!("Unable to load plane: Not supported anymore, rebuilding it");
+                            // let get_float = |key: &str| -> anyhow::Result<f32> {
+                            //     let val = properties
+                            //         .custom_properties
+                            //         .iter()
+                            //         .find(|p| p.key == key)
+                            //         .ok_or_else(|| {
+                            //             anyhow::anyhow!("Entity has no {} property", key)
+                            //         })?;
+                            //     match val.value {
+                            //         Value::Float(f) => Ok(f as f32),
+                            //         _ => Err(anyhow::anyhow!("{} is not a float", key)),
+                            //     }
+                            // };
+                            // 
+                            // let get_int = |key: &str| -> anyhow::Result<u32> {
+                            //     let val = properties
+                            //         .custom_properties
+                            //         .iter()
+                            //         .find(|p| p.key == key)
+                            //         .ok_or_else(|| {
+                            //             anyhow::anyhow!("Entity has no {} property", key)
+                            //         })?;
+                            //     match val.value {
+                            //         Value::Int(i) => Ok(i as u32),
+                            //         _ => Err(anyhow::anyhow!("{} is not an int", key)),
+                            //     }
+                            // };
+                            // 
+                            // let width = get_float("width")?;
+                            // let height = get_float("height")?;
+                            // let tiles_x = get_int("tiles_x")?;
+                            // let tiles_z = get_int("tiles_z")?;
+                            // 
+                            // PlaneBuilder::new()
+                            //     .with_size(width, height)
+                            //     .with_tiles(tiles_x, tiles_z)
+                            //     .build(graphics_clone, PROTO_TEXTURE, Some(&asset_name))
+                            //     .await
+                        }
+                        ResourceReferenceType::Cube => {
+                            Model::load_from_memory(graphics_clone, include_bytes!("../../resources/models/cube.glb"), Some(&asset_name)).await.map(MeshRenderer::from_handle)
                         }
                     }
                 };
diff --git a/src/commonMain/kotlin/com/dropbear/EntityRef.kt b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
index 29f78c1..0374e1e 100644
--- a/src/commonMain/kotlin/com/dropbear/EntityRef.kt
+++ b/src/commonMain/kotlin/com/dropbear/EntityRef.kt
@@ -23,16 +23,16 @@ class EntityRef(val id: EntityId = EntityId(0L)) {
     }
 
     /**
-     * Fetches the transform component for the entity.
+     * Fetches the [EntityTransform] component for the entity.
      */
-    fun getTransform(): Transform? {
+    fun getTransform(): EntityTransform? {
         return engine.native.getTransform(id)
     }
 
     /**
-     * Sets and replaces the transform component for the entity.
+     * Sets and replaces the [EntityTransform] component for the entity.
      */
-    fun setTransform(transform: Transform?) {
+    fun setTransform(transform: EntityTransform?) {
         if (transform == null) return
         return engine.native.setTransform(id, transform)
     }
diff --git a/src/commonMain/kotlin/com/dropbear/EntityTransform.kt b/src/commonMain/kotlin/com/dropbear/EntityTransform.kt
new file mode 100644
index 0000000..1464d1f
--- /dev/null
+++ b/src/commonMain/kotlin/com/dropbear/EntityTransform.kt
@@ -0,0 +1,23 @@
+package com.dropbear
+
+import com.dropbear.math.Transform
+
+/**
+ * A component that contains the local and world transforms of an entity.
+ */
+class EntityTransform(var local: Transform, var world: Transform) {
+
+    /**
+     * Walks up the hierarchy to find the transform of the parent, then multiply to create a propagated [Transform].
+     */
+    fun propagate(): Transform? {
+        return null
+    }
+
+    /**
+     * Sets the local and world transforms to the engine.
+     */
+    fun set() {
+
+    }
+}
\ No newline at end of file
diff --git a/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt b/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
index f673304..8161089 100644
--- a/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
+++ b/src/commonMain/kotlin/com/dropbear/ffi/NativeEngine.kt
@@ -2,6 +2,7 @@ package com.dropbear.ffi
 
 import com.dropbear.Camera
 import com.dropbear.EntityId
+import com.dropbear.EntityTransform
 import com.dropbear.asset.AssetHandle
 import com.dropbear.asset.ModelHandle
 import com.dropbear.asset.TextureHandle
@@ -36,8 +37,8 @@ expect class NativeEngine {
     fun getAttachedCamera(entityId: EntityId): Camera?
     fun setCamera(camera: Camera);
 
-    fun getTransform(entityId: EntityId): Transform?
-    fun setTransform(entityId: EntityId, transform: Transform)
+    fun getTransform(entityId: EntityId): EntityTransform?
+    fun setTransform(entityId: EntityId, transform: EntityTransform)
 
     // ------------------------ MODEL PROPERTIES -------------------------
 
diff --git a/src/jvmMain/java/com/dropbear/ffi/JNINative.java b/src/jvmMain/java/com/dropbear/ffi/JNINative.java
index 770f777..7b69756 100644
--- a/src/jvmMain/java/com/dropbear/ffi/JNINative.java
+++ b/src/jvmMain/java/com/dropbear/ffi/JNINative.java
@@ -1,7 +1,7 @@
 package com.dropbear.ffi;
 
 import com.dropbear.Camera;
-import com.dropbear.math.Transform;
+import com.dropbear.EntityTransform;
 
 /**
  * Describes all the functions that are available in
@@ -36,8 +36,8 @@ public class JNINative {
     public static native void setCamera(long worldHandle, Camera camera);
 
     // transformations
-    public static native Transform getTransform(long handle, long entityHandle);
-    public static native void setTransform(long worldHandle, long id, Transform transform);
+    public static native EntityTransform getTransform(long handle, long entityHandle);
+    public static native void setTransform(long worldHandle, long id, EntityTransform transform);
 
     // properties
     public static native String getStringProperty(long worldHandle, long entityHandle, String label);
diff --git a/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt b/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
index 65da21f..341e8a9 100644
--- a/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
+++ b/src/jvmMain/kotlin/com/dropbear/ffi/NativeEngine.jvm.kt
@@ -3,6 +3,7 @@ package com.dropbear.ffi
 import com.dropbear.Camera
 import com.dropbear.DropbearEngine
 import com.dropbear.EntityId
+import com.dropbear.EntityTransform
 import com.dropbear.asset.TextureHandle
 import com.dropbear.exception.DropbearNativeException
 import com.dropbear.exceptionOnError
@@ -76,11 +77,11 @@ actual class NativeEngine {
     }
 
 
-    actual fun getTransform(entityId: EntityId): Transform? {
+    actual fun getTransform(entityId: EntityId): EntityTransform? {
         return JNINative.getTransform(worldHandle, entityId.id)
     }
 
-    actual fun setTransform(entityId: EntityId, transform: Transform) {
+    actual fun setTransform(entityId: EntityId, transform: EntityTransform) {
         return JNINative.setTransform(worldHandle, entityId.id, transform)
     }
 
diff --git a/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt b/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
index 2354fd5..906f9a2 100644
--- a/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
+++ b/src/nativeMain/kotlin/com/dropbear/ffi/NativeEngine.native.kt
@@ -9,6 +9,7 @@ package com.dropbear.ffi
 
 import com.dropbear.Camera
 import com.dropbear.EntityId
+import com.dropbear.EntityTransform
 import com.dropbear.asset.TextureHandle
 import com.dropbear.exception.DropbearNativeException
 import com.dropbear.exceptionOnError
@@ -61,7 +62,7 @@ actual class NativeEngine {
         }
     }
 
-    actual fun getTransform(entityId: EntityId): Transform? {
+    actual fun getTransform(entityId: EntityId): EntityTransform? {
         val world = worldHandle ?: return null
         memScoped {
             val outTransform = alloc<NativeTransform>()
@@ -70,54 +71,57 @@ actual class NativeEngine {
                 entity_id = entityId.id,
                 out_transform = outTransform.ptr
             )
-            if (result == 0) {
-                return Transform(
-                    position = com.dropbear.math.Vector3D(
-                        outTransform.position_x,
-                        outTransform.position_y,
-                        outTransform.position_z
-                    ),
-                    rotation = com.dropbear.math.QuaternionD(
-                        outTransform.rotation_x,
-                        outTransform.rotation_y,
-                        outTransform.rotation_z,
-                        outTransform.rotation_w
-                    ),
-                    scale = com.dropbear.math.Vector3D(
-                        outTransform.scale_x,
-                        outTransform.scale_y,
-                        outTransform.scale_z
-                    )
-                )
-            } else {
-                return null
-            }
-        }
-    }
-
-    actual fun setTransform(entityId: EntityId, transform: Transform) {
+//            if (result == 0) {
+//                return Transform(
+//                    position = com.dropbear.math.Vector3D(
+//                        outTransform.position_x,
+//                        outTransform.position_y,
+//                        outTransform.position_z
+//                    ),
+//                    rotation = com.dropbear.math.QuaternionD(
+//                        outTransform.rotation_x,
+//                        outTransform.rotation_y,
+//                        outTransform.rotation_z,
+//                        outTransform.rotation_w
+//                    ),
+//                    scale = com.dropbear.math.Vector3D(
+//                        outTransform.scale_x,
+//                        outTransform.scale_y,
+//                        outTransform.scale_z
+//                    )
+//                )
+//            } else {
+//                return null
+//            }
+            println("Function com.dropbear.ffi.NativeEngine.native.getTransform() not completed yet, will return NULL")
+            return null
+        }
+    }
+
+    actual fun setTransform(entityId: EntityId, transform: EntityTransform) {
         val world = worldHandle ?: return
         memScoped {
-            val nativeTransform = cValue<NativeTransform> {
-                position_x = transform.position.x
-                position_y = transform.position.y
-                position_z = transform.position.z
-
-                rotation_w = transform.rotation.w
-                rotation_x = transform.rotation.x
-                rotation_y = transform.rotation.y
-                rotation_z = transform.rotation.z
-
-                scale_x = transform.scale.x
-                scale_y = transform.scale.y
-                scale_z = transform.scale.z
-            }
-
-            dropbear_set_transform(
-                world_ptr = world.reinterpret(),
-                entity_id = entityId.id,
-                transform = nativeTransform
-            )
+//            val nativeTransform = cValue<NativeTransform> {
+//                position_x = transform.position.x
+//                position_y = transform.position.y
+//                position_z = transform.position.z
+//
+//                rotation_w = transform.rotation.w
+//                rotation_x = transform.rotation.x
+//                rotation_y = transform.rotation.y
+//                rotation_z = transform.rotation.z
+//
+//                scale_x = transform.scale.x
+//                scale_y = transform.scale.y
+//                scale_z = transform.scale.z
+//            }
+//
+//            dropbear_set_transform(
+//                world_ptr = world.reinterpret(),
+//                entity_id = entityId.id,
+//                transform = nativeTransform
+//            )
+            println("Function com.dropbear.ffi.NativeEngine.native.setTransform() not completed yet, will not do any action")
         }
     }
 
diff --git a/src/nativeMain/kotlin/com/dropbear/math/note.txt b/src/nativeMain/kotlin/com/dropbear/math/note.txt
deleted file mode 100644
index 3483dbe..0000000
--- a/src/nativeMain/kotlin/com/dropbear/math/note.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-this folder is just so i can easily add any future packages easily in intellij without all the folders
-being clumped up. 
\ No newline at end of file