kitgit

tirbofish/dropbear · commit

f4117cebc420f094dec152df0375f0075d3e1f48

wip: made a way more neater component registry that actually doesnt suck ass (so far). i cbb to do the rest tho.

Unverified

Thribhu K <4tkbytes@pm.me> · 2026-02-16 13:26

view full diff

diff --git a/Cargo.toml b/Cargo.toml
index cc56cb1..1e5e384 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -86,6 +86,7 @@ puffin = "0.19"
 bitflags = "2.10"
 features = "0.10"
 puffin_http = "0.16"
+dyn-clone = "1.0"
 
 [workspace.dependencies.image]
 version = "0.24"
diff --git a/crates/dropbear-engine/Cargo.toml b/crates/dropbear-engine/Cargo.toml
index c59f954..d3b0e2d 100644
--- a/crates/dropbear-engine/Cargo.toml
+++ b/crates/dropbear-engine/Cargo.toml
@@ -10,7 +10,6 @@ readme.workspace = true
 
 [dependencies]
 dropbear_future-queue = { path = "../dropbear_future-queue" }
-dropbear-traits = { path = "../dropbear-traits" }
 dropbear-macro = { path = "../dropbear-macro" }
 slank = { path = "../slank", features = ["download-slang", "use-wgpu"] }
 
diff --git a/crates/dropbear-engine/src/animation.rs b/crates/dropbear-engine/src/animation.rs
index 0a99650..2177c1f 100644
--- a/crates/dropbear-engine/src/animation.rs
+++ b/crates/dropbear-engine/src/animation.rs
@@ -1,14 +1,9 @@
-use dropbear_traits::{Component, ComponentDescriptor, ComponentInitContext, ComponentInitFuture, ComponentUpdateContext, InsertBundle, SerializableComponent};
 use std::collections::HashMap;
 use std::sync::Arc;
-use egui::{CollapsingHeader, Ui};
 use glam::Mat4;
 use wgpu::util::DeviceExt;
-use crate::asset::ASSET_REGISTRY;
-use crate::entity::MeshRenderer;
 use crate::graphics::SharedGraphicsContext;
 use crate::model::{AnimationInterpolation, ChannelValues, Model, NodeTransform};
-use std::any::Any;
 
 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
 pub struct AnimationComponent {
@@ -34,72 +29,6 @@ pub struct AnimationComponent {
     pub bind_group: Option<wgpu::BindGroup>,
 }
 
-impl Component for AnimationComponent {
-    type Serialized = AnimationComponent;
-
-    fn static_descriptor() -> ComponentDescriptor {
-        ComponentDescriptor {
-            fqtn: "dropbear_engine::animation::AnimationComponent".to_string(),
-            type_name: "AnimationComponent".to_string(),
-            category: Some("Animation".to_string()),
-            description: Some("A component that allows playback of a component".to_string()),
-        }
-    }
-
-    fn deserialize(serialized: &Self::Serialized) -> Self {
-        serialized.clone()
-    }
-
-    fn serialize(&self) -> Self::Serialized {
-        self.clone()
-    }
-
-    fn inspect(&mut self, ui: &mut Ui) {
-        CollapsingHeader::new("Animation").default_open(true).show(ui, |ui| {
-            ui.label("Active animation:");
-            let mut enabled = self.active_animation_index.is_some();
-            let mut value = self.active_animation_index.unwrap_or(0);
-
-            ui.horizontal(|ui| {
-                if ui.checkbox(&mut enabled, "Enable").changed() {
-                    self.active_animation_index = if enabled { Some(value) } else { None };
-                }
-
-                ui.add_enabled(enabled, egui::DragValue::new(&mut value));
-
-                if enabled && self.active_animation_index != Some(value) {
-                    self.active_animation_index = Some(value);
-                }
-            });
-
-            // todo: complete some more
-        });
-    }
-
-    fn update(&mut self, ctx: &mut ComponentUpdateContext) {
-        let world = ctx.world();
-        let Ok(renderer) = world.get::<&MeshRenderer>(ctx.entity) else {
-            return;
-        };
-
-        let handle = renderer.model();
-        if handle.is_null() {
-            return;
-        }
-
-        let registry = ASSET_REGISTRY.read();
-        let Some(model) = registry.get_model(handle) else {
-            return;
-        };
-
-        self.update(ctx.dt, model);
-
-        if let Some(graphics) = ctx.resources.get::<Arc<SharedGraphicsContext>>() {
-            self.prepare_gpu_resources(graphics.clone());
-        }
-    }
-}
-
 impl Default for AnimationComponent {
     fn default() -> Self {
         Self {
@@ -116,26 +45,6 @@ impl Default for AnimationComponent {
     }
 }
 
-#[typetag::serde]
-impl SerializableComponent for AnimationComponent {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
-        Box::new(self.clone())
-    }
-
-    fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = self.clone();
-        Box::pin(async move {
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((value,)));
-            Ok(insert)
-        })
-    }
-}
-
 impl AnimationComponent {
     pub fn new() -> Self {
         Self::default()
diff --git a/crates/dropbear-engine/src/asset.rs b/crates/dropbear-engine/src/asset.rs
index bd7821a..5872436 100644
--- a/crates/dropbear-engine/src/asset.rs
+++ b/crates/dropbear-engine/src/asset.rs
@@ -40,6 +40,9 @@ impl<T> Clone for Handle<T> {
 impl<T> Handle<T> {
     /// Creates a null handle, for when there is no way to uniquely identify a hash (such as a viewport texture).
     ///
+    /// A NULL model would be a model with no vertices or any properties, just a husk.
+    /// A NULL texture would throw an error, as it is not possible.
+    ///
     /// # Safety
     /// You will want to watch out, as adding this onto the asset registry with a type
     /// where there already is a null handle item, it will be overwritten and data
@@ -77,12 +80,24 @@ pub enum AssetKind {
 /// Common
 impl AssetRegistry {
     pub fn new() -> Self {
-        Self {
+        let mut result = Self {
             textures: Default::default(),
             texture_labels: Default::default(),
             models: Default::default(),
             model_labels: Default::default(),
-        }
+        };
+
+        result.add_model(Model {
+            hash: 0,
+            label: "null".to_string(),
+            path: Default::default(),
+            meshes: vec![],
+            materials: vec![],
+            skins: vec![],
+            animations: vec![],
+            nodes: vec![],
+        });
+        result
     }
 
     /// A convenient helper function for hashing a byte slice of data.
diff --git a/crates/dropbear-engine/src/entity.rs b/crates/dropbear-engine/src/entity.rs
index b1d6af4..a8422cf 100644
--- a/crates/dropbear-engine/src/entity.rs
+++ b/crates/dropbear-engine/src/entity.rs
@@ -16,9 +16,9 @@ use crate::{
 };
 use anyhow::anyhow;
 use egui::{CollapsingHeader, Ui};
-use dropbear_traits::{Component, ComponentDescriptor, ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent};
 use std::any::Any;
 use crate::asset::Handle;
+use crate::model::Material;
 
 /// 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)]
@@ -78,71 +78,6 @@ impl EntityTransform {
     }
 }
 
-impl Component for EntityTransform {
-    type Serialized = EntityTransform;
-
-    fn static_descriptor() -> ComponentDescriptor {
-        ComponentDescriptor {
-            fqtn: "dropbear_engine::entity::EntityTransform".to_string(),
-            type_name: "EntityTransform".to_string(),
-            category: Some("Transform".to_string()),
-            description: Some("A component that allows the entity to be transformed both locally and globally".to_string()),
-        }
-    }
-
-    fn deserialize(serialized: &Self::Serialized) -> Self {
-        serialized.clone()
-    }
-
-    fn serialize(&self) -> Self::Serialized {
-        self.clone()
-    }
-
-    fn inspect(&mut self, ui: &mut Ui) {
-        CollapsingHeader::new("Entity Transform")
-            .default_open(true)
-            .show(ui, |ui| {
-                ui.set_min_width(300.0);
-
-                ui.group(|ui| {
-                    ui.strong("Local Transform");
-                    ui.add_space(4.0);
-
-                    self.local.inspect(ui);
-                });
-
-                ui.add_space(8.0);
-
-                ui.group(|ui| {
-                    ui.strong("World Transform");
-                    ui.add_space(4.0);
-
-                    self.world.inspect(ui);
-                });
-            });
-    }
-}
-
-#[typetag::serde]
-impl SerializableComponent for EntityTransform {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
-        Box::new(*self)
-    }
-
-    fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = *self;
-        Box::pin(async move {
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((value,)));
-            Ok(insert)
-        })
-    }
-}
-
 /// A type that represents a position, rotation and scale of an entity
 ///
 /// This type is the most primitive model, as it implements most traits.
@@ -226,7 +161,7 @@ impl Transform {
         self.scale *= scale;
     }
 
-    fn inspect(&mut self, ui: &mut Ui) {
+    pub fn inspect(&mut self, ui: &mut Ui) {
         ui.horizontal(|ui| {
             ui.label("Position:");
         });
@@ -351,18 +286,29 @@ pub struct MeshRenderer {
     handle: Handle<Model>,
     pub instance: Instance,
     previous_matrix: DMat4,
-    texture_override: Option<Handle<Texture>>,
+    pub material_snapshot: HashMap<String, Material>
 }
 
 impl MeshRenderer {
     pub fn from_handle(model: Handle<Model>) -> Self {
+        let mut hm = HashMap::new();
+        let material_snapshot = ASSET_REGISTRY
+            .read()
+            .get_model(model)
+            .map(|m| {
+                m.materials.clone()
+            })
+            .unwrap_or_default();
+        for m in material_snapshot {
+            hm.insert(m.name.clone(), m);
+        }
         Self {
             handle: model,
             instance: Instance::default(),
             previous_matrix: DMat4::IDENTITY,
             import_scale: 1.0,
-            texture_override: None,
             is_selected: false,
+            material_snapshot: hm,
         }
     }
     
@@ -383,8 +329,8 @@ impl MeshRenderer {
             instance: Instance::default(),
             import_scale: 1.0,
             previous_matrix: DMat4::IDENTITY,
-            texture_override: None,
             is_selected: false,
+            material_snapshot: Default::default(),
         })
     }
 
@@ -417,15 +363,11 @@ impl MeshRenderer {
     pub fn model(&self) -> Handle<Model> {
         self.handle
     }
-
-    pub fn set_texture_override(&mut self, texture: Handle<Texture>) {
-        self.texture_override = Some(texture);
-    }
-
-    pub fn texture_override(&self) -> Option<Handle<Texture>> {
-        self.texture_override
+    
+    pub fn mutate_material(&mut self, material_name: &str, f: impl FnOnce(&mut Material)) {
+        self.material_snapshot.entry(material_name.to_string()).and_modify(f);
     }
-
+    
     pub fn is_texture_attached(&self, texture: Handle<Texture>) -> bool {
         let registry = ASSET_REGISTRY.read();
         
@@ -459,82 +401,18 @@ impl MeshRenderer {
     }
 
     pub fn reset_texture_override(&mut self) {
-        self.texture_override = None;
-    }
-}
-
-#[derive(Default, Debug, Clone, Serialize, Deserialize)]
-pub struct SerializedMeshRenderer {
-    pub handle: ResourceReference,
-    pub import_scale: Option<f32>,
-    pub texture_override: Option<ResourceReference>,
-}
-
-impl Component for MeshRenderer {
-    type Serialized = SerializedMeshRenderer;
-
-    fn static_descriptor() -> ComponentDescriptor {
-        ComponentDescriptor {
-            fqtn: "dropbear_engine::entity::MeshRenderer".to_string(),
-            type_name: "MeshRenderer".to_string(),
-            category: Some("Meshes".to_string()),
-            description: Some("Renders a 3D model".to_string()),
-        }
-    }
-
-    fn deserialize(serialized: &Self::Serialized) -> Self {
-        let handle = match serialized.handle.ref_type {
-            ResourceReferenceType::Unassigned { id } => Handle::new(id),
-            _ => Handle::NULL,
-        };
-
-        let mut renderer = MeshRenderer::from_handle(handle);
-        if let Some(scale) = serialized.import_scale {
-            renderer.set_import_scale(scale);
-        }
-
-        renderer
-    }
-
-    fn serialize(&self) -> Self::Serialized {
-        let handle = self.model();
-        let handle_ref = if handle.is_null() {
-            ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id })
-        } else {
-            let registry = ASSET_REGISTRY.read();
-            registry
-                .get_model(handle)
-                .map(|model| model.path.clone())
-                .unwrap_or_else(|| {
-                    ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id })
-                })
-        };
-
-        let texture_override = self.texture_override().map(|handle| {
-            let registry = ASSET_REGISTRY.read();
-            let label = registry.get_label_from_texture_handle(handle);
-            let reference = label.and_then(|value| {
-                if value.starts_with(EUCA_SCHEME) {
-                    Some(ResourceReference::from_reference(ResourceReferenceType::File(value)))
-                } else {
-                    None
-                }
-            });
-
-            reference.unwrap_or_else(|| {
-                ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id })
+        let mut hm = HashMap::new();
+        let material_snapshot = ASSET_REGISTRY
+            .read()
+            .get_model(self.handle)
+            .map(|m| {
+                m.materials.clone()
             })
-        });
-
-        SerializedMeshRenderer {
-            handle: handle_ref,
-            import_scale: Some(self.import_scale()),
-            texture_override,
+            .unwrap_or_default();
+        for m in material_snapshot {
+            hm.insert(m.name.clone(), m);
         }
-    }
-
-    fn inspect(&mut self, ui: &mut Ui) {
-        let _ = ui;
+        self.material_snapshot = hm;
     }
 }
 
diff --git a/crates/dropbear-engine/src/lib.rs b/crates/dropbear-engine/src/lib.rs
index 432307d..5841f21 100644
--- a/crates/dropbear-engine/src/lib.rs
+++ b/crates/dropbear-engine/src/lib.rs
@@ -21,7 +21,6 @@ pub mod mipmap;
 pub mod sky;
 pub mod features;
 pub mod animation;
-pub use dropbear_traits as component;
 
 features! {
     pub mod build {
diff --git a/crates/dropbear-engine/src/lighting.rs b/crates/dropbear-engine/src/lighting.rs
index aef3065..e970c5e 100644
--- a/crates/dropbear-engine/src/lighting.rs
+++ b/crates/dropbear-engine/src/lighting.rs
@@ -1,4 +1,4 @@
-use crate::attenuation::{Attenuation, RANGE_50, ATTENUATION_PRESETS};
+use crate::attenuation::{Attenuation, RANGE_50};
 use crate::buffer::{ResizableBuffer, UniformBuffer};
 use crate::graphics::SharedGraphicsContext;
 use crate::pipelines::light_cube::InstanceInput;
@@ -9,12 +9,9 @@ use crate::{
 use glam::{DMat4, DVec3};
 use std::fmt::{Display, Formatter};
 use std::sync::Arc;
-use egui::{CollapsingHeader, Ui};
 use wgpu::{BindGroup};
-use dropbear_traits::{Component, ComponentDescriptor};
 use crate::asset::{Handle, ASSET_REGISTRY};
-use crate::model::Material;
-use crate::procedural::{ProcObj, ProcedurallyGeneratedObject};
+use crate::procedural::{ProcedurallyGeneratedObject};
 
 pub const MAX_LIGHTS: usize = 10;
 
@@ -178,112 +175,6 @@ impl Default for LightComponent {
     }
 }
 
-impl Component for LightComponent {
-    type Serialized = LightComponent;
-
-    fn static_descriptor() -> ComponentDescriptor {
-        ComponentDescriptor {
-            fqtn: "dropbear_engine::lighting::LightComponent".to_string(),
-            type_name: "LightComponent".to_string(),
-            category: Some("Lighting".to_string()),
-            description: Some("Light parameters used by the renderer".to_string()),
-        }
-    }
-
-    fn deserialize(serialized: &Self::Serialized) -> Self {
-        serialized.clone()
-    }
-
-    fn serialize(&self) -> Self::Serialized {
-        self.clone()
-    }
-
-    fn inspect(&mut self, ui: &mut Ui) {
-        CollapsingHeader::new("Light")
-            .default_open(true)
-            .show(ui, |ui| {
-                egui::ComboBox::from_id_salt("light_type")
-                    .selected_text(self.light_type.to_string())
-                    .show_ui(ui, |ui| {
-                        ui.selectable_value(
-                            &mut self.light_type,
-                            LightType::Directional,
-                            "Directional",
-                        );
-                        ui.selectable_value(&mut self.light_type, LightType::Point, "Point");
-                        ui.selectable_value(&mut self.light_type, LightType::Spot, "Spot");
-                    });
-
-                ui.separator();
-
-                let mut colour = self.colour.as_vec3().to_array();
-                ui.horizontal(|ui| {
-                    ui.label("Colour");
-                    egui::color_picker::color_edit_button_rgb(ui, &mut colour);
-                });
-                self.colour = glam::Vec3::from_array(colour).as_dvec3();
-
-                ui.horizontal(|ui| {
-                    ui.label("Intensity");
-                    ui.add(egui::Slider::new(&mut self.intensity, 0.0..=10.0));
-                });
-
-                ui.horizontal(|ui| {
-                    ui.checkbox(&mut self.enabled, "Enabled");
-                    ui.checkbox(&mut self.visible, "Visible");
-                });
-
-                let is_point = matches!(self.light_type, LightType::Point);
-                let is_spot = matches!(self.light_type, LightType::Spot);
-
-                if is_point || is_spot {
-                    ui.separator();
-                    egui::ComboBox::from_id_salt("attenuation_range")
-                        .selected_text(format!("Range {}", self.attenuation.range))
-                        .show_ui(ui, |ui| {
-                            for (preset, label) in ATTENUATION_PRESETS {
-                                ui.selectable_value(&mut self.attenuation, *preset, *label);
-                            }
-                        });
-                }
-
-                if is_spot {
-                    ui.separator();
-                    ui.add(
-                        egui::Slider::new(&mut self.cutoff_angle, 1.0..=89.0)
-                            .text("Inner")
-                            .suffix("\u{00B0}")
-                            .step_by(0.1),
-                    );
-                    ui.add(
-                        egui::Slider::new(&mut self.outer_cutoff_angle, 1.0..=90.0)
-                            .text("Outer")
-                            .suffix("\u{00B0}")
-                            .step_by(0.1),
-                    );
-
-                    if self.outer_cutoff_angle <= self.cutoff_angle {
-                        self.outer_cutoff_angle = self.cutoff_angle + 1.0;
-                    }
-                }
-
-                ui.separator();
-                ui.label("Shadows");
-                ui.checkbox(&mut self.cast_shadows, "Cast Shadows");
-                ui.horizontal(|ui| {
-                    ui.label("Depth");
-                    ui.add(egui::DragValue::new(&mut self.depth.start).speed(0.1));
-                    ui.label("..");
-                    ui.add(egui::DragValue::new(&mut self.depth.end).speed(0.1));
-                });
-
-                if self.depth.end < self.depth.start {
-                    self.depth.end = self.depth.start;
-                }
-            });
-    }
-}
-
 impl LightComponent {
     pub fn default_direction() -> DVec3 {
         let dir = DVec3::new(-0.35, -1.0, -0.25);
@@ -402,7 +293,7 @@ impl Light {
             .build_model(
                 graphics.clone(),
                 None,
-                None,
+                Some("light cube"),
                 ASSET_REGISTRY.clone()
             );
 
diff --git a/crates/dropbear-engine/src/model.rs b/crates/dropbear-engine/src/model.rs
index 3cb8897..6e3d3de 100644
--- a/crates/dropbear-engine/src/model.rs
+++ b/crates/dropbear-engine/src/model.rs
@@ -52,7 +52,7 @@ pub struct Material {
     pub emissive_factor: [f32; 3],
     pub metallic_factor: f32,
     pub roughness_factor: f32,
-    pub alpha_mode: gltf::material::AlphaMode,
+    pub alpha_mode: AlphaMode,
     pub alpha_cutoff: Option<f32>,
     pub double_sided: bool,
     pub occlusion_strength: f32,
@@ -66,6 +66,24 @@ pub struct Material {
     pub occlusion_texture: Option<Texture>,
 }
 
+#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize, Default)]
+pub enum AlphaMode {
+    #[default]
+    Opaque = 1,
+    Mask,
+    Blend,
+}
+
+impl Into<AlphaMode> for gltf::material::AlphaMode {
+    fn into(self) -> AlphaMode {
+        match self {
+            gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
+            gltf::material::AlphaMode::Mask => AlphaMode::Mask,
+            gltf::material::AlphaMode::Blend => AlphaMode::Blend,
+        }
+    }
+}
+
 /// Represents a node in the scene graph (can be a joint/bone or a mesh)
 #[derive(Clone, Debug)]
 pub struct Node {
@@ -177,7 +195,7 @@ impl Material {
             emissive_factor: [0.0, 0.0, 0.0],
             metallic_factor: 1.0,
             roughness_factor: 1.0,
-            alpha_mode: gltf::material::AlphaMode::Opaque,
+            alpha_mode: AlphaMode::Opaque,
             alpha_cutoff: None,
             double_sided: false,
             occlusion_strength: 1.0,
@@ -1014,7 +1032,7 @@ impl Model {
             material.emissive_factor = processed.emissive_factor;
             material.metallic_factor = processed.metallic_factor;
             material.roughness_factor = processed.roughness_factor;
-            material.alpha_mode = processed.alpha_mode;
+            material.alpha_mode = processed.alpha_mode.into();
             material.alpha_cutoff = processed.alpha_cutoff;
             material.double_sided = processed.double_sided;
             material.occlusion_strength = processed.occlusion_strength;
diff --git a/crates/dropbear-engine/src/texture.rs b/crates/dropbear-engine/src/texture.rs
index 76551f9..4d36ed9 100644
--- a/crates/dropbear-engine/src/texture.rs
+++ b/crates/dropbear-engine/src/texture.rs
@@ -4,7 +4,7 @@ use image::GenericImageView;
 use serde::{Deserialize, Serialize};
 use crate::asset::AssetRegistry;
 use crate::graphics::SharedGraphicsContext;
-use crate::utils::ToPotentialString;
+use crate::utils::{ResourceReference, ToPotentialString};
 
 /// As defined in `shaders.wgsl` as
 /// ```
@@ -69,6 +69,7 @@ pub struct Texture {
     pub size: wgpu::Extent3d,
     pub view: wgpu::TextureView,
     pub hash: Option<u64>,
+    pub reference: Option<ResourceReference>
 }
 
 impl Texture {
@@ -141,6 +142,7 @@ impl Texture {
             sampler,
             size,
             hash: None,
+            reference: None,
         }
     }
 
@@ -190,6 +192,7 @@ impl Texture {
             view,
             label: label.to_potential_string(),
             hash: None,
+            reference: None,
         }
     }
 
@@ -230,6 +233,7 @@ impl Texture {
             size,
             view,
             hash: None,
+            reference: None,
         }
     }
 
@@ -241,7 +245,9 @@ impl Texture {
     ) -> anyhow::Result<Self> {
         puffin::profile_function!(label.unwrap_or(""));
         let data = fs::read(path)?;
-        Ok(Self::from_bytes(graphics.clone(), &data, label))
+        let mut result = Self::from_bytes(graphics.clone(), &data, label);
+        result.reference = Some(ResourceReference::from_path(path)?);
+        Ok(result)
     }
 
     /// Loads the texture from bytes.
@@ -436,6 +442,7 @@ impl Texture {
             size,
             view,
             hash: Some(hash),
+            reference: Some(ResourceReference::from_bytes(bytes)),
         }
     }
 
diff --git a/crates/dropbear-engine/src/utils.rs b/crates/dropbear-engine/src/utils.rs
index 8a5acb9..d5558b1 100644
--- a/crates/dropbear-engine/src/utils.rs
+++ b/crates/dropbear-engine/src/utils.rs
@@ -94,12 +94,6 @@ pub enum ResourceReferenceType {
     /// Typically creates errors, so watch out!
     None,
 
-    /// A stable placeholder reference that represents an intentional "no model selected" state.
-    ///
-    /// This is distinct from [`ResourceReferenceType::None`] so it can be serialized and
-    /// round-tripped without being treated as an error, while still being unique per instance.
-    Unassigned { id: u64 },
-
     /// A file type. The [`String`] is the reference from the project or the runtime executable.
     File(String),
 
diff --git a/crates/dropbear-traits/Cargo.toml b/crates/dropbear-traits/Cargo.toml
deleted file mode 100644
index bbd0fdd..0000000
--- a/crates/dropbear-traits/Cargo.toml
+++ /dev/null
@@ -1,14 +0,0 @@
-[package]
-name = "dropbear-traits"
-version.workspace = true
-edition.workspace = true
-license.workspace = true
-repository.workspace = true
-readme = "README.md"
-
-[dependencies]
-serde.workspace = true
-egui.workspace = true
-anyhow.workspace = true
-hecs.workspace = true
-typetag.workspace = true
\ No newline at end of file
diff --git a/crates/dropbear-traits/README.md b/crates/dropbear-traits/README.md
deleted file mode 100644
index 018c5f7..0000000
--- a/crates/dropbear-traits/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# dropbear-traits
-
-Contains all the traits that other crates would use. 
\ No newline at end of file
diff --git a/crates/dropbear-traits/src/lib.rs b/crates/dropbear-traits/src/lib.rs
deleted file mode 100644
index ec5af64..0000000
--- a/crates/dropbear-traits/src/lib.rs
+++ /dev/null
@@ -1,156 +0,0 @@
-use std::any::{Any, TypeId};
-use std::collections::HashMap;
-use std::future::Future;
-use std::pin::Pin;
-use hecs::{Entity, World, DynamicBundle};
-use std::sync::Arc;
-use serde::{Deserialize, Serialize};
-
-pub struct ComponentDescriptor {
-    pub fqtn: String,
-    pub type_name: String,
-    pub category: Option<String>,
-    pub description: Option<String>,
-}
-
-pub struct ComponentRepository {
-    components: HashMap<TypeId, ComponentDescriptor>,
-}
-
-impl ComponentRepository {
-    pub fn new() -> Self {
-        Self {
-            components: Default::default(),
-        }
-    }
-
-    pub fn register<T: Component + 'static>(&mut self) {
-        let type_id = TypeId::of::<T>();
-
-        let descriptor = T::static_descriptor();
-
-        self.components.insert(type_id, descriptor);
-    }
-
-    pub fn get_descriptor<T: Component + 'static>(&self) -> Option<&ComponentDescriptor> {
-        self.components.get(&TypeId::of::<T>())
-    }
-
-    pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &ComponentDescriptor)> {
-        self.components.iter()
-    }
-
-    pub fn descriptors(&self) -> impl Iterator<Item = &ComponentDescriptor> {
-        self.components.values()
-    }
-}
-
-pub trait Component {
-    type Serialized: Serialize + for<'de> Deserialize<'de>;
-
-    fn static_descriptor() -> ComponentDescriptor;
-
-    fn deserialize(serialized: &Self::Serialized) -> Self;
-    fn serialize(&self) -> Self::Serialized;
-    fn inspect(&mut self, ui: &mut egui::Ui);
-    fn update(&mut self, _ctx: &mut ComponentUpdateContext) {}
-}
-
-pub mod registry;
-
-pub struct ComponentResources {
-    map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
-}
-
-pub struct ComponentUpdateContext {
-    pub entity: Entity,
-    pub dt: f32,
-    pub resources: Arc<ComponentResources>,
-    world_ptr: *const World,
-}
-
-impl ComponentUpdateContext {
-    pub fn world(&self) -> &World {
-        unsafe { &*self.world_ptr }
-    }
-
-    pub fn new(entity: Entity, dt: f32, resources: Arc<ComponentResources>, world: &World) -> Self {
-        Self {
-            entity,
-            dt,
-            resources,
-            world_ptr: world as *const World,
-        }
-    }
-}
-
-impl ComponentResources {
-    pub fn new() -> Self {
-        Self {
-            map: HashMap::new(),
-        }
-    }
-
-    pub fn insert<T: Any + Send + Sync>(&mut self, value: T) {
-        self.map.insert(TypeId::of::<T>(), Box::new(value));
-    }
-
-    pub fn get<T: Any + Send + Sync>(&self) -> Option<&T> {
-        self.map
-            .get(&TypeId::of::<T>())
-            .and_then(|boxed| boxed.downcast_ref::<T>())
-    }
-
-    pub fn get_mut<T: Any + Send + Sync>(&mut self) -> Option<&mut T> {
-        self.map
-            .get_mut(&TypeId::of::<T>())
-            .and_then(|boxed| boxed.downcast_mut::<T>())
-    }
-}
-
-#[derive(Clone)]
-pub struct ComponentInitContext {
-    pub entity: Entity,
-    pub resources: Arc<ComponentResources>,
-}
-
-pub type ComponentInitFuture = Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn ComponentInsert>>> + Send + 'static>>;
-
-pub trait ComponentInsert: Sync + Send {
-    fn insert(self: Box<Self>, world: &mut World, entity: Entity) -> anyhow::Result<()>;
-}
-
-pub struct InsertBundle<T: DynamicBundle + Send + 'static>(pub T);
-
-impl<T: DynamicBundle + Send + 'static + Sync> ComponentInsert for InsertBundle<T> {
-    fn insert(self: Box<Self>, world: &mut World, entity: Entity) -> anyhow::Result<()> {
-        world.insert(entity, self.0).map_err(|e| anyhow::anyhow!(e.to_string()))?;
-        Ok(())
-    }
-}
-
-#[typetag::serde(tag = "type")]
-pub trait SerializableComponent: Send + Sync {
-    fn as_any(&self) -> &dyn Any;
-    fn clone_box(&self) -> Box<dyn SerializableComponent>;
-
-    fn type_name(&self) -> &'static str {
-        std::any::type_name::<Self>()
-    }
-
-    fn display_name(&self) -> String {
-        self.type_name()
-            .split("::")
-            .last()
-            .unwrap_or(self.type_name())
-            .to_string()
-    }
-
-    fn init(&self, ctx: ComponentInitContext) -> ComponentInitFuture;
-}
-
-impl Clone for Box<dyn SerializableComponent> {
-    fn clone(&self) -> Self {
-        self.clone_box()
-    }
-}
\ No newline at end of file
diff --git a/crates/dropbear-traits/src/registry.rs b/crates/dropbear-traits/src/registry.rs
deleted file mode 100644
index da8e150..0000000
--- a/crates/dropbear-traits/src/registry.rs
+++ /dev/null
@@ -1,244 +0,0 @@
-use std::any::TypeId;
-use std::collections::HashMap;
-use std::sync::Arc;
-
-use egui::Ui;
-use hecs::{Entity, World};
-
-use crate::{Component, ComponentResources, ComponentUpdateContext, SerializableComponent};
-
-pub struct ComponentRegistry {
-    next_id: u32,
-    entries: HashMap<u32, ComponentEntry>,
-    type_to_id: HashMap<TypeId, u32>,
-    converters: Vec<ConverterEntry>,
-}
-
-struct ComponentEntry {
-    #[allow(dead_code)]
-    type_id: TypeId,
-    fqtn: &'static str,
-    display_name: String,
-    create_default: Option<fn() -> Box<dyn SerializableComponent>>,
-    extract_fn: Option<fn(&World, Entity) -> Option<Box<dyn SerializableComponent>>>,
-    remove_fn: Option<fn(&mut World, Entity)>,
-    inspect_fn: Option<fn(&mut World, Entity, &mut Ui) -> bool>,
-    update_fn: Option<fn(&mut World, f32, &Arc<ComponentResources>)>,
-}
-
-struct ConverterEntry {
-    from_type: TypeId,
-    convert_fn: Box<dyn Fn(&World, Entity) -> Option<Box<dyn SerializableComponent>> + Send + Sync>,
-}
-
-impl ComponentRegistry {
-    pub fn new() -> Self {
-        Self {
-            next_id: 1,
-            entries: HashMap::new(),
-            type_to_id: HashMap::new(),
-            converters: Vec::new(),
-        }
-    }
-
-    pub fn register_with_default<T>(&mut self)
-    where
-        T: SerializableComponent + Default + Clone + 'static,
-    {
-        self.register_entry::<T>(None, None);
-    }
-
-    pub fn register_with_default_component<T>(&mut self)
-    where
-        T: Component + SerializableComponent + Default + Clone + 'static,
-    {
-        let inspect_fn: fn(&mut World, Entity, &mut Ui) -> bool = |world, entity, ui| {
-            if let Ok(mut component) = world.get::<&mut T>(entity) {
-                component.inspect(ui);
-                true
-            } else {
-                false
-            }
-        };
-
-        let update_fn: fn(&mut World, f32, &Arc<ComponentResources>) =
-            |world, dt, resources| {
-                let world_ptr = world as *const World;
-                let mut query = world.query::<(Entity, &mut T)>();
-                for (entity, component) in query.iter() {
-                    let mut ctx = ComponentUpdateContext::new(
-                        entity,
-                        dt,
-                        resources.clone(),
-                        unsafe { &*world_ptr },
-                    );
-                    component.update(&mut ctx);
-                }
-            };
-
-        self.register_entry::<T>(Some(inspect_fn), Some(update_fn));
-    }
-
-    fn register_entry<T>(
-        &mut self,
-        inspect_fn: Option<fn(&mut World, Entity, &mut Ui) -> bool>,
-        update_fn: Option<fn(&mut World, f32, &Arc<ComponentResources>)>,
-    )
-    where
-        T: SerializableComponent + Default + Clone + 'static,
-    {
-        let type_id = TypeId::of::<T>();
-        if self.type_to_id.contains_key(&type_id) {
-            return;
-        }
-
-        let id = self.next_id;
-        self.next_id = self.next_id.saturating_add(1);
-
-        let entry = ComponentEntry {
-            type_id,
-            fqtn: std::any::type_name::<T>(),
-            display_name: short_name(std::any::type_name::<T>()),
-            create_default: Some(|| Box::new(T::default())),
-            extract_fn: Some(|world, entity| {
-                world
-                    .get::<&T>(entity)
-                    .ok()
-                    .map(|component| component.clone_box())
-            }),
-            remove_fn: Some(|world, entity| {
-                let _ = world.remove_one::<T>(entity);
-            }),
-            inspect_fn,
-            update_fn,
-        };
-
-        self.entries.insert(id, entry);
-        self.type_to_id.insert(type_id, id);
-    }
-
-    pub fn register_converter<From, To, F>(&mut self, converter: F)
-    where
-        From: Sync + Send + 'static,
-        To: SerializableComponent + 'static,
-        F: Fn(&World, Entity, &From) -> Option<To> + Sync + Send + 'static,
-    {
-        let from_id = TypeId::of::<From>();
-        let convert_fn = move |world: &World, entity: Entity| -> Option<Box<dyn SerializableComponent>> {
-            world
-                .get::<&From>(entity)
-                .ok()
-                .and_then(|component| converter(world, entity, &component))
-                .map(|converted| Box::new(converted) as Box<dyn SerializableComponent>)
-        };
-
-        self.converters.push(ConverterEntry {
-            from_type: from_id,
-            convert_fn: Box::new(convert_fn),
-        });
-    }
-
-    pub fn iter_available_components(&self) -> impl Iterator<Item = (u32, &str)> {
-        self.entries
-            .iter()
-            .map(|(id, entry)| (*id, entry.fqtn))
-    }
-
-    pub fn create_default_component(&self, id: u32) -> Option<Box<dyn SerializableComponent>> {
-        self.entries
-            .get(&id)
-            .and_then(|entry| entry.create_default.map(|ctor| ctor()))
-    }
-
-    pub fn id_for_component(&self, component: &dyn SerializableComponent) -> Option<u32> {
-        self.type_to_id.get(&component.as_any().type_id()).copied()
-    }
-
-    pub fn remove_component_by_id(&self, world: &mut World, entity: Entity, id: u32) {
-        if let Some(entry) = self.entries.get(&id) {
-            if let Some(remove_fn) = entry.remove_fn {
-                remove_fn(world, entity);
-            }
-        }
-    }
-
-    pub fn extract_all_components(
-        &self,
-        world: &World,
-        entity: Entity,
-    ) -> Vec<Box<dyn SerializableComponent>> {
-        let mut components = Vec::new();
-
-        for entry in self.entries.values() {
-            if let Some(extract_fn) = entry.extract_fn {
-                if let Some(component) = extract_fn(world, entity) {
-                    components.push(component);
-                }
-            }
-        }
-
-        for converter in &self.converters {
-            if let Some(component) = (converter.convert_fn)(world, entity) {
-                components.push(component);
-            }
-        }
-
-        components
-    }
-
-    pub fn inspect_components(&self, world: &mut World, entity: Entity, ui: &mut Ui) {
-        let mut ids: Vec<u32> = self.entries.keys().copied().collect();
-        ids.sort_unstable();
-
-        for id in ids {
-            let Some(entry) = self.entries.get(&id) else {
-                continue;
-            };
-
-            if let Some(inspect_fn) = entry.inspect_fn {
-                if inspect_fn(world, entity, ui) {
-                    ui.add_space(6.0);
-                }
-            }
-        }
-    }
-
-    pub fn update_components(
-        &self,
-        world: &mut World,
-        dt: f32,
-        resources: &Arc<ComponentResources>,
-    ) {
-        let mut ids: Vec<u32> = self.entries.keys().copied().collect();
-        ids.sort_unstable();
-
-        for id in ids {
-            let Some(entry) = self.entries.get(&id) else {
-                continue;
-            };
-
-            if let Some(update_fn) = entry.update_fn {
-                update_fn(world, dt, resources);
-            }
-        }
-    }
-    
-    pub fn find_components_by_numeric_id(
-        &self,
-        id: u64
-    ) -> Vec<(u32, &str)> {
-        self.entries
-            .iter()
-            .filter(|(_, entry)| entry.display_name.starts_with(&id.to_string()))
-            .map(|(id, entry)| (*id, entry.fqtn))
-            .collect()
-    }
-
-    pub fn iter(&self) -> impl Iterator<Item = (u32, &str)> {
-        self.iter_available_components()
-    }
-}
-
-fn short_name(fqtn: &str) -> String {
-    fqtn.split("::").last().unwrap_or(fqtn).to_string()
-}
diff --git a/crates/eucalyptus-core/Cargo.toml b/crates/eucalyptus-core/Cargo.toml
index 611bfb8..bf8ce15 100644
--- a/crates/eucalyptus-core/Cargo.toml
+++ b/crates/eucalyptus-core/Cargo.toml
@@ -10,7 +10,6 @@ readme = "README.md"
 crate-type = ["rlib", "cdylib"]
 
 [dependencies]
-dropbear-traits = { path = "../dropbear-traits" }
 dropbear-macro = { path = "../dropbear-macro" }
 magna-carta = { path = "../magna-carta" }
 
@@ -46,6 +45,7 @@ bytemuck.workspace = true
 #yakui.workspace = true
 thiserror.workspace = true
 combine.workspace = true
+dyn-clone.workspace = true
 
 [features]
 default = []
diff --git a/crates/eucalyptus-core/src/animation/mod.rs b/crates/eucalyptus-core/src/animation/mod.rs
new file mode 100644
index 0000000..0d8418b
--- /dev/null
+++ b/crates/eucalyptus-core/src/animation/mod.rs
@@ -0,0 +1,82 @@
+use std::sync::Arc;
+use egui::{CollapsingHeader, Ui};
+use hecs::{Entity, World};
+use dropbear_engine::animation::AnimationComponent;
+use dropbear_engine::asset::ASSET_REGISTRY;
+use dropbear_engine::entity::MeshRenderer;
+use dropbear_engine::graphics::SharedGraphicsContext;
+use crate::component::{Component, ComponentDescriptor, SerializedComponent};
+
+#[typetag::serde]
+impl SerializedComponent for AnimationComponent {}
+
+impl Component for AnimationComponent {
+    type SerializedForm = Self;
+
+    fn descriptor() -> ComponentDescriptor {
+        ComponentDescriptor {
+            fqtn: "dropbear_engine::animation::AnimationComponent".to_string(),
+            type_name: "AnimationComponent".to_string(),
+            category: Some("Animation".to_string()),
+            description: Some("Animates a 3D MeshRenderer".to_string()),
+        }
+    }
+
+    async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>
+    where
+        Self: Sized
+    {
+        Ok(Self::default())
+    }
+
+    async fn init(ser: Self::SerializedForm, _graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+        Ok(ser)
+    }
+
+    fn update_component(&mut self, world: &World, entity: Entity, dt: f32, graphics: Arc<SharedGraphicsContext>) {
+        let Ok(renderer) = world.get::<&MeshRenderer>(entity) else {
+            return;
+        };
+
+        let handle = renderer.model();
+        if handle.is_null() {
+            return;
+        }
+
+        let registry = ASSET_REGISTRY.read();
+        let Some(model) = registry.get_model(handle) else {
+            return;
+        };
+
+        self.update(dt, model);
+
+        self.prepare_gpu_resources(graphics.clone());
+    }
+
+    fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
+        Box::new(self.clone())
+    }
+
+    fn inspect(&mut self, ui: &mut Ui) {
+        CollapsingHeader::new("Animation").default_open(true).show(ui, |ui| {
+            ui.label("Active animation:");
+            let mut enabled = self.active_animation_index.is_some();
+            let mut value = self.active_animation_index.unwrap_or(0);
+
+            ui.horizontal(|ui| {
+                if ui.checkbox(&mut enabled, "Enable").changed() {
+                    self.active_animation_index = if enabled { Some(value) } else { None };
+                }
+
+                ui.add_enabled(enabled, egui::DragValue::new(&mut value));
+
+                if enabled && self.active_animation_index != Some(value) {
+                    self.active_animation_index = Some(value);
+                }
+            });
+
+            ui.label("Not implemented yet!");
+            // todo: complete some more
+        });
+    }
+}
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/camera.rs b/crates/eucalyptus-core/src/camera.rs
index ddd081d..0777ecd 100644
--- a/crates/eucalyptus-core/src/camera.rs
+++ b/crates/eucalyptus-core/src/camera.rs
@@ -1,10 +1,14 @@
 //! Additional information and context for cameras from the [`dropbear_engine::camera`]
 use crate::states::SerializableCamera;
 use dropbear_engine::camera::{Camera, CameraBuilder, CameraSettings};
-use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent};
 use glam::DVec3;
 use serde::{Deserialize, Serialize};
 use std::any::Any;
+use std::sync::Arc;
+use egui::{CollapsingHeader, Ui};
+use hecs::{Entity, World};
+use dropbear_engine::graphics::SharedGraphicsContext;
+use crate::component::{Component, ComponentDescriptor, SerializedComponent};
 use crate::ptr::WorldPtr;
 use crate::scripting::result::DropbearNativeResult;
 use crate::types::NVector3;
@@ -16,6 +20,53 @@ pub struct CameraComponent {
     pub starting_camera: bool,
 }
 
+#[typetag::serde]
+impl SerializedComponent for SerializableCamera {}
+
+impl Component for Camera {
+    type SerializedForm = SerializableCamera;
+
+    fn descriptor() -> ComponentDescriptor {
+        ComponentDescriptor {
+            fqtn: "dropbear_engine::camera::Camera".to_string(),
+            type_name: "Camera3D".to_string(),
+            category: Some("Camera".to_string()),
+            description: Some("Allows you to view the scene through the eyes of the component".to_string()),
+        }
+    }
+
+    async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>
+    where
+        Self: Sized
+    {
+        Ok(Camera::predetermined(graphics.clone(), None))
+    }
+
+    async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+        let label = ser.label.clone();
+        let builder = CameraBuilder::from(ser);
+        Ok(Camera::new(graphics.clone(), builder, Some(label.as_str())))
+    }
+
+    fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, graphics: Arc<SharedGraphicsContext>) {
+        self.update(graphics.clone())
+    }
+
+    fn save(&self, world: &World, entity: Entity) -> Box<dyn SerializedComponent> {
+        if let Ok((cam, comp)) = world.query_one::<(&Camera, &CameraComponent)>(entity).get() {
+            Box::new(SerializableCamera::from_ecs_camera(cam, comp))
+        } else {
+            Box::new(SerializableCamera::default())
+        }
+    }
+
+    fn inspect(&mut self, ui: &mut Ui) {
+        CollapsingHeader::new("Camera3D").show(ui, |ui| {
+            ui.label("Not implemented yet!"); 
+        });
+    }
+}
+
 impl Default for CameraComponent {
     fn default() -> Self {
         Self::new()
@@ -36,26 +87,6 @@ impl CameraComponent {
     }
 }
 
-#[typetag::serde]
-impl SerializableComponent for CameraComponent {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
-        Box::new(self.clone())
-    }
-
-    fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = self.clone();
-        Box::pin(async move {
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((value,)));
-            Ok(insert)
-        })
-    }
-}
-
 impl From<SerializableCamera> for CameraBuilder {
     fn from(value: SerializableCamera) -> Self {
         let forward = value.transform.rotation * DVec3::Z;
diff --git a/crates/eucalyptus-core/src/component.rs b/crates/eucalyptus-core/src/component.rs
new file mode 100644
index 0000000..e1b8ed4
--- /dev/null
+++ b/crates/eucalyptus-core/src/component.rs
@@ -0,0 +1,459 @@
+use std::any::TypeId;
+use std::collections::HashMap;
+use std::sync::Arc;
+use egui::{CollapsingHeader, Ui};
+use hecs::{Entity, World};
+use serde::{Deserialize, Serialize};
+use dropbear_engine::asset::{Handle, ASSET_REGISTRY};
+use dropbear_engine::entity::{EntityTransform, MeshRenderer};
+use dropbear_engine::graphics::SharedGraphicsContext;
+use dropbear_engine::model::{Model};
+use dropbear_engine::procedural::ProcedurallyGeneratedObject;
+use dropbear_engine::texture::Texture;
+use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
+use crate::hierarchy::EntityTransformExt;
+use crate::states::{SerializedMaterialCustomisation, SerializedMeshRenderer};
+use crate::utils::ResolveReference;
+
+pub use typetag::*;
+
+pub struct ComponentRegistry {
+    /// Maps TypeId to ComponentDescriptor for quick lookups
+    descriptors: HashMap<TypeId, ComponentDescriptor>,
+    /// Maps fully qualified type name to TypeId for lookups by string
+    fqtn_to_type: HashMap<String, TypeId>,
+    /// Maps category name to list of TypeIds in that category
+    categories: HashMap<String, Vec<TypeId>>,
+    /// Functions that extract and serialize components from entities
+    extractors: HashMap<TypeId, ExtractorFn>,
+}
+
+type ExtractorFn = Box<dyn Fn(&hecs::World, hecs::Entity) -> Option<Box<dyn SerializedComponent>> + Send + Sync>;
+
+impl ComponentRegistry {
+    pub fn new() -> Self {
+        Self {
+            descriptors: HashMap::new(),
+            fqtn_to_type: HashMap::new(),
+            categories: HashMap::new(),
+            extractors: Default::default(),
+        }
+    }
+
+    /// Register a component type with the registry
+    pub fn register<T: Component + 'static + Sync + Send>(&mut self) {
+        let type_id = TypeId::of::<T>();
+        let descriptor = T::descriptor();
+
+        self.fqtn_to_type.insert(descriptor.fqtn.clone(), type_id);
+
+        if let Some(ref category) = descriptor.category {
+            self.categories
+                .entry(category.clone())
+                .or_insert_with(Vec::new)
+                .push(type_id);
+        }
+
+        self.extractors.insert(
+            type_id,
+            Box::new(|world: &hecs::World, entity: hecs::Entity| {
+                world.get::<&T>(entity).ok().map(|component| {
+                    component.save(world, entity)
+                })
+            })
+        );
+
+        self.descriptors.insert(type_id, descriptor);
+    }
+
+    /// Get descriptor for a specific component type
+    pub fn get_descriptor<T: Component + 'static>(&self) -> Option<&ComponentDescriptor> {
+        self.descriptors.get(&TypeId::of::<T>())
+    }
+
+    /// Get descriptor by fully qualified type name
+    pub fn get_descriptor_by_fqtn(&self, fqtn: &str) -> Option<&ComponentDescriptor> {
+        self.fqtn_to_type
+            .get(fqtn)
+            .and_then(|type_id| self.descriptors.get(type_id))
+    }
+
+    /// Get all registered component descriptors
+    pub fn all_descriptors(&self) -> impl Iterator<Item = &ComponentDescriptor> {
+        self.descriptors.values()
+    }
+
+    /// Get all component descriptors in a specific category
+    pub fn descriptors_in_category(&self, category: &str) -> Vec<&ComponentDescriptor> {
+        self.categories
+            .get(category)
+            .map(|type_ids| {
+                type_ids
+                    .iter()
+                    .filter_map(|type_id| self.descriptors.get(type_id))
+                    .collect()
+            })
+            .unwrap_or_default()
+    }
+
+    /// Get all category names
+    pub fn categories(&self) -> impl Iterator<Item = &String> {
+        self.categories.keys()
+    }
+
+    /// Check if a component type is registered
+    pub fn is_registered<T: Component + 'static>(&self) -> bool {
+        self.descriptors.contains_key(&TypeId::of::<T>())
+    }
+
+    /// Get the TypeId for a component by its fully qualified type name
+    pub fn get_type_id(&self, fqtn: &str) -> Option<TypeId> {
+        self.fqtn_to_type.get(fqtn).copied()
+    }
+
+    /// Get count of registered components
+    pub fn count(&self) -> usize {
+        self.descriptors.len()
+    }
+
+    /// Extract all registered components from an entity
+    pub fn extract_all_components(
+        &self,
+        world: &hecs::World,
+        entity: hecs::Entity,
+    ) -> Vec<Box<dyn SerializedComponent>> {
+        self.extractors
+            .values()
+            .filter_map(|extractor| extractor(world, entity))
+            .collect()
+    }
+
+    /// Extract a specific component by type
+    pub fn extract_component<T: Component + 'static>(
+        &self,
+        world: &hecs::World,
+        entity: hecs::Entity,
+    ) -> Option<Box<dyn SerializedComponent>> {
+        let type_id = TypeId::of::<T>();
+        self.extractors
+            .get(&type_id)
+            .and_then(|extractor| extractor(world, entity))
+    }
+
+    /// Extract components by category
+    pub fn extract_components_in_category(
+        &self,
+        world: &hecs::World,
+        entity: hecs::Entity,
+        category: &str,
+    ) -> Vec<Box<dyn SerializedComponent>> {
+        self.categories
+            .get(category)
+            .map(|type_ids| {
+                type_ids
+                    .iter()
+                    .filter_map(|type_id| {
+                        self.extractors
+                            .get(type_id)
+                            .and_then(|extractor| extractor(world, entity))
+                    })
+                    .collect()
+            })
+            .unwrap_or_default()
+    }
+}
+
+impl Default for ComponentRegistry {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+/// A blanket trait for types that can be serialized as a component.
+#[typetag::serde(tag = "type")]
+pub trait SerializedComponent: dyn_clone::DynClone + Send + Sync {}
+
+dyn_clone::clone_trait_object!(SerializedComponent);
+
+#[derive(Clone, Debug)]
+pub struct ComponentDescriptor {
+    /// Fully qualified type name of the component, such as `eucalyptus_core::components::MeshRenderer`.
+    pub fqtn: String,
+    /// Short name of the component, such as `MeshRenderer`.
+    pub type_name: String,
+    /// Category of the component, such as `Rendering`.
+    pub category: Option<String>,
+    /// Description of the component, such as `Renders a 3D model`.
+    pub description: Option<String>,
+}
+
+/// Defines a type that can be considered a component of an entity.
+pub trait Component: Sized {
+    /// A custom format of the component for saving the state of the component to disk.
+    ///
+    /// To have your type available, you must include this blanket trait:
+    /// ```rust no-run
+    /// #[typetag::serde]
+    /// impl SerializedComponent for T {}
+    /// ```
+    type SerializedForm: Serialize + for<'de> Deserialize<'de> + SerializedComponent;
+
+    fn descriptor() -> ComponentDescriptor;
+
+    /// Creates a new instance of the component for times when there is no existing component to
+    /// initialise from.
+    async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>;
+
+    /// Converts [`Self::SerializedForm`] into a [`Component`] instance that can be added to
+    /// `hecs::EntityBuilder` during scene initialisation.
+    async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>;
+
+    /// Called every frame to update the component's state.
+    fn update_component(&mut self, world: &hecs::World, entity: hecs::Entity, dt: f32, graphics: Arc<SharedGraphicsContext>);
+
+    /// Called when saving the scene to disk. Returns the [`Self::SerializedForm`] of the component that can be
+    /// saved to disk.
+    fn save(&self, world: &hecs::World, entity: hecs::Entity) -> Box<dyn SerializedComponent>;
+
+    /// In the editor, how the component will be represented in the `Resource Viewer` dock.
+    fn inspect(&mut self, ui: &mut egui::Ui);
+}
+
+#[typetag::serde]
+impl SerializedComponent for SerializedMeshRenderer {}
+
+// sample for MeshRenderer
+impl Component for MeshRenderer {
+    type SerializedForm = SerializedMeshRenderer;
+
+    fn descriptor() -> ComponentDescriptor {
+        ComponentDescriptor {
+            fqtn: "dropbear_engine::entity::MeshRenderer".to_string(),
+            type_name: "MeshRenderer".to_string(),
+            category: Some("Rendering".to_string()),
+            description: Some("Renders a mesh".to_string()),
+        }
+    }
+
+    async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>
+    where
+        Self: Sized
+    {
+        Ok(MeshRenderer::from_handle(Handle::NULL))
+    }
+
+    async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+        let import_scale = ser.import_scale.unwrap_or(1.0);
+
+        let handle = match &ser.handle.ref_type {
+            ResourceReferenceType::None => {
+                log::debug!("ResourceReferenceType is None, setting to `Handle::NULL`");
+                Handle::NULL
+            }
+            ResourceReferenceType::File(_) => {
+                log::debug!("Loading model from file: {:?}", ser.handle);
+                let path = ser.handle.resolve()?;
+                let buffer = std::fs::read(&path)?;
+                Model::load_from_memory_raw(graphics.clone(), buffer, Some(ser.label.as_str()), ASSET_REGISTRY.clone()).await?
+            }
+            ResourceReferenceType::Bytes(bytes) => {
+                log::debug!("Loading model from bytes [Len: {}]", bytes.len());
+                Model::load_from_memory_raw(graphics.clone(), bytes, Some(ser.label.as_str()), ASSET_REGISTRY.clone()).await?
+            }
+            ResourceReferenceType::ProcObj(obj) => match obj {
+                dropbear_engine::procedural::ProcObj::Cuboid { size_bits } => {
+                    let size = [
+                        f32::from_bits(size_bits[0]),
+                        f32::from_bits(size_bits[1]),
+                        f32::from_bits(size_bits[2]),
+                    ];
+                    log::debug!("Loading model from cuboid: {:?}", size);
+
+                    let size_vec = glam::DVec3::new(size[0] as f64, size[1] as f64, size[2] as f64);
+                    ProcedurallyGeneratedObject::cuboid(size_vec).build_model(
+                        graphics.clone(),
+                        None,
+                        Some(ser.label.as_str()),
+                        ASSET_REGISTRY.clone(),
+                    )
+                }
+            },
+        };
+
+        let mut renderer = MeshRenderer::from_handle(handle);
+        renderer.set_import_scale(import_scale);
+
+        for (label, m) in ser.texture_override {
+            if let Some(mat) = renderer.material_snapshot.get_mut(&label) {
+                mat.tint = m.tint;
+                mat.emissive_factor = m.emissive_factor;
+                mat.metallic_factor = m.metallic_factor;
+                mat.roughness_factor = m.roughness_factor;
+                mat.alpha_mode = m.alpha_mode;
+                mat.alpha_cutoff = m.alpha_cutoff;
+                mat.double_sided = m.double_sided;
+                mat.occlusion_strength = m.occlusion_strength;
+                mat.normal_scale = m.normal_scale;
+                mat.uv_tiling = m.uv_tiling;
+                mat.texture_tag = m.texture_tag;
+                mat.wrap_mode = m.wrap_mode;
+
+                let get_tex = async |resource: Option<ResourceReference>| -> Option<anyhow::Result<Texture>> {
+                    if let Some(dif) = resource {
+                        match dif.ref_type {
+                            ResourceReferenceType::None => {
+                                None
+                            }
+                            ResourceReferenceType::File(_) => {
+                                let path = dif.resolve().ok()?;
+                                let tex = Texture::from_file(graphics.clone(), &path, Some(&label)).await;
+                                Some(tex)
+                            }
+                            ResourceReferenceType::Bytes(bytes) => {
+                                let tex = Texture::from_bytes(graphics.clone(), &bytes, Some(&label));
+                                Some(Ok(tex))
+                            }
+                            ResourceReferenceType::ProcObj(_) => {
+                                Some(Err(anyhow::anyhow!("Using a ProcObj as a texture is not valid, for texture with label {}", label)))
+                            }
+                        }
+                    } else {
+                        None
+                    }
+                };
+
+                if let Some(tex) = get_tex(m.diffuse_texture).await {
+                    mat.diffuse_texture = tex?;
+                }
+
+                if let Some(tex) = get_tex(m.emissive_texture).await {
+                    mat.emissive_texture = Some(tex?);
+                }
+
+                if let Some(tex) = get_tex(m.normal_texture).await {
+                    mat.normal_texture = tex?;
+                }
+
+                if let Some(tex) = get_tex(m.occlusion_texture).await {
+                    mat.occlusion_texture = Some(tex?);
+                }
+
+                if let Some(tex) = get_tex(m.metallic_roughness_texture).await {
+                    mat.metallic_roughness_texture = Some(tex?);
+                }
+            }
+        }
+
+        Ok(renderer)
+    }
+
+    fn update_component(&mut self, world: &World, entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {
+        if let Ok((mesh, transform)) = world.query_one::<(&mut MeshRenderer, &EntityTransform)>(entity).get() {
+            mesh.update(&transform.propagate(&world, entity))
+        }
+    }
+
+    fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
+        let asset = ASSET_REGISTRY.read();
+        let model = asset.get_model(self.model());
+
+        let mut texture_override: HashMap<String, SerializedMaterialCustomisation> = HashMap::new();
+        for (label, mat) in &self.material_snapshot {
+            let diffuse_texture = model
+                .and_then(|m| m.materials.iter()
+                    .find(|material|
+                        material.diffuse_texture.label.as_ref() == Some(&label) &&
+                            material.diffuse_texture.reference == mat.diffuse_texture.reference
+                    )
+                    .and_then(|material| material.diffuse_texture.reference.clone())
+                );
+
+            let normal_texture = model
+                .and_then(|m| m.materials.iter()
+                    .find(|material|
+                        material.normal_texture.label.as_ref() == Some(&label) &&
+                            material.normal_texture.reference == mat.normal_texture.reference
+                    )
+                    .and_then(|material| material.normal_texture.reference.clone())
+                );
+
+            let emissive_texture = model
+                .and_then(|m| {
+                    let mat_ref = mat.emissive_texture.as_ref()?.reference.as_ref()?;
+                    m.materials.iter()
+                        .find_map(|material| {
+                            let nt = material.emissive_texture.as_ref()?;
+                            if nt.label.as_ref() == Some(&label) && nt.reference.as_ref() == Some(mat_ref) {
+                                nt.reference.clone()
+                            } else {
+                                None
+                            }
+                        })
+                });
+
+            let occlusion_texture = model
+                .and_then(|m| {
+                    let mat_ref = mat.occlusion_texture.as_ref()?.reference.as_ref()?;
+                    m.materials.iter()
+                        .find_map(|material| {
+                            let nt = material.occlusion_texture.as_ref()?;
+                            if nt.label.as_ref() == Some(&label) && nt.reference.as_ref() == Some(mat_ref) {
+                                nt.reference.clone()
+                            } else {
+                                None
+                            }
+                        })
+                });
+
+            let metallic_roughness_texture = model
+                .and_then(|m| {
+                    let mat_ref = mat.metallic_roughness_texture.as_ref()?.reference.as_ref()?;
+                    m.materials.iter()
+                        .find_map(|material| {
+                            let nt = material.metallic_roughness_texture.as_ref()?;
+                            if nt.label.as_ref() == Some(&label) && nt.reference.as_ref() == Some(mat_ref) {
+                                nt.reference.clone()
+                            } else {
+                                None
+                            }
+                        })
+                });
+
+
+            texture_override.insert(label.to_string(), SerializedMaterialCustomisation {
+                label: label.clone(),
+                diffuse_texture,
+                tint: mat.tint,
+                emissive_factor: mat.emissive_factor,
+                metallic_factor: mat.metallic_factor,
+                roughness_factor: mat.roughness_factor,
+                alpha_mode: mat.alpha_mode,
+                alpha_cutoff: mat.alpha_cutoff,
+                double_sided: mat.double_sided,
+                occlusion_strength: mat.occlusion_strength,
+                normal_scale: mat.normal_scale,
+                uv_tiling: mat.uv_tiling,
+                texture_tag: mat.texture_tag.clone(),
+                wrap_mode: mat.wrap_mode,
+                emissive_texture,
+                normal_texture,
+                occlusion_texture,
+                metallic_roughness_texture,
+            });
+        }
+
+        Box::new(SerializedMeshRenderer {
+            label: model.unwrap().label.clone(),
+            handle: Default::default(),
+            import_scale: None,
+            texture_override,
+        })
+    }
+
+    fn inspect(&mut self, ui: &mut Ui) {
+        CollapsingHeader::new("Mesh Renderer").show(ui, |ui| {
+            ui.label("Not implemented yet (MeshRenderer)");
+        });
+    }
+}
+
diff --git a/crates/eucalyptus-core/src/lib.rs b/crates/eucalyptus-core/src/lib.rs
index 3c17c63..b3f2f23 100644
--- a/crates/eucalyptus-core/src/lib.rs
+++ b/crates/eucalyptus-core/src/lib.rs
@@ -8,7 +8,6 @@ pub mod ptr;
 pub mod runtime;
 pub mod scene;
 pub mod scripting;
-pub mod spawn;
 pub mod states;
 pub mod utils;
 pub mod command;
@@ -20,22 +19,24 @@ pub mod entity;
 pub mod engine;
 pub mod transform;
 pub mod asset;
+pub mod component;
+pub mod animation;
 
 pub use dropbear_macro as macros;
-pub use dropbear_traits as traits;
 
 pub use egui;
 pub use rapier3d;
 use dropbear_engine::animation::AnimationComponent;
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer};
-use dropbear_traits::registry::ComponentRegistry;
+use dropbear_engine::lighting::Light;
 use properties::CustomProperties;
 use crate::camera::CameraComponent;
+use crate::component::ComponentRegistry;
 use crate::physics::collider::ColliderGroup;
 use crate::physics::kcc::KCC;
 use crate::physics::rigidbody::RigidBody;
-use crate::states::{SerializableCamera, Light, Script, SerializedMeshRenderer};
+use crate::states::{SerializableCamera, SerializedLight, Script, SerializedMeshRenderer};
 
 /// The appdata directory for storing any information.
 ///
@@ -49,56 +50,14 @@ pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo {
 pub fn register_components(
     component_registry: &mut ComponentRegistry,
 ) {
-    component_registry.register_with_default_component::<EntityTransform>();
-    component_registry.register_with_default::<CustomProperties>();
-    component_registry.register_with_default::<Light>();
-    component_registry.register_with_default::<Script>();
-    component_registry.register_with_default::<SerializedMeshRenderer>();
-    component_registry.register_with_default::<SerializableCamera>();
-    component_registry.register_with_default::<RigidBody>();
-    component_registry.register_with_default::<ColliderGroup>();
-    component_registry.register_with_default::<KCC>();
-    component_registry.register_with_default_component::<AnimationComponent>();
-
-    component_registry.register_converter::<MeshRenderer, SerializedMeshRenderer, _>(
-        |_, _, renderer| {
-            Some(SerializedMeshRenderer::from_renderer(renderer))
-        },
-    );
-
-    component_registry.register_converter::<CameraComponent, SerializableCamera, _>(
-        |world, entity, component| {
-            let Ok(camera) = world.get::<&Camera>(entity) else {
-                log::debug!(
-                            "Camera component without matching Camera found on entity {:?}",
-                            entity
-                        );
-                return None;
-            };
-
-            Some(SerializableCamera::from_ecs_camera(&camera, component))
-        },
-    );
-
-    // // register plugin defined structs
-    // if let Err(e) = plugin_registry.load_plugins() {
-    //     fatal!("Failed to load plugins: {}", e);
-    //     return;
-    // }
-    //
-    // for p in plugin_registry.list_plugins() {
-    //     log::info!("Plugin {} has been loaded", p.display_name);
-    // }
-    //
-    // log::info!("Total plugins added: {}", plugin_registry.plugins.len());
-    //
-    // for plugin in plugin_registry.list_plugins() {
-    //     if let Some(p) = plugin_registry.get_mut(&plugin.display_name) {
-    //         p.register_component(component_registry);
-    //         log::info!(
-    //                     "Components for plugin [{}] has been registered to component registry",
-    //                     plugin.display_name
-    //                 );
-    //     }
-    // }
+    component_registry.register::<EntityTransform>();
+    component_registry.register::<CustomProperties>();
+    component_registry.register::<Light>();
+    component_registry.register::<Script>();
+    component_registry.register::<MeshRenderer>();
+    component_registry.register::<Camera>();
+    component_registry.register::<RigidBody>();
+    component_registry.register::<ColliderGroup>();
+    component_registry.register::<KCC>();
+    component_registry.register::<AnimationComponent>();
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/lighting.rs b/crates/eucalyptus-core/src/lighting.rs
index 52dbc2a..e7eae7d 100644
--- a/crates/eucalyptus-core/src/lighting.rs
+++ b/crates/eucalyptus-core/src/lighting.rs
@@ -1,14 +1,77 @@
+use std::sync::Arc;
+use egui::Ui;
 use crate::ptr::WorldPtr;
 use crate::scripting::jni::utils::{FromJObject, ToJObject};
 use crate::scripting::native::DropbearNativeError;
 use crate::scripting::result::DropbearNativeResult;
 use crate::types::NVector3;
 use dropbear_engine::entity::{EntityTransform, Transform};
-use dropbear_engine::lighting::{LightComponent, LightType};
+use dropbear_engine::lighting::{Light, LightComponent, LightType};
 use glam::{DQuat, DVec3};
 use hecs::{Entity, World};
 use jni::objects::{JObject, JValue};
 use jni::JNIEnv;
+use dropbear_engine::graphics::SharedGraphicsContext;
+use crate::component::{Component, ComponentDescriptor, SerializedComponent};
+use crate::states::SerializedLight;
+
+#[typetag::serde]
+impl SerializedComponent for SerializedLight {}
+
+impl Component for Light {
+    type SerializedForm = SerializedLight;
+
+    fn descriptor() -> ComponentDescriptor {
+        ComponentDescriptor {
+            fqtn: "dropbear_engine::lighting::Light".to_string(),
+            type_name: "Light".to_string(),
+            category: Some("Lighting".to_string()),
+            description: Some("An object that emits light".to_string()),
+        }
+    }
+
+    async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>
+    where
+        Self: Sized
+    {
+        Ok(Light::new(graphics.clone(), LightComponent::default(), Transform::default(), None).await)
+    }
+
+    async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+        let light = Light::new(
+            graphics.clone(),
+            ser.light_component,
+            ser.transform,
+            Some(ser.label.as_str())
+        ).await;
+        
+        Ok(light)
+    }
+
+    fn update_component(&mut self, world: &World, entity: Entity, _dt: f32, graphics: Arc<SharedGraphicsContext>) {
+        if let Ok((light, comp, trans)) = world.query_one::<(&mut Light, &mut LightComponent, &mut Transform)>(entity).get() {
+            light.update(&graphics, comp, trans);
+        }
+    }
+
+    fn save(&self, world: &World, entity: Entity) -> Box<dyn SerializedComponent> {
+        if let Ok((_, comp, transform)) = world.query_one::<(&Light, &LightComponent, &Transform)>(entity).get() {
+            Box::new(SerializedLight {
+                label: self.label.clone(),
+                transform: *transform,
+                light_component: comp.clone(),
+                enabled: comp.enabled,
+                entity_id: Some(entity),
+            })
+        } else {
+            Box::new(SerializedLight::default())
+        }
+    }
+
+    fn inspect(&mut self, ui: &mut Ui) {
+        todo!()
+    }
+}
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug)]
diff --git a/crates/eucalyptus-core/src/mesh.rs b/crates/eucalyptus-core/src/mesh.rs
index 0649945..b87c86c 100644
--- a/crates/eucalyptus-core/src/mesh.rs
+++ b/crates/eucalyptus-core/src/mesh.rs
@@ -205,17 +205,21 @@ fn set_texture_override(
     let _ = shared::resolve_target_material_name(model, &material_name)
         .ok_or(DropbearNativeError::InvalidArgument)?;
 
-    let handle = Handle::<dropbear_engine::texture::Texture>::new(texture_handle);
-    if reader.get_texture(handle).is_none() {
+    let handle = Handle::<Texture>::new(texture_handle);
+    let Some(diff_tex) = reader.get_texture(handle) else {
         return Err(DropbearNativeError::InvalidHandle);
-    }
-
-    drop(reader);
+    };
 
     let mut renderer = world
         .get::<&mut MeshRenderer>(entity)
         .map_err(|_| DropbearNativeError::NoSuchComponent)?;
-    renderer.set_texture_override(handle);
+
+    if let Some(v) = renderer.material_snapshot.get_mut(&material_name) {
+        v.diffuse_texture = diff_tex.clone();
+    } else {
+        return Err(DropbearNativeError::InvalidArgument);
+    }
+
     Ok(())
 }
 
diff --git a/crates/eucalyptus-core/src/physics/collider.rs b/crates/eucalyptus-core/src/physics/collider.rs
index 252ed5b..46950b4 100644
--- a/crates/eucalyptus-core/src/physics/collider.rs
+++ b/crates/eucalyptus-core/src/physics/collider.rs
@@ -21,19 +21,22 @@ use crate::states::Label;
 use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::wgpu::util::{BufferInitDescriptor, DeviceExt};
 use dropbear_engine::wgpu::{Buffer, BufferUsages};
-use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent};
 use std::any::Any;
 use ::jni::objects::{JObject, JValue};
 use ::jni::JNIEnv;
 use rapier3d::prelude::ColliderBuilder;
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
+use egui::{CollapsingHeader, Ui};
 use crate::physics::collider::shared::{get_collider, get_collider_mut};
 use crate::scripting::native::DropbearNativeError;
 use crate::scripting::result::DropbearNativeResult;
 use crate::types::{NCollider, NVector3};
 use glam::DQuat;
+use hecs::{Entity, World};
 use rapier3d::prelude::{Rotation, SharedShape, TypedShape, Vector};
+use dropbear_engine::animation::AnimationComponent;
+use crate::component::{Component, ComponentDescriptor, SerializedComponent};
 use crate::physics::PhysicsState;
 use crate::ptr::PhysicsStatePtr;
 
@@ -54,22 +57,41 @@ impl ColliderGroup {
 }
 
 #[typetag::serde]
-impl SerializableComponent for ColliderGroup {
-    fn as_any(&self) -> &dyn Any {
-        self
+impl SerializedComponent for ColliderGroup {}
+
+impl Component for ColliderGroup {
+    type SerializedForm = Self;
+
+    fn descriptor() -> ComponentDescriptor {
+        ComponentDescriptor {
+            fqtn: "eucalyptus_core::physics::collider::ColliderGroup".to_string(),
+            type_name: "ColliderGroup".to_string(),
+            category: Some("Physics".to_string()),
+            description: Some("A group of colliders".to_string()),
+        }
     }
 
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
+    async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>
+    where
+        Self: Sized
+    {
+        Ok(Self::new())
+    }
+
+    async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+        Ok(ser)
+    }
+
+    fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
+
+    fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
     }
 
-    fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = self.clone();
-        Box::pin(async move {
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((value,)));
-            Ok(insert)
-        })
+    fn inspect(&mut self, ui: &mut Ui) {
+        CollapsingHeader::new("Colliders").default_open(true).show(ui, |ui| {
+            ui.label("Not implemented yet!");
+        });
     }
 }
 
diff --git a/crates/eucalyptus-core/src/physics/kcc.rs b/crates/eucalyptus-core/src/physics/kcc.rs
index f2a6811..1d41390 100644
--- a/crates/eucalyptus-core/src/physics/kcc.rs
+++ b/crates/eucalyptus-core/src/physics/kcc.rs
@@ -5,9 +5,11 @@ pub mod character_collision;
 
 use rapier3d::control::{CharacterCollision, KinematicCharacterController};
 use serde::{Deserialize, Serialize};
-use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent};
 use crate::states::Label;
 use std::any::Any;
+use std::sync::Arc;
+use egui::Ui;
+use hecs::{Entity, World};
 use crate::physics::PhysicsState;
 use crate::scripting::jni::utils::ToJObject;
 use crate::scripting::native::DropbearNativeError;
@@ -18,6 +20,9 @@ use jni::JNIEnv;
 use rapier3d::dynamics::RigidBodyType;
 use rapier3d::math::Rotation;
 use rapier3d::prelude::QueryFilter;
+use dropbear_engine::animation::AnimationComponent;
+use dropbear_engine::graphics::SharedGraphicsContext;
+use crate::component::{Component, ComponentDescriptor, SerializedComponent};
 use crate::ptr::WorldPtr;
 
 /// The kinematic character controller (kcc) component.
@@ -30,22 +35,41 @@ pub struct KCC {
 }
 
 #[typetag::serde]
-impl SerializableComponent for KCC {
-    fn as_any(&self) -> &dyn Any {
-        self
+impl SerializedComponent for KCC {}
+
+impl Component for KCC {
+    type SerializedForm = Self;
+
+    fn descriptor() -> ComponentDescriptor {
+        ComponentDescriptor {
+            fqtn: "eucalyptus_core::physics::kcc::KCC".to_string(),
+            type_name: "KinematicCharacterController".to_string(),
+            category: Some("Physics".to_string()),
+            description: Some("A kinematic character controller".to_string()),
+        }
+    }
+
+    async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>
+    where
+        Self: Sized
+    {
+        Ok(Self::default())
+    }
+
+    async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+        Ok(ser)
     }
 
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
+    fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
+
+    fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
     }
 
-    fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = self.clone();
-        Box::pin(async move {
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((value,)));
-            Ok(insert)
-        })
+    fn inspect(&mut self, ui: &mut Ui) {
+        egui::CollapsingHeader::new("Kinematic Character Controller").default_open(true).show(ui, |ui| {
+            ui.label("Not implemented yet!")
+        });
     }
 }
 
diff --git a/crates/eucalyptus-core/src/physics/rigidbody.rs b/crates/eucalyptus-core/src/physics/rigidbody.rs
index fbc5e3e..6d87ea2 100644
--- a/crates/eucalyptus-core/src/physics/rigidbody.rs
+++ b/crates/eucalyptus-core/src/physics/rigidbody.rs
@@ -4,12 +4,16 @@ use crate::scripting::jni::utils::{FromJObject, ToJObject};
 use crate::scripting::native::DropbearNativeError;
 use crate::scripting::result::DropbearNativeResult;
 use crate::states::Label;
-use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent};
 use std::any::Any;
+use std::sync::Arc;
 use ::jni::objects::{JObject, JValue};
 use ::jni::JNIEnv;
+use egui::{CollapsingHeader, Ui};
+use hecs::{Entity, World};
 use rapier3d::prelude::RigidBodyType;
 use serde::{Deserialize, Serialize};
+use dropbear_engine::graphics::SharedGraphicsContext;
+use crate::component::{Component, ComponentDescriptor, SerializedComponent};
 use crate::types::{IndexNative, NCollider, RigidBodyContext, NVector3};
 use crate::physics::PhysicsState;
 use crate::ptr::{PhysicsStatePtr, WorldPtr};
@@ -183,22 +187,41 @@ impl Default for RigidBody {
 }
 
 #[typetag::serde]
-impl SerializableComponent for RigidBody {
-	fn as_any(&self) -> &dyn Any {
-		self
+impl SerializedComponent for RigidBody {}
+
+impl Component for RigidBody {
+	type SerializedForm = Self;
+
+	fn descriptor() -> ComponentDescriptor {
+		ComponentDescriptor {
+			fqtn: "eucalyptus_core::physics::rigidbody::RigidBody".to_string(),
+			type_name: "RigidBody".to_string(),
+			category: Some("Physics".to_string()),
+			description: Some("An object that can move, rotate or collide with another object".to_string()),
+		}
 	}
 
-	fn clone_box(&self) -> Box<dyn SerializableComponent> {
+	async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>
+	where
+		Self: Sized
+	{
+		Ok(Self::default())
+	}
+
+	async fn init(ser: Self::SerializedForm, _graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+		Ok(ser)
+	}
+
+	fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
+
+	fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
 		Box::new(self.clone())
 	}
 
-	fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture {
-		let value = self.clone();
-		Box::pin(async move {
-			let insert: Box<dyn dropbear_traits::ComponentInsert> =
-				Box::new(InsertBundle((value,)));
-			Ok(insert)
-		})
+	fn inspect(&mut self, ui: &mut Ui) {
+		CollapsingHeader::new("RigidBody").default_open(true).show(ui, |ui| {
+			ui.label("Not implemented yet!");
+		});
 	}
 }
 
diff --git a/crates/eucalyptus-core/src/properties.rs b/crates/eucalyptus-core/src/properties.rs
index 692e008..e72ac56 100644
--- a/crates/eucalyptus-core/src/properties.rs
+++ b/crates/eucalyptus-core/src/properties.rs
@@ -1,15 +1,18 @@
 use std::fmt;
 use std::fmt::{Display, Formatter};
 use serde::{Deserialize, Serialize};
-use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent};
 use std::any::Any;
-use egui::Ui;
+use std::sync::Arc;
+use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, TextEdit, Ui};
 use hecs::{Entity, World};
+use dropbear_engine::graphics::SharedGraphicsContext;
+use crate::component::{Component, ComponentDescriptor, SerializedComponent};
 use crate::ptr::WorldPtr;
 use crate::scripting::native::DropbearNativeError;
 use crate::scripting::result::DropbearNativeResult;
 use crate::states::Property;
 use crate::types::NVector3;
+use crate::warn;
 
 /// Properties for an entity, typically queries with `entity.getProperty<Float>` and `entity.setProperty(21)`
 #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
@@ -18,6 +21,166 @@ pub struct CustomProperties {
     pub next_id: u64,
 }
 
+#[typetag::serde]
+impl SerializedComponent for CustomProperties {}
+
+impl Component for CustomProperties {
+    type SerializedForm = Self;
+
+    fn descriptor() -> ComponentDescriptor {
+        ComponentDescriptor {
+            fqtn: "eucalyptus_core::properties::CustomProperties".to_string(),
+            type_name: "CustomProperties".to_string(),
+            category: None,
+            description: Some("Custom properties for an entity".to_string()),
+        }
+    }
+
+    async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>
+    where
+        Self: Sized
+    {
+        Ok(Self::new())
+    }
+
+    async fn init(ser: Self::SerializedForm, _graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+        Ok(ser)
+    }
+
+    fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
+
+    fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
+        Box::new(self.clone())
+    }
+
+    fn inspect(&mut self, ui: &mut Ui) {
+        CollapsingHeader::new("Custom Properties").default_open(true).show(ui, |ui| {
+            ui.vertical(|ui| {
+                Grid::new("properties").striped(true).show(ui, |ui| {
+                    ui.label(RichText::new("Key"));
+                    ui.label(RichText::new("Type"));
+                    ui.label(RichText::new("Value"));
+                    ui.label(RichText::new("Action"));
+                    ui.end_row();
+
+                    let mut to_delete: Option<u64> = None;
+                    let mut to_rename: Option<(u64, String)> = None;
+
+                    for (_i, property) in self.custom_properties.iter_mut().enumerate() {
+                        let mut edited_key = property.key.clone();
+                        ui.add_sized([100.0, 20.0], TextEdit::singleline(&mut edited_key));
+
+                        if edited_key != property.key {
+                            to_rename = Some((property.id, edited_key));
+                        }
+
+                        let current_type = ValueType::from(&mut property.value);
+                        let mut selected_type = current_type;
+
+                        ComboBox::from_id_salt(format!("type_{}", property.id))
+                            .selected_text(format!("{:?}", selected_type))
+                            .show_ui(ui, |ui| {
+                                ui.selectable_value(
+                                    &mut selected_type,
+                                    ValueType::String,
+                                    "String",
+                                );
+                                ui.selectable_value(&mut selected_type, ValueType::Float, "Float");
+                                ui.selectable_value(&mut selected_type, ValueType::Int, "Int");
+                                ui.selectable_value(&mut selected_type, ValueType::Bool, "Bool");
+                                ui.selectable_value(&mut selected_type, ValueType::Vec3, "Vec3");
+                            });
+
+                        if selected_type != current_type {
+                            property.value = match selected_type {
+                                ValueType::String => Value::String(String::new()),
+                                ValueType::Float => Value::Double(0.0),
+                                ValueType::Int => Value::Int(0),
+                                ValueType::Bool => Value::Bool(false),
+                                ValueType::Vec3 => Value::Vec3([0.0, 0.0, 0.0]),
+                            };
+                        }
+
+                        let speed = {
+                            let input = ui.input(|i| i.modifiers);
+                            if input.shift {
+                                0.01
+                            } else if cfg!(target_os = "macos") && input.mac_cmd
+                                || !cfg!(target_os = "macos") && input.ctrl
+                            {
+                                1.0
+                            } else {
+                                0.1
+                            }
+                        };
+
+                        match &mut property.value {
+                            Value::String(s) => {
+                                ui.add_sized([100.0, 20.0], egui::TextEdit::singleline(s));
+                            }
+                            Value::Int(n) => {
+                                ui.add(DragValue::new(n).speed(1.0));
+                            }
+                            Value::Double(f) => {
+                                ui.add(DragValue::new(f).speed(speed));
+                            }
+                            Value::Bool(b) => {
+                                if ui.button(if *b { "✅" } else { "❌" }).clicked() {
+                                    *b = !*b;
+                                }
+                            }
+                            Value::Vec3(v) => {
+                                ui.horizontal(|ui| {
+                                    ui.add(DragValue::new(&mut v[0]).speed(speed));
+                                    ui.add(DragValue::new(&mut v[1]).speed(speed));
+                                    ui.add(DragValue::new(&mut v[2]).speed(speed));
+                                });
+                            }
+                        }
+
+                        if ui.button("🗑️").clicked() {
+                            log::debug!("Trashing {}", property.key);
+                            to_delete = Some(property.id);
+                        }
+
+                        ui.end_row();
+                    }
+
+                    if let Some(id) = to_delete {
+                        self.custom_properties.retain(|p| p.id != id);
+                    }
+
+                    if let Some((id, new_key)) = to_rename {
+                        if let Some(property) =
+                            self.custom_properties.iter_mut().find(|p| p.id == id)
+                        {
+                            property.key = new_key;
+                        } else {
+                            warn!("Failed to rename property: id not found");
+                        }
+                    }
+
+                    if ui.button("Add").clicked() {
+                        log::debug!("Inserting new default value");
+                        let mut new_key = String::from("new_property");
+                        let mut counter = 1;
+                        while self.custom_properties.iter().any(|p| p.key == new_key) {
+                            new_key = format!("new_property_{}", counter);
+                            counter += 1;
+                        }
+                        self.custom_properties.push(Property {
+                            id: self.next_id,
+                            key: new_key,
+                            value: Value::default(),
+                        });
+                        self.next_id += 1;
+                    }
+                });
+            });
+        });
+    }
+}
+
 impl CustomProperties {
     /// Creates a new [CustomProperties]
     pub fn new() -> Self {
@@ -128,26 +291,6 @@ impl Default for CustomProperties {
     }
 }
 
-#[typetag::serde]
-impl SerializableComponent for CustomProperties {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
-        Box::new(self.clone())
-    }
-
-    fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = self.clone();
-        Box::pin(async move {
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((value,)));
-            Ok(insert)
-        })
-    }
-}
-
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub enum Value {
     String(String),
@@ -176,6 +319,40 @@ impl Display for Value {
     }
 }
 
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum ValueType {
+    String,
+    Float,
+    Int,
+    Bool,
+    Vec3,
+}
+
+impl From<Value> for ValueType {
+    fn from(value: Value) -> Self {
+        match value {
+            Value::String(_) => ValueType::String,
+            Value::Int(_) => ValueType::Int,
+            Value::Double(_) => ValueType::Float,
+            Value::Bool(_) => ValueType::Bool,
+            Value::Vec3(_) => ValueType::Vec3,
+        }
+    }
+}
+
+impl From<&mut Value> for ValueType {
+    fn from(value: &mut Value) -> Self {
+        match value {
+            Value::String(_) => ValueType::String,
+            Value::Int(_) => ValueType::Int,
+            Value::Double(_) => ValueType::Float,
+            Value::Bool(_) => ValueType::Bool,
+            Value::Vec3(_) => ValueType::Vec3,
+        }
+    }
+}
+
+
 pub mod shared {
     use hecs::World;
     use crate::properties::CustomProperties;
diff --git a/crates/eucalyptus-core/src/scene.rs b/crates/eucalyptus-core/src/scene.rs
index 7adaa2c..acf2ad3 100644
--- a/crates/eucalyptus-core/src/scene.rs
+++ b/crates/eucalyptus-core/src/scene.rs
@@ -5,13 +5,11 @@ pub mod scripting;
 
 use crate::camera::CameraComponent;
 use crate::hierarchy::{Children, Parent, SceneHierarchy};
-use crate::states::{SerializableCamera, Label, Light, Script, SerializedMeshRenderer, WorldLoadingStatus, PROJECT};
+use crate::states::{SerializableCamera, Label, SerializedLight, Script, SerializedMeshRenderer, WorldLoadingStatus, PROJECT};
 use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform};
 use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::lighting::{Light as EngineLight, LightComponent};
-use dropbear_traits::{ComponentInitContext, ComponentResources, SerializableComponent};
-use dropbear_traits::registry::ComponentRegistry;
 use glam::{DQuat, DVec3};
 use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
 use ron::ser::PrettyConfig;
@@ -23,6 +21,7 @@ use std::sync::Arc;
 use crossbeam_channel::Sender;
 use egui::Ui;
 use hecs::Entity;
+use crate::component::{Component, ComponentRegistry, SerializedComponent};
 use crate::physics::collider::ColliderGroup;
 use crate::physics::kcc::KCC;
 use crate::physics::PhysicsState;
@@ -35,7 +34,7 @@ pub struct SceneEntity {
     pub label: Label,
 
     #[serde(default)]
-    pub components: Vec<Box<dyn SerializableComponent>>,
+    pub components: Vec<Box<dyn SerializedComponent>>,
 
     #[serde(skip)]
     pub entity_id: Option<hecs::Entity>,
@@ -125,28 +124,6 @@ impl SceneConfig {
         }
     }
 
-    /// Helper function to init a component and insert it into the world.
-    async fn init_component(
-        component: &Box<dyn SerializableComponent>,
-        entity: Entity,
-        world: &mut hecs::World,
-        resources: Arc<ComponentResources>,
-        label: &str,
-    ) -> anyhow::Result<()> {
-        let ctx = ComponentInitContext { entity, resources };
-        let insert = component.init(ctx).await?;
-        insert.insert(world, entity).map_err(|e| {
-            anyhow::anyhow!(
-                "Failed to insert component '{}' for '{}': {}",
-                component.type_name(),
-                label,
-                e
-            )
-        })?;
-
-        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())
@@ -482,7 +459,7 @@ impl SceneConfig {
                     EngineLight::new(graphics.clone(), comp.clone(), trans, Some("Default Light"))
                         .await;
 
-                let light_config = Light {
+                let light_config = SerializedLight {
                     label: "Default Light".to_string(),
                     transform: trans,
                     light_component: comp.clone(),
diff --git a/crates/eucalyptus-core/src/spawn.rs b/crates/eucalyptus-core/src/spawn.rs
deleted file mode 100644
index ccb44ca..0000000
--- a/crates/eucalyptus-core/src/spawn.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-//! Traits and helpers for dealing with spawn queues.
-
-use crate::scene::SceneEntity;
-use dropbear_engine::future::{FutureHandle, FutureQueue};
-use dropbear_engine::graphics::SharedGraphicsContext;
-use parking_lot::Mutex;
-use std::sync::{Arc, LazyLock};
-
-/// All spawns that are waiting to be spawned in.
-pub static PENDING_SPAWNS: LazyLock<Mutex<Vec<PendingSpawn>>> =
-    LazyLock::new(|| Mutex::new(Vec::new()));
-
-/// A spawn that's waiting to be added into the world.
-#[derive(Clone)]
-pub struct PendingSpawn {
-    pub scene_entity: SceneEntity,
-    /// An optional future handle to an object.
-    ///
-    /// If one is specified, it is assumed that the returned object is a [`MeshRenderer`](dropbear_engine::entity::MeshRenderer).
-    ///
-    /// If one is NOT specified, it will be created based off the information provided. It is **recommended** to set it to [`None`].
-    pub handle: Option<FutureHandle>,
-}
-
-/// Extension trait for checking the editor (or anything else) for any spawns
-/// that are waiting to be polled and added
-pub trait PendingSpawnController {
-    /// Checks up on the spawn list and spawns them into the world after it has been
-    /// asynchronously loaded.
-    ///
-    /// This is expected to be run on the update loop.
-    fn check_up(
-        &mut self,
-        graphics: Arc<SharedGraphicsContext>,
-        queue: Arc<FutureQueue>,
-    ) -> anyhow::Result<()>;
-}
-
-/// Helper function to spawn a [`PendingSpawn`]
-pub fn push_pending_spawn(spawn: PendingSpawn) {
-    log::debug!("Pushing spawn");
-    PENDING_SPAWNS.lock().push(spawn);
-}
diff --git a/crates/eucalyptus-core/src/states.rs b/crates/eucalyptus-core/src/states.rs
index 697b9b0..effb359 100644
--- a/crates/eucalyptus-core/src/states.rs
+++ b/crates/eucalyptus-core/src/states.rs
@@ -12,12 +12,12 @@ use dropbear_engine::lighting::LightComponent;
 use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::asset::ASSET_REGISTRY;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType, EUCA_SCHEME};
-use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent};
 use once_cell::sync::Lazy;
 use parking_lot::RwLock;
 use serde::{Deserialize, Serialize};
 use std::borrow::Borrow;
 use std::any::Any;
+use std::collections::HashMap;
 use std::fmt;
 use std::fmt::{Display, Formatter};
 use std::ops::{Deref, DerefMut};
@@ -25,7 +25,9 @@ use std::path::PathBuf;
 use std::sync::Arc;
 use egui::{CollapsingHeader, TextEdit, Ui};
 use hecs::{Entity, World};
-use dropbear_traits::{Component, ComponentDescriptor};
+use dropbear_engine::model::AlphaMode;
+use dropbear_engine::texture::TextureWrapMode;
+use crate::component::{Component, ComponentDescriptor, SerializedComponent};
 use crate::properties::Value;
 
 /// A global "singleton" that contains the configuration of a project.
@@ -156,10 +158,13 @@ pub struct Script {
     pub tags: Vec<String>,
 }
 
+#[typetag::serde]
+impl SerializedComponent for Script {}
+
 impl Component for Script {
-    type Serialized = Self;
+    type SerializedForm = Self;
 
-    fn static_descriptor() -> ComponentDescriptor {
+    fn descriptor() -> ComponentDescriptor {
         ComponentDescriptor {
             fqtn: "eucalyptus_core::states::Script".to_string(),
             type_name: "Script".to_string(),
@@ -168,40 +173,47 @@ impl Component for Script {
         }
     }
 
-    fn deserialize(serialized: &Self::Serialized) -> Self {
-        serialized.clone()
+    async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>
+    where
+        Self: Sized
+    {
+        Ok(Self::default())
     }
 
-    fn serialize(&self) -> Self::Serialized {
-        self.clone()
+    async fn init(ser: Self::SerializedForm, _graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+        Ok(ser)
+    }
+
+    fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
+
+    fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
+        Box::new(self.clone())
     }
 
     fn inspect(&mut self, ui: &mut Ui) {
-        ui.vertical(|ui| {
-            CollapsingHeader::new("Logic")
-                .default_open(true)
-                .show(ui, |ui| {
-                    let mut local_del: Option<usize> = None;
-                    for (i, tag) in self.tags.iter_mut().enumerate() {
-                        let current_width = ui.available_width();
-                        ui.horizontal(|ui| {
-                            ui.add_sized(
-                                [current_width * 70.0 / 100.0, 20.0],
-                                TextEdit::singleline(tag),
-                            );
-                            if ui.button("🗑️").clicked() {
-                                local_del = Some(i);
-                            }
-                        });
-                    }
-                    if let Some(i) = local_del {
-                        self.tags.remove(i);
-                    }
-                    if ui.button("➕ Add").clicked() {
-                        self.tags.push(String::new())
-                    }
-                });
-        });
+        CollapsingHeader::new("Logic")
+            .default_open(true)
+            .show(ui, |ui| {
+                let mut local_del: Option<usize> = None;
+                for (i, tag) in self.tags.iter_mut().enumerate() {
+                    let current_width = ui.available_width();
+                    ui.horizontal(|ui| {
+                        ui.add_sized(
+                            [current_width * 70.0 / 100.0, 20.0],
+                            TextEdit::singleline(tag),
+                        );
+                        if ui.button("🗑️").clicked() {
+                            local_del = Some(i);
+                        }
+                    });
+                }
+                if let Some(i) = local_del {
+                    self.tags.remove(i);
+                }
+                if ui.button("➕ Add").clicked() {
+                    self.tags.push(String::new())
+                }
+            });
     }
 }
 
@@ -253,8 +265,8 @@ impl SerializableCamera {
         };
 
         let transform = Transform {
-            position: position,
-            rotation: rotation,
+            position,
+            rotation,
             scale: glam::DVec3::ONE,
         };
 
@@ -282,7 +294,7 @@ pub struct Property {
 
 // A serializable configuration struct for the [Light] type
 #[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct Light {
+pub struct SerializedLight {
     pub label: String,
     pub transform: Transform,
     pub light_component: LightComponent,
@@ -292,10 +304,10 @@ pub struct Light {
     pub entity_id: Option<hecs::Entity>,
 }
 
-impl Default for Light {
+impl Default for SerializedLight {
     fn default() -> Self {
         Self {
-            label: "New Light".to_string(),
+            label: "Default Light".to_string(),
             transform: Transform::default(),
             light_component: LightComponent::default(),
             enabled: true,
@@ -424,189 +436,32 @@ impl DerefMut for Label {
 }
 
 /// A [MeshRenderer] that is serialized into a file to be stored as a value for config.
-
 #[derive(Default, Debug, Clone, Serialize, Deserialize)]
 pub struct SerializedMeshRenderer {
+    pub label: String,
     pub handle: ResourceReference,
     pub import_scale: Option<f32>,
-    pub texture_override: Option<ResourceReference>,
-}
-
-impl SerializedMeshRenderer {
-    /// Creates a new [SerializedMeshRenderer] from an existing [MeshRenderer] by cloning data.
-    pub fn from_renderer(renderer: &MeshRenderer) -> Self {
-        let handle = renderer.model();
-        let handle_ref = if handle.is_null() {
-            ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id })
-        } else {
-            let registry = ASSET_REGISTRY.read();
-            registry
-                .get_model(handle)
-                .map(|model| model.path.clone())
-                .unwrap_or_else(|| {
-                    ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id })
-                })
-        };
-
-        let texture_override = renderer.texture_override().map(|handle| {
-            let registry = ASSET_REGISTRY.read();
-            let label = registry.get_label_from_texture_handle(handle);
-            let reference = label.and_then(|value| {
-                if value.starts_with(EUCA_SCHEME) {
-                    Some(ResourceReference::from_reference(ResourceReferenceType::File(value)))
-                } else {
-                    None
-                }
-            });
-
-            reference.unwrap_or_else(|| {
-                ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id })
-            })
-        });
-
-        Self {
-            handle: handle_ref,
-            import_scale: Some(renderer.import_scale()),
-            texture_override,
-        }
-    }
-}
-
-#[typetag::serde]
-impl SerializableComponent for Script {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
-        Box::new(self.clone())
-    }
-
-    fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = self.clone();
-        Box::pin(async move {
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((value,)));
-            Ok(insert)
-        })
-    }
-}
-
-#[typetag::serde]
-impl SerializableComponent for SerializableCamera {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
-        Box::new(self.clone())
-    }
-
-    fn init(&self, ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = self.clone();
-        Box::pin(async move {
-            let graphics = ctx
-                .resources
-                .get::<Arc<SharedGraphicsContext>>()
-                .ok_or_else(|| anyhow::anyhow!("SharedGraphicsContext missing for Camera3D init"))?;
-
-            let builder = CameraBuilder::from(value.clone());
-            let camera = Camera::new(graphics.clone(), builder, Some(value.label.as_str()));
-            let component = CameraComponent::from(value);
-
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((camera, component)));
-            Ok(insert)
-        })
-    }
-}
-
-#[typetag::serde]
-impl SerializableComponent for Light {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
-        Box::new(self.clone())
-    }
-
-    fn init(&self, ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = self.clone();
-        Box::pin(async move {
-            let graphics = ctx
-                .resources
-                .get::<Arc<SharedGraphicsContext>>()
-                .ok_or_else(|| anyhow::anyhow!("SharedGraphicsContext missing for Light init"))?;
-
-            let engine_light = dropbear_engine::lighting::Light::new(
-                graphics.clone(),
-                value.light_component.clone(),
-                value.transform,
-                Some(value.label.as_str()),
-            )
-            .await;
-
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((
-                    value.light_component.clone(),
-                    engine_light,
-                    value.clone(),
-                    value.transform,
-                )));
-            Ok(insert)
-        })
-    }
+    pub texture_override: HashMap<String, SerializedMaterialCustomisation>,
 }
 
-#[typetag::serde]
-impl SerializableComponent for Label {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
-        Box::new(self.clone())
-    }
-
-    fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = self.clone();
-        Box::pin(async move {
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((value,)));
-            Ok(insert)
-        })
-    }
-}
-
-#[typetag::serde]
-impl SerializableComponent for SerializedMeshRenderer {
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn clone_box(&self) -> Box<dyn SerializableComponent> {
-        Box::new(self.clone())
-    }
-
-    fn init(&self, ctx: ComponentInitContext) -> ComponentInitFuture {
-        let value = self.clone();
-        Box::pin(async move {
-            let graphics = ctx
-                .resources
-                .get::<Arc<SharedGraphicsContext>>()
-                .ok_or_else(|| anyhow::anyhow!("SharedGraphicsContext missing for MeshRenderer init"))?;
-
-            let label = format!("Entity {:?}", ctx.entity);
-            let renderer = crate::utils::mesh_loader::load_mesh_renderer_from_serialized(
-                &value,
-                graphics.clone(),
-                &label,
-            )
-            .await?;
-            let insert: Box<dyn dropbear_traits::ComponentInsert> =
-                Box::new(InsertBundle((renderer,)));
-            Ok(insert)
-        })
-    }
+#[derive(Default, Debug, Clone, Serialize, Deserialize)]
+pub struct SerializedMaterialCustomisation {
+    pub label: String,
+    pub diffuse_texture: Option<ResourceReference>,
+    pub tint: [f32; 4],
+    pub emissive_factor: [f32; 3],
+    pub metallic_factor: f32,
+    pub roughness_factor: f32,
+    pub alpha_mode: AlphaMode,
+    pub alpha_cutoff: Option<f32>,
+    pub double_sided: bool,
+    pub occlusion_strength: f32,
+    pub normal_scale: f32,
+    pub uv_tiling: [f32; 2],
+    pub texture_tag: Option<String>,
+    pub wrap_mode: TextureWrapMode,
+    pub emissive_texture: Option<ResourceReference>,
+    pub normal_texture: Option<ResourceReference>,
+    pub occlusion_texture: Option<ResourceReference>,
+    pub metallic_roughness_texture: Option<ResourceReference>,
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/transform.rs b/crates/eucalyptus-core/src/transform.rs
index bbac6db..79b6798 100644
--- a/crates/eucalyptus-core/src/transform.rs
+++ b/crates/eucalyptus-core/src/transform.rs
@@ -1,3 +1,4 @@
+use std::sync::Arc;
 use crate::hierarchy::EntityTransformExt;
 use crate::ptr::WorldPtr;
 use crate::scripting::jni::utils::{FromJObject, ToJObject};
@@ -7,8 +8,54 @@ use dropbear_engine::entity::{EntityTransform, Transform};
 use glam::{DQuat, DVec3};
 use ::jni::objects::{JObject, JValue};
 use ::jni::JNIEnv;
+use egui::{CollapsingHeader, Ui};
+use hecs::{Entity, World};
+use dropbear_engine::graphics::SharedGraphicsContext;
+use crate::component::{Component, ComponentDescriptor, SerializedComponent};
 use crate::types::{NTransform, NVector3};
 
+#[typetag::serde]
+impl SerializedComponent for EntityTransform {}
+
+impl Component for EntityTransform {
+    type SerializedForm = Self;
+
+    fn descriptor() -> ComponentDescriptor {
+        ComponentDescriptor {
+            fqtn: "dropbear_engine::entity::EntityTransform".to_string(),
+            type_name: "EntityTransform".to_string(),
+            category: None,
+            description: Some("Allows the entity to have a space within the world".to_string()),
+        }
+    }
+
+    async fn first_time(_: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+        Ok(Self::default())
+    }
+
+    async fn init(ser: Self::SerializedForm, _: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> {
+        Ok(ser)
+    }
+
+    fn update_component(&mut self, _: &World, _: Entity, _: f32, _: Arc<SharedGraphicsContext>) {}
+
+    fn save(&self, _: &World, _: Entity) -> Box<dyn SerializedComponent> {
+        Box::new(self.clone())
+    }
+
+    fn inspect(&mut self, ui: &mut Ui) {
+        CollapsingHeader::new("Entity Transform").show(ui, |ui| {
+            CollapsingHeader::new("Local").show(ui, |ui| {
+                self.local_mut().inspect(ui);
+            });
+            ui.add_space(4.0);
+            CollapsingHeader::new("World").show(ui, |ui| {
+                self.world_mut().inspect(ui);
+            });
+        });
+    }
+}
+
 impl FromJObject for Transform {
     fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> {
         let pos_val = env.get_field(obj, "position", "Lcom/dropbear/math/Vector3d;")
diff --git a/crates/eucalyptus-core/src/utils.rs b/crates/eucalyptus-core/src/utils.rs
index 2ba682f..6214acc 100644
--- a/crates/eucalyptus-core/src/utils.rs
+++ b/crates/eucalyptus-core/src/utils.rs
@@ -2,7 +2,6 @@
 
 pub mod option;
 pub mod hashmap;
-pub mod mesh_loader;
 
 use crate::states::Node;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType, relative_path_from_euca};
diff --git a/crates/eucalyptus-core/src/utils/mesh_loader.rs b/crates/eucalyptus-core/src/utils/mesh_loader.rs
deleted file mode 100644
index f42281a..0000000
--- a/crates/eucalyptus-core/src/utils/mesh_loader.rs
+++ /dev/null
@@ -1,84 +0,0 @@
-use std::sync::Arc;
-
-use dropbear_engine::asset::ASSET_REGISTRY;
-use dropbear_engine::entity::MeshRenderer;
-use dropbear_engine::graphics::SharedGraphicsContext;
-use dropbear_engine::model::Model;
-use dropbear_engine::procedural::ProcedurallyGeneratedObject;
-use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
-
-use crate::states::SerializedMeshRenderer;
-use crate::utils::ResolveReference;
-
-pub async fn load_mesh_renderer_from_serialized(
-    serialized: &SerializedMeshRenderer,
-    graphics: Arc<SharedGraphicsContext>,
-    label: &str,
-) -> anyhow::Result<MeshRenderer> {
-    let import_scale = serialized.import_scale.unwrap_or(1.0);
-
-    let handle = match &serialized.handle.ref_type {
-        ResourceReferenceType::None => {
-            anyhow::bail!("Resource reference type is None for '{}'", label);
-        }
-        ResourceReferenceType::Unassigned { id } => {
-            let model = Model {
-                label: "None".to_string(),
-                hash: *id,
-                path: ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: *id }),
-                meshes: Vec::new(),
-                materials: Vec::new(),
-                skins: Vec::new(),
-                animations: Vec::new(),
-                nodes: Vec::new(),
-            };
-
-            let mut registry = ASSET_REGISTRY.write();
-            if let Some(existing) = registry.model_handle_by_hash(*id) {
-                existing
-            } else {
-                registry.add_model(model)
-            }
-        }
-        ResourceReferenceType::File(reference) => {
-            let path = serialized.handle.resolve()?;
-            log::debug!("Path for entity {} is {} from reference {}", label, path.display(), reference);
-            let buffer = std::fs::read(&path)?;
-            Model::load_from_memory_raw(graphics.clone(), buffer, Some(label), ASSET_REGISTRY.clone()).await?
-        }
-        ResourceReferenceType::Bytes(bytes) => {
-            log::info!("Loading entity '{}' from bytes [Len: {}]", label, bytes.len());
-            Model::load_from_memory_raw(graphics.clone(), bytes, Some(label), ASSET_REGISTRY.clone()).await?
-        }
-        ResourceReferenceType::ProcObj(obj) => match obj {
-            dropbear_engine::procedural::ProcObj::Cuboid { size_bits } => {
-                let size = [
-                    f32::from_bits(size_bits[0]),
-                    f32::from_bits(size_bits[1]),
-                    f32::from_bits(size_bits[2]),
-                ];
-                log::info!("Loading entity '{}' from cuboid: {:?}", label, size);
-
-                let size_vec = glam::DVec3::new(size[0] as f64, size[1] as f64, size[2] as f64);
-                ProcedurallyGeneratedObject::cuboid(size_vec).build_model(
-                    graphics.clone(),
-                    None,
-                    Some(label),
-                    ASSET_REGISTRY.clone(),
-                )
-            }
-        },
-    };
-
-    let mut renderer = MeshRenderer::from_handle(handle);
-    renderer.set_import_scale(import_scale);
-
-    if serialized.texture_override.is_some() {
-        log::debug!(
-            "texture_override is set for '{}' but not applied yet", 
-            label
-        );
-    }
-
-    Ok(renderer)
-}
diff --git a/crates/eucalyptus-editor/src/editor/component.rs b/crates/eucalyptus-editor/src/editor/component.rs
index 52b8e0a..7dbea57 100644
--- a/crates/eucalyptus-editor/src/editor/component.rs
+++ b/crates/eucalyptus-editor/src/editor/component.rs
@@ -30,38 +30,6 @@ pub trait InspectableComponent {
     );
 }
 
-#[derive(Copy, Clone, PartialEq, Debug)]
-pub enum ValueType {
-    String,
-    Float,
-    Int,
-    Bool,
-    Vec3,
-}
-
-impl From<Value> for ValueType {
-    fn from(value: Value) -> Self {
-        match value {
-            Value::String(_) => ValueType::String,
-            Value::Int(_) => ValueType::Int,
-            Value::Double(_) => ValueType::Float,
-            Value::Bool(_) => ValueType::Bool,
-            Value::Vec3(_) => ValueType::Vec3,
-        }
-    }
-}
-
-impl From<&mut Value> for ValueType {
-    fn from(value: &mut Value) -> Self {
-        match value {
-            Value::String(_) => ValueType::String,
-            Value::Int(_) => ValueType::Int,
-            Value::Double(_) => ValueType::Float,
-            Value::Bool(_) => ValueType::Bool,
-            Value::Vec3(_) => ValueType::Vec3,
-        }
-    }
-}
 
 fn wrap_angle_degrees(angle: f64) -> f64 {
     (angle + 180.0).rem_euclid(360.0) - 180.0
@@ -82,128 +50,7 @@ impl InspectableComponent for CustomProperties {
         _signal: &mut Signal,
         _label: &mut String,
     ) {
-        ui.vertical(|ui| {
-            Grid::new("properties").striped(true).show(ui, |ui| {
-                ui.label(RichText::new("Key"));
-                ui.label(RichText::new("Type"));
-                ui.label(RichText::new("Value"));
-                ui.label(RichText::new("Action"));
-                ui.end_row();
-
-                let mut to_delete: Option<u64> = None;
-                let mut to_rename: Option<(u64, String)> = None;
-
-                for (_i, property) in self.custom_properties.iter_mut().enumerate() {
-                    let mut edited_key = property.key.clone();
-                    ui.add_sized([100.0, 20.0], TextEdit::singleline(&mut edited_key));
-
-                    if edited_key != property.key {
-                        to_rename = Some((property.id, edited_key));
-                    }
-
-                    let current_type = ValueType::from(&mut property.value);
-                    let mut selected_type = current_type;
-
-                    ComboBox::from_id_salt(format!("type_{}", property.id))
-                        .selected_text(format!("{:?}", selected_type))
-                        .show_ui(ui, |ui| {
-                            ui.selectable_value(
-                                &mut selected_type,
-                                ValueType::String,
-                                "String",
-                            );
-                            ui.selectable_value(&mut selected_type, ValueType::Float, "Float");
-                            ui.selectable_value(&mut selected_type, ValueType::Int, "Int");
-                            ui.selectable_value(&mut selected_type, ValueType::Bool, "Bool");
-                            ui.selectable_value(&mut selected_type, ValueType::Vec3, "Vec3");
-                        });
-
-                    if selected_type != current_type {
-                        property.value = match selected_type {
-                            ValueType::String => Value::String(String::new()),
-                            ValueType::Float => Value::Double(0.0),
-                            ValueType::Int => Value::Int(0),
-                            ValueType::Bool => Value::Bool(false),
-                            ValueType::Vec3 => Value::Vec3([0.0, 0.0, 0.0]),
-                        };
-                    }
-
-                    let speed = {
-                        let input = ui.input(|i| i.modifiers);
-                        if input.shift {
-                            0.01
-                        } else if cfg!(target_os = "macos") && input.mac_cmd
-                            || !cfg!(target_os = "macos") && input.ctrl
-                        {
-                            1.0
-                        } else {
-                            0.1
-                        }
-                    };
-
-                    match &mut property.value {
-                        Value::String(s) => {
-                            ui.add_sized([100.0, 20.0], egui::TextEdit::singleline(s));
-                        }
-                        Value::Int(n) => {
-                            ui.add(DragValue::new(n).speed(1.0));
-                        }
-                        Value::Double(f) => {
-                            ui.add(DragValue::new(f).speed(speed));
-                        }
-                        Value::Bool(b) => {
-                            if ui.button(if *b { "✅" } else { "❌" }).clicked() {
-                                *b = !*b;
-                            }
-                        }
-                        Value::Vec3(v) => {
-                            ui.horizontal(|ui| {
-                                ui.add(DragValue::new(&mut v[0]).speed(speed));
-                                ui.add(DragValue::new(&mut v[1]).speed(speed));
-                                ui.add(DragValue::new(&mut v[2]).speed(speed));
-                            });
-                        }
-                    }
-
-                    if ui.button("🗑️").clicked() {
-                        log::debug!("Trashing {}", property.key);
-                        to_delete = Some(property.id);
-                    }
-
-                    ui.end_row();
-                }
 
-                if let Some(id) = to_delete {
-                    self.custom_properties.retain(|p| p.id != id);
-                }
-
-                if let Some((id, new_key)) = to_rename {
-                    if let Some(property) =
-                        self.custom_properties.iter_mut().find(|p| p.id == id)
-                    {
-                        property.key = new_key;
-                    } else {
-                        warn!("Failed to rename property: id not found");
-                    }
-                }
-
-                if ui.button("Add").clicked() {
-                    log::debug!("Inserting new default value");
-                    let mut new_key = String::from("new_property");
-                    let mut counter = 1;
-                    while self.custom_properties.iter().any(|p| p.key == new_key) {
-                        new_key = format!("new_property_{}", counter);
-                        counter += 1;
-                    }
-                    self.custom_properties.push(Property {
-                        id: self.next_id,
-                        key: new_key,
-                        value: Value::default(),
-                    });
-                    self.next_id += 1;
-                }
-            });
-        });
     }
 }
 
diff --git a/include/dropbear.h b/include/dropbear.h
index 7672e60..a29e1f8 100644
--- a/include/dropbear.h
+++ b/include/dropbear.h
@@ -68,6 +68,39 @@ typedef struct ColliderShapeFfi {
 
 typedef ColliderShapeFfi ColliderShape;
 
+typedef enum NShapeCastStatusTag {
+    NShapeCastStatusTag_OutOfIterations = 0,
+    NShapeCastStatusTag_Converged = 1,
+    NShapeCastStatusTag_Failed = 2,
+    NShapeCastStatusTag_PenetratingOrWithinTargetDist = 3,
+} NShapeCastStatusTag;
+
+typedef struct NShapeCastStatusOutOfIterations {
+} NShapeCastStatusOutOfIterations;
+
+typedef struct NShapeCastStatusConverged {
+} NShapeCastStatusConverged;
+
+typedef struct NShapeCastStatusFailed {
+} NShapeCastStatusFailed;
+
+typedef struct NShapeCastStatusPenetratingOrWithinTargetDist {
+} NShapeCastStatusPenetratingOrWithinTargetDist;
+
+typedef union NShapeCastStatusData {
+    NShapeCastStatusOutOfIterations OutOfIterations;
+    NShapeCastStatusConverged Converged;
+    NShapeCastStatusFailed Failed;
+    NShapeCastStatusPenetratingOrWithinTargetDist PenetratingOrWithinTargetDist;
+} NShapeCastStatusData;
+
+typedef struct NShapeCastStatusFfi {
+    NShapeCastStatusTag tag;
+    NShapeCastStatusData data;
+} NShapeCastStatusFfi;
+
+typedef NShapeCastStatusFfi NShapeCastStatus;
+
 typedef enum NAnimationInterpolationTag {
     NAnimationInterpolationTag_Linear = 0,
     NAnimationInterpolationTag_Step = 1,
@@ -146,58 +179,113 @@ typedef struct NChannelValuesFfi {
 
 typedef NChannelValuesFfi NChannelValues;
 
-typedef enum NShapeCastStatusTag {
-    NShapeCastStatusTag_OutOfIterations = 0,
-    NShapeCastStatusTag_Converged = 1,
-    NShapeCastStatusTag_Failed = 2,
-    NShapeCastStatusTag_PenetratingOrWithinTargetDist = 3,
-} NShapeCastStatusTag;
-
-typedef struct NShapeCastStatusOutOfIterations {
-} NShapeCastStatusOutOfIterations;
+typedef struct IndexNative {
+    uint32_t index;
+    uint32_t generation;
+} IndexNative;
 
-typedef struct NShapeCastStatusConverged {
-} NShapeCastStatusConverged;
+typedef struct NCollider {
+    IndexNative index;
+    uint64_t entity_id;
+    uint32_t id;
+} NCollider;
 
-typedef struct NShapeCastStatusFailed {
-} NShapeCastStatusFailed;
+typedef struct NShapeCastHit {
+    NCollider collider;
+    double distance;
+    NVector3 witness1;
+    NVector3 witness2;
+    NVector3 normal1;
+    NVector3 normal2;
+    NShapeCastStatus status;
+} NShapeCastHit;
 
-typedef struct NShapeCastStatusPenetratingOrWithinTargetDist {
-} NShapeCastStatusPenetratingOrWithinTargetDist;
+typedef struct RigidBodyContext {
+    IndexNative index;
+    uint64_t entity_id;
+} RigidBodyContext;
 
-typedef union NShapeCastStatusData {
-    NShapeCastStatusOutOfIterations OutOfIterations;
-    NShapeCastStatusConverged Converged;
-    NShapeCastStatusFailed Failed;
-    NShapeCastStatusPenetratingOrWithinTargetDist PenetratingOrWithinTargetDist;
-} NShapeCastStatusData;
+typedef struct AxisLock {
+    bool x;
+    bool y;
+    bool z;
+} AxisLock;
 
-typedef struct NShapeCastStatusFfi {
-    NShapeCastStatusTag tag;
-    NShapeCastStatusData data;
-} NShapeCastStatusFfi;
+typedef struct Progress {
+    size_t current;
+    size_t total;
+    const char* message;
+} Progress;
 
-typedef NShapeCastStatusFfi NShapeCastStatus;
+typedef struct NColour {
+    uint8_t r;
+    uint8_t g;
+    uint8_t b;
+    uint8_t a;
+} NColour;
 
-typedef struct IndexNative {
-    uint32_t index;
-    uint32_t generation;
-} IndexNative;
+typedef struct NRange {
+    float start;
+    float end;
+} NRange;
 
-typedef struct IndexNativeArray {
-    IndexNative* values;
+typedef struct i32Array {
+    int32_t* values;
     size_t length;
     size_t capacity;
-} IndexNativeArray;
+} i32Array;
 
-typedef struct CharacterCollisionArray {
-    uint64_t entity_id;
-    IndexNativeArray collisions;
-} CharacterCollisionArray;
+typedef struct NNodeTransform {
+    NVector3 translation;
+    NQuaternion rotation;
+    NVector3 scale;
+} NNodeTransform;
+
+typedef struct NNode {
+    const char* name;
+    const int32_t* parent;
+    i32Array children;
+    NNodeTransform transform;
+} NNode;
+
+typedef struct NNodeArray {
+    NNode* values;
+    size_t length;
+    size_t capacity;
+} NNodeArray;
 
 typedef void* WorldPtr;
 
-typedef void* AssetRegistryPtr;
+typedef struct NTransform {
+    NVector3 position;
+    NQuaternion rotation;
+    NVector3 scale;
+} NTransform;
+
+typedef struct f64ArrayArray {
+    double* values;
+    size_t length;
+    size_t capacity;
+} f64ArrayArray;
+
+typedef struct NSkin {
+    const char* name;
+    i32Array joints;
+    f64ArrayArray inverse_bind_matrices;
+    const int32_t* skeleton_root;
+} NSkin;
+
+typedef struct NSkinArray {
+    NSkin* values;
+    size_t length;
+    size_t capacity;
+} NSkinArray;
+
+typedef struct NColliderArray {
+    NCollider* values;
+    size_t length;
+    size_t capacity;
+} NColliderArray;
 
 typedef struct f64Array {
     double* values;
@@ -230,36 +318,43 @@ typedef struct NAnimationArray {
     size_t capacity;
 } NAnimationArray;
 
-typedef struct NCollider {
-    IndexNative index;
-    uint64_t entity_id;
-    uint32_t id;
-} NCollider;
+typedef void* AssetRegistryPtr;
 
-typedef struct NColliderArray {
-    NCollider* values;
+typedef void* InputStatePtr;
+
+typedef struct IndexNativeArray {
+    IndexNative* values;
     size_t length;
     size_t capacity;
-} NColliderArray;
-
-typedef void* CommandBufferPtr;
+} IndexNativeArray;
 
-typedef void* SceneLoaderPtr;
+typedef struct CharacterCollisionArray {
+    uint64_t entity_id;
+    IndexNativeArray collisions;
+} CharacterCollisionArray;
 
 typedef struct NVector2 {
     double x;
     double y;
 } NVector2;
 
+typedef void* GraphicsContextPtr;
+
+typedef struct NAttenuation {
+    float constant;
+    float linear;
+    float quadratic;
+} NAttenuation;
+
 typedef struct u64Array {
     uint64_t* values;
     size_t length;
     size_t capacity;
 } u64Array;
 
-typedef struct ConnectedGamepadIds {
-    u64Array ids;
-} ConnectedGamepadIds;
+typedef void* SceneLoaderPtr;
+
+typedef void* CommandBufferPtr;
 
 typedef struct NVector4 {
     double x;
@@ -268,12 +363,6 @@ typedef struct NVector4 {
     double w;
 } NVector4;
 
-typedef struct i32Array {
-    int32_t* values;
-    size_t length;
-    size_t capacity;
-} i32Array;
-
 typedef struct NModelVertex {
     NVector3 position;
     NVector3 normal;
@@ -304,6 +393,11 @@ typedef struct NMeshArray {
     size_t capacity;
 } NMeshArray;
 
+typedef struct RayHit {
+    NCollider collider;
+    double distance;
+} RayHit;
+
 typedef struct NMaterial {
     const char* name;
     uint64_t diffuse_texture;
@@ -330,103 +424,9 @@ typedef struct NMaterialArray {
 
 typedef void* PhysicsStatePtr;
 
-typedef struct NTransform {
-    NVector3 position;
-    NQuaternion rotation;
-    NVector3 scale;
-} NTransform;
-
-typedef struct NNodeTransform {
-    NVector3 translation;
-    NQuaternion rotation;
-    NVector3 scale;
-} NNodeTransform;
-
-typedef struct NNode {
-    const char* name;
-    const int32_t* parent;
-    i32Array children;
-    NNodeTransform transform;
-} NNode;
-
-typedef struct NNodeArray {
-    NNode* values;
-    size_t length;
-    size_t capacity;
-} NNodeArray;
-
-typedef struct NShapeCastHit {
-    NCollider collider;
-    double distance;
-    NVector3 witness1;
-    NVector3 witness2;
-    NVector3 normal1;
-    NVector3 normal2;
-    NShapeCastStatus status;
-} NShapeCastHit;
-
-typedef struct NAttenuation {
-    float constant;
-    float linear;
-    float quadratic;
-} NAttenuation;
-
-typedef struct Progress {
-    size_t current;
-    size_t total;
-    const char* message;
-} Progress;
-
-typedef struct NRange {
-    float start;
-    float end;
-} NRange;
-
-typedef struct f64ArrayArray {
-    double* values;
-    size_t length;
-    size_t capacity;
-} f64ArrayArray;
-
-typedef struct NSkin {
-    const char* name;
-    i32Array joints;
-    f64ArrayArray inverse_bind_matrices;
-    const int32_t* skeleton_root;
-} NSkin;
-
-typedef struct NSkinArray {
-    NSkin* values;
-    size_t length;
-    size_t capacity;
-} NSkinArray;
-
-typedef struct RayHit {
-    NCollider collider;
-    double distance;
-} RayHit;
-
-typedef struct RigidBodyContext {
-    IndexNative index;
-    uint64_t entity_id;
-} RigidBodyContext;
-
-typedef struct AxisLock {
-    bool x;
-    bool y;
-    bool z;
-} AxisLock;
-
-typedef void* InputStatePtr;
-
-typedef void* GraphicsContextPtr;
-
-typedef struct NColour {
-    uint8_t r;
-    uint8_t g;
-    uint8_t b;
-    uint8_t a;
-} NColour;
+typedef struct ConnectedGamepadIds {
+    u64Array ids;
+} ConnectedGamepadIds;
 
 int32_t dropbear_gamepad_is_button_pressed(InputStatePtr input, uint64_t gamepad_id, int32_t button_ordinal, bool* out0);
 int32_t dropbear_gamepad_get_left_stick_position(InputStatePtr input, uint64_t gamepad_id, NVector2* out0);
@@ -459,10 +459,6 @@ int32_t dropbear_collider_get_collider_translation(PhysicsStatePtr physics, cons
 int32_t dropbear_collider_set_collider_translation(PhysicsStatePtr physics, const NCollider* collider, const NVector3* translation);
 int32_t dropbear_collider_get_collider_rotation(PhysicsStatePtr physics, const NCollider* collider, NVector3* out0);
 int32_t dropbear_collider_set_collider_rotation(PhysicsStatePtr physics, const NCollider* collider, const NVector3* rotation);
-int32_t dropbear_kcc_kcc_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
-int32_t dropbear_kcc_move_character(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NVector3* translation, double delta_time);
-int32_t dropbear_kcc_set_rotation(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NQuaternion* rotation);
-int32_t dropbear_kcc_get_hit(WorldPtr world, uint64_t entity, CharacterCollisionArray* out0);
 int32_t dropbear_rigidbody_exists_for_entity(WorldPtr world, PhysicsStatePtr physics, uint64_t entity, IndexNative* out0, bool* out0_present);
 int32_t dropbear_rigidbody_get_rigidbody_mode(WorldPtr _world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, int32_t* out0);
 int32_t dropbear_rigidbody_set_rigidbody_mode(WorldPtr world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, int32_t mode);
@@ -487,6 +483,10 @@ int32_t dropbear_rigidbody_set_rigidbody_lock_rotation(WorldPtr world, PhysicsSt
 int32_t dropbear_rigidbody_get_rigidbody_children(WorldPtr _world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, NColliderArray* out0);
 int32_t dropbear_rigidbody_apply_impulse(WorldPtr world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, double x, double y, double z);
 int32_t dropbear_rigidbody_apply_torque_impulse(WorldPtr world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, double x, double y, double z);
+int32_t dropbear_kcc_kcc_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
+int32_t dropbear_kcc_move_character(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NVector3* translation, double delta_time);
+int32_t dropbear_kcc_set_rotation(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NQuaternion* rotation);
+int32_t dropbear_kcc_get_hit(WorldPtr world, uint64_t entity, CharacterCollisionArray* out0);
 int32_t dropbear_scripting_load_scene_async(CommandBufferPtr command_buffer, SceneLoaderPtr scene_loader, const char* scene_name, uint64_t* out0);
 int32_t dropbear_scripting_load_scene_async_with_loading(CommandBufferPtr command_buffer, SceneLoaderPtr scene_loader, const char* scene_name, const char* loading_scene, uint64_t* out0);
 int32_t dropbear_scripting_switch_to_scene_immediate(CommandBufferPtr command_buffer, const char* scene_name);
@@ -494,40 +494,6 @@ int32_t dropbear_scripting_get_scene_load_handle_scene_name(SceneLoaderPtr scene
 int32_t dropbear_scripting_switch_to_scene_async(CommandBufferPtr command_buffer, SceneLoaderPtr scene_loader, uint64_t scene_id);
 int32_t dropbear_scripting_get_scene_load_progress(SceneLoaderPtr scene_loader, uint64_t scene_id, Progress* out0);
 int32_t dropbear_scripting_get_scene_load_status(SceneLoaderPtr scene_loader, uint64_t scene_id, uint32_t* out0);
-int32_t dropbear_engine_get_asset(AssetRegistryPtr asset, const char* label, const AssetKind* kind, uint64_t* out0, bool* out0_present);
-int32_t dropbear_asset_model_get_label(AssetRegistryPtr asset, uint64_t model_handle, char** out0);
-int32_t dropbear_asset_model_get_meshes(AssetRegistryPtr asset, uint64_t model_handle, NMeshArray* out0);
-int32_t dropbear_asset_model_get_materials(AssetRegistryPtr asset, uint64_t model_handle, NMaterialArray* out0);
-int32_t dropbear_asset_model_get_skins(AssetRegistryPtr asset, uint64_t model_handle, NSkinArray* out0);
-int32_t dropbear_asset_model_get_animations(AssetRegistryPtr asset, uint64_t model_handle, NAnimationArray* out0);
-int32_t dropbear_asset_model_get_nodes(AssetRegistryPtr asset, uint64_t model_handle, NNodeArray* out0);
-int32_t dropbear_asset_texture_get_label(AssetRegistryPtr asset_manager, uint64_t texture_handle, char** out0, bool* out0_present);
-int32_t dropbear_asset_texture_get_width(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0);
-int32_t dropbear_asset_texture_get_height(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0);
-int32_t dropbear_camera_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
-int32_t dropbear_camera_get_eye(WorldPtr world, uint64_t entity, NVector3* out0);
-int32_t dropbear_camera_set_eye(WorldPtr world, uint64_t entity, const NVector3* eye);
-int32_t dropbear_camera_get_target(WorldPtr world, uint64_t entity, NVector3* out0);
-int32_t dropbear_camera_set_target(WorldPtr world, uint64_t entity, const NVector3* target);
-int32_t dropbear_camera_get_up(WorldPtr world, uint64_t entity, NVector3* out0);
-int32_t dropbear_camera_set_up(WorldPtr world, uint64_t entity, const NVector3* up);
-int32_t dropbear_camera_get_aspect(WorldPtr world, uint64_t entity, double* out0);
-int32_t dropbear_camera_get_fovy(WorldPtr world, uint64_t entity, double* out0);
-int32_t dropbear_camera_set_fovy(WorldPtr world, uint64_t entity, double fovy);
-int32_t dropbear_camera_get_znear(WorldPtr world, uint64_t entity, double* out0);
-int32_t dropbear_camera_set_znear(WorldPtr world, uint64_t entity, double znear);
-int32_t dropbear_camera_get_zfar(WorldPtr world, uint64_t entity, double* out0);
-int32_t dropbear_camera_set_zfar(WorldPtr world, uint64_t entity, double zfar);
-int32_t dropbear_camera_get_yaw(WorldPtr world, uint64_t entity, double* out0);
-int32_t dropbear_camera_set_yaw(WorldPtr world, uint64_t entity, double yaw);
-int32_t dropbear_camera_get_pitch(WorldPtr world, uint64_t entity, double* out0);
-int32_t dropbear_camera_set_pitch(WorldPtr world, uint64_t entity, double pitch);
-int32_t dropbear_camera_get_speed(WorldPtr world, uint64_t entity, double* out0);
-int32_t dropbear_camera_set_speed(WorldPtr world, uint64_t entity, double speed);
-int32_t dropbear_camera_get_sensitivity(WorldPtr world, uint64_t entity, double* out0);
-int32_t dropbear_camera_set_sensitivity(WorldPtr world, uint64_t entity, double sensitivity);
-int32_t dropbear_engine_get_entity(WorldPtr world, const char* label, uint64_t* out0);
-int32_t dropbear_engine_quit(CommandBufferPtr command_buffer);
 int32_t dropbear_entity_label_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
 int32_t dropbear_entity_get_label(WorldPtr world, uint64_t entity, char** out0);
 int32_t dropbear_entity_get_children(WorldPtr world, uint64_t entity, u64Array* out0);
@@ -567,9 +533,6 @@ int32_t dropbear_lighting_get_casts_shadows(WorldPtr world, uint64_t entity, boo
 int32_t dropbear_lighting_set_casts_shadows(WorldPtr world, uint64_t entity, bool casts_shadows);
 int32_t dropbear_lighting_get_depth(WorldPtr world, uint64_t entity, NRange* out0);
 int32_t dropbear_lighting_set_depth(WorldPtr world, uint64_t entity, const NRange* depth);
-int32_t dropbear_mesh_get_texture(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t* out0, bool* out0_present);
-int32_t dropbear_mesh_set_texture_override(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t texture_handle);
-int32_t dropbear_mesh_set_material_tint(WorldPtr world, AssetRegistryPtr asset, GraphicsContextPtr graphics, uint64_t entity, const char* material_name, float r, float g, float b, float a);
 int32_t dropbear_physics_get_gravity(PhysicsStatePtr physics, NVector3* out0);
 int32_t dropbear_physics_set_gravity(PhysicsStatePtr physics, const NVector3* gravity);
 int32_t dropbear_physics_raycast(PhysicsStatePtr physics, const NVector3* origin, const NVector3* dir, double time_of_impact, bool solid, RayHit* out0, bool* out0_present);
@@ -598,5 +561,42 @@ int32_t dropbear_transform_set_local_transform(WorldPtr world, uint64_t entity, 
 int32_t dropbear_transform_get_world_transform(WorldPtr world, uint64_t entity, NTransform* out0);
 int32_t dropbear_transform_set_world_transform(WorldPtr world, uint64_t entity, const NTransform* transform);
 int32_t dropbear_transform_propogate_transform(WorldPtr world, uint64_t entity, NTransform* out0);
+int32_t dropbear_engine_get_entity(WorldPtr world, const char* label, uint64_t* out0);
+int32_t dropbear_engine_quit(CommandBufferPtr command_buffer);
+int32_t dropbear_engine_get_asset(AssetRegistryPtr asset, const char* label, const AssetKind* kind, uint64_t* out0, bool* out0_present);
+int32_t dropbear_asset_model_get_label(AssetRegistryPtr asset, uint64_t model_handle, char** out0);
+int32_t dropbear_asset_model_get_meshes(AssetRegistryPtr asset, uint64_t model_handle, NMeshArray* out0);
+int32_t dropbear_asset_model_get_materials(AssetRegistryPtr asset, uint64_t model_handle, NMaterialArray* out0);
+int32_t dropbear_asset_model_get_skins(AssetRegistryPtr asset, uint64_t model_handle, NSkinArray* out0);
+int32_t dropbear_asset_model_get_animations(AssetRegistryPtr asset, uint64_t model_handle, NAnimationArray* out0);
+int32_t dropbear_asset_model_get_nodes(AssetRegistryPtr asset, uint64_t model_handle, NNodeArray* out0);
+int32_t dropbear_asset_texture_get_label(AssetRegistryPtr asset_manager, uint64_t texture_handle, char** out0, bool* out0_present);
+int32_t dropbear_asset_texture_get_width(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0);
+int32_t dropbear_asset_texture_get_height(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0);
+int32_t dropbear_mesh_get_texture(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t* out0, bool* out0_present);
+int32_t dropbear_mesh_set_texture_override(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t texture_handle);
+int32_t dropbear_mesh_set_material_tint(WorldPtr world, AssetRegistryPtr asset, GraphicsContextPtr graphics, uint64_t entity, const char* material_name, float r, float g, float b, float a);
+int32_t dropbear_camera_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0);
+int32_t dropbear_camera_get_eye(WorldPtr world, uint64_t entity, NVector3* out0);
+int32_t dropbear_camera_set_eye(WorldPtr world, uint64_t entity, const NVector3* eye);
+int32_t dropbear_camera_get_target(WorldPtr world, uint64_t entity, NVector3* out0);
+int32_t dropbear_camera_set_target(WorldPtr world, uint64_t entity, const NVector3* target);
+int32_t dropbear_camera_get_up(WorldPtr world, uint64_t entity, NVector3* out0);
+int32_t dropbear_camera_set_up(WorldPtr world, uint64_t entity, const NVector3* up);
+int32_t dropbear_camera_get_aspect(WorldPtr world, uint64_t entity, double* out0);
+int32_t dropbear_camera_get_fovy(WorldPtr world, uint64_t entity, double* out0);
+int32_t dropbear_camera_set_fovy(WorldPtr world, uint64_t entity, double fovy);
+int32_t dropbear_camera_get_znear(WorldPtr world, uint64_t entity, double* out0);
+int32_t dropbear_camera_set_znear(WorldPtr world, uint64_t entity, double znear);
+int32_t dropbear_camera_get_zfar(WorldPtr world, uint64_t entity, double* out0);
+int32_t dropbear_camera_set_zfar(WorldPtr world, uint64_t entity, double zfar);
+int32_t dropbear_camera_get_yaw(WorldPtr world, uint64_t entity, double* out0);
+int32_t dropbear_camera_set_yaw(WorldPtr world, uint64_t entity, double yaw);
+int32_t dropbear_camera_get_pitch(WorldPtr world, uint64_t entity, double* out0);
+int32_t dropbear_camera_set_pitch(WorldPtr world, uint64_t entity, double pitch);
+int32_t dropbear_camera_get_speed(WorldPtr world, uint64_t entity, double* out0);
+int32_t dropbear_camera_set_speed(WorldPtr world, uint64_t entity, double speed);
+int32_t dropbear_camera_get_sensitivity(WorldPtr world, uint64_t entity, double* out0);
+int32_t dropbear_camera_set_sensitivity(WorldPtr world, uint64_t entity, double sensitivity);
 
 #endif /* DROPBEAR_H */