kitgit

tirbofish/dropbear · commit

f785b02ee1ce9d6c404c07b12f6ffece4fedafca

wip: mess

Unverified

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

view full diff

diff --git a/crates/dropbear-engine/src/asset.rs b/crates/dropbear-engine/src/asset.rs
index 5872436..69e47f5 100644
--- a/crates/dropbear-engine/src/asset.rs
+++ b/crates/dropbear-engine/src/asset.rs
@@ -7,6 +7,7 @@ use crate::{
     texture::Texture,
 };
 use crate::graphics::SharedGraphicsContext;
+use crate::utils::ResourceReference;
 use crate::model::Model;
 
 pub static ASSET_REGISTRY: LazyLock<Arc<RwLock<AssetRegistry>>> = LazyLock::new(|| Arc::new(RwLock::new(AssetRegistry::new())));
@@ -261,6 +262,20 @@ impl AssetRegistry {
         self.model_labels.get(label).cloned()
     }
 
+    pub fn list_models(&self) -> Vec<(Handle<Model>, String, ResourceReference)> {
+        self.models
+            .values()
+            .map(|model| (Handle::new(model.hash), model.label.clone(), model.path.clone()))
+            .collect()
+    }
+
+    pub fn get_model_handle_by_reference(&self, reference: &ResourceReference) -> Option<Handle<Model>> {
+        self.models
+            .values()
+            .find(|model| &model.path == reference)
+            .map(|model| Handle::new(model.hash))
+    }
+
     pub fn model_handle_by_hash(&self, hash: u64) -> Option<Handle<Model>> {
         self.models.contains_key(&hash).then(|| Handle::new(hash))
     }
diff --git a/crates/dropbear-engine/src/entity.rs b/crates/dropbear-engine/src/entity.rs
index da0b2a7..f533dab 100644
--- a/crates/dropbear-engine/src/entity.rs
+++ b/crates/dropbear-engine/src/entity.rs
@@ -227,46 +227,22 @@ impl Transform {
             ui.label("Scale:");
         });
 
-        let mut uniform_scale = self.scale.x == self.scale.y
-            && self.scale.y == self.scale.z;
-
         ui.horizontal(|ui| {
-            if ui.checkbox(&mut uniform_scale, "Uniform").changed() {
-                if uniform_scale {
-                    let avg = (self.scale.x + self.scale.y + self.scale.z) / 3.0;
-                    self.scale = DVec3::splat(avg);
-                }
-            }
-        });
+            ui.colored_label(egui::Color32::from_rgb(200, 80, 80), "X:");
+            ui.add(egui::DragValue::new(&mut self.scale.x)
+                .speed(0.01)
+                .fixed_decimals(3));
 
-        if uniform_scale {
-            ui.horizontal(|ui| {
-                ui.label("XYZ:");
-                if ui.add(egui::DragValue::new(&mut self.scale.x)
-                    .speed(0.01)
-                    .fixed_decimals(3)).changed()
-                {
-                    self.scale = DVec3::splat(self.scale.x);
-                }
-            });
-        } else {
-            ui.horizontal(|ui| {
-                ui.colored_label(egui::Color32::from_rgb(200, 80, 80), "X:");
-                ui.add(egui::DragValue::new(&mut self.scale.x)
-                    .speed(0.01)
-                    .fixed_decimals(3));
-
-                ui.colored_label(egui::Color32::from_rgb(80, 200, 80), "Y:");
-                ui.add(egui::DragValue::new(&mut self.scale.y)
-                    .speed(0.01)
-                    .fixed_decimals(3));
-
-                ui.colored_label(egui::Color32::from_rgb(80, 120, 220), "Z:");
-                ui.add(egui::DragValue::new(&mut self.scale.z)
-                    .speed(0.01)
-                    .fixed_decimals(3));
-            });
-        }
+            ui.colored_label(egui::Color32::from_rgb(80, 200, 80), "Y:");
+            ui.add(egui::DragValue::new(&mut self.scale.y)
+                .speed(0.01)
+                .fixed_decimals(3));
+
+            ui.colored_label(egui::Color32::from_rgb(80, 120, 220), "Z:");
+            ui.add(egui::DragValue::new(&mut self.scale.z)
+                .speed(0.01)
+                .fixed_decimals(3));
+        });
     }
 }
 
diff --git a/crates/eucalyptus-core/build.rs b/crates/eucalyptus-core/build.rs
index 3cfe003..b18d041 100644
--- a/crates/eucalyptus-core/build.rs
+++ b/crates/eucalyptus-core/build.rs
@@ -10,6 +10,5 @@ fn main() -> anyhow::Result<()> {
 
     println!("cargo:rerun-if-changed=build.rs");
     println!("cargo:rerun-if-changed=src");
-    println!("cargo:rerun-if-changed=../../include/dropbear.h");
     Ok(())
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/animation/mod.rs b/crates/eucalyptus-core/src/animation/mod.rs
index b1fec32..9623fe0 100644
--- a/crates/eucalyptus-core/src/animation/mod.rs
+++ b/crates/eucalyptus-core/src/animation/mod.rs
@@ -23,13 +23,6 @@ impl Component for AnimationComponent {
         }
     }
 
-    async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
-    where
-        Self: Sized
-    {
-        Ok((Self::default(), ))
-    }
-
     fn init<'a>(
         ser: &'a Self::SerializedForm,
         _graphics: Arc<SharedGraphicsContext>,
@@ -37,7 +30,7 @@ impl Component for AnimationComponent {
         Box::pin(async move { Ok((ser.clone(), )) })
     }
 
-    fn update_component(&mut self, world: &World, entity: Entity, dt: f32, graphics: Arc<SharedGraphicsContext>) {
+    fn update_component(&mut self, world: &World, _physics: &mut crate::physics::PhysicsState, entity: Entity, dt: f32, graphics: Arc<SharedGraphicsContext>) {
         let Ok(renderer) = world.get::<&MeshRenderer>(entity) else {
             return;
         };
@@ -63,26 +56,49 @@ impl Component for AnimationComponent {
 }
 
 impl InspectableComponent for AnimationComponent {
-    fn inspect(&mut self, ui: &mut Ui) {
+    fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
         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| {
+                ui.label("Active Animation");
                 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));
+                let response = ui.add_enabled(
+                    enabled,
+                    egui::DragValue::new(&mut value).speed(1.0).range(0..=1_000_000),
+                );
 
-                if enabled && self.active_animation_index != Some(value) {
+                if enabled && response.changed() {
                     self.active_animation_index = Some(value);
                 }
             });
 
-            ui.label("Not implemented yet!");
-            // todo: complete some more
+            ui.horizontal(|ui| {
+                ui.label("Playing");
+                ui.checkbox(&mut self.is_playing, "");
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Looping");
+                ui.checkbox(&mut self.looping, "");
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Speed");
+                ui.add(egui::DragValue::new(&mut self.speed).speed(0.01).range(0.0..=10.0));
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Time");
+                ui.add(egui::DragValue::new(&mut self.time).speed(0.01).range(0.0..=1_000_000.0));
+                if ui.button("Reset").clicked() {
+                    self.time = 0.0;
+                }
+            });
         });
     }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-core/src/camera.rs b/crates/eucalyptus-core/src/camera.rs
index b568e3b..2380214 100644
--- a/crates/eucalyptus-core/src/camera.rs
+++ b/crates/eucalyptus-core/src/camera.rs
@@ -36,15 +36,6 @@ impl Component for Camera {
         }
     }
 
-    async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
-    where
-        Self: Sized
-    {
-        let comp = CameraComponent::new();
-        let cam = Camera::predetermined(graphics.clone(), Some("default camera"));
-        Ok((cam, comp))
-    }
-
     fn init<'a>(
         ser: &'a Self::SerializedForm,
         graphics: Arc<SharedGraphicsContext>,
@@ -59,7 +50,7 @@ impl Component for Camera {
         })
     }
 
-    fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, graphics: Arc<SharedGraphicsContext>) {
+    fn update_component(&mut self, _world: &World, _physics: &mut crate::physics::PhysicsState, _entity: Entity, _dt: f32, graphics: Arc<SharedGraphicsContext>) {
         self.update(graphics.clone())
     }
 
@@ -73,9 +64,79 @@ impl Component for Camera {
 }
 
 impl InspectableComponent for Camera {
-    fn inspect(&mut self, ui: &mut Ui) {
+    fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
         CollapsingHeader::new("Camera3D").show(ui, |ui| {
-            ui.label("Not implemented yet!");
+            let mut changed = false;
+
+            ui.horizontal(|ui| {
+                ui.label("Label");
+                changed |= ui.text_edit_singleline(&mut self.label).changed();
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Eye");
+                changed |= ui.add(egui::DragValue::new(&mut self.eye.x).speed(0.1)).changed();
+                changed |= ui.add(egui::DragValue::new(&mut self.eye.y).speed(0.1)).changed();
+                changed |= ui.add(egui::DragValue::new(&mut self.eye.z).speed(0.1)).changed();
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Target");
+                changed |= ui.add(egui::DragValue::new(&mut self.target.x).speed(0.1)).changed();
+                changed |= ui.add(egui::DragValue::new(&mut self.target.y).speed(0.1)).changed();
+                changed |= ui.add(egui::DragValue::new(&mut self.target.z).speed(0.1)).changed();
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Up");
+                changed |= ui.add(egui::DragValue::new(&mut self.up.x).speed(0.1)).changed();
+                changed |= ui.add(egui::DragValue::new(&mut self.up.y).speed(0.1)).changed();
+                changed |= ui.add(egui::DragValue::new(&mut self.up.z).speed(0.1)).changed();
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Aspect");
+                changed |= ui
+                    .add(egui::DragValue::new(&mut self.aspect).speed(0.01).range(0.1..=10.0))
+                    .changed();
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Near Plane");
+                changed |= ui
+                    .add(egui::DragValue::new(&mut self.znear).speed(0.01).range(0.01..=1000.0))
+                    .changed();
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Far Plane");
+                changed |= ui
+                    .add(egui::DragValue::new(&mut self.zfar).speed(1.0).range(0.1..=10000.0))
+                    .changed();
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("FOV");
+                changed |= ui
+                    .add(egui::Slider::new(&mut self.settings.fov_y, 1.0..=179.0).suffix("°"))
+                    .changed();
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Speed");
+                changed |= ui.add(egui::DragValue::new(&mut self.settings.speed).speed(0.1)).changed();
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Sensitivity");
+                changed |= ui
+                    .add(egui::DragValue::new(&mut self.settings.sensitivity).speed(0.001))
+                    .changed();
+            });
+
+            if changed {
+                self.update_view_proj();
+            }
         });
     }
 }
diff --git a/crates/eucalyptus-core/src/component.rs b/crates/eucalyptus-core/src/component.rs
index 641ce18..ccc2279 100644
--- a/crates/eucalyptus-core/src/component.rs
+++ b/crates/eucalyptus-core/src/component.rs
@@ -3,23 +3,26 @@ use std::collections::HashMap;
 use std::future::Future;
 use std::pin::Pin;
 use std::sync::Arc;
-use egui::{CollapsingHeader, Ui};
+use egui::{CollapsingHeader, ComboBox, DragValue, RichText, Ui, UiBuilder};
 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::procedural::{ProcObj, ProcedurallyGeneratedObject};
 use dropbear_engine::texture::Texture;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use crate::hierarchy::EntityTransformExt;
+use crate::physics::PhysicsState;
 use crate::states::{SerializedMaterialCustomisation, SerializedMeshRenderer};
 use crate::utils::ResolveReference;
 use downcast_rs::{Downcast, impl_downcast};
 
 pub use typetag::*;
 
+pub const DRAGGED_ASSET_ID: &str = "dragged_asset_reference";
+
 pub struct ComponentRegistry {
     /// Maps TypeId to ComponentDescriptor for quick lookups
     descriptors: HashMap<TypeId, ComponentDescriptor>,
@@ -59,11 +62,11 @@ type LoaderFn = Box<
         + Sync
 >;
 type ExtractorFn = Box<dyn Fn(&hecs::World, hecs::Entity) -> Option<Box<dyn SerializedComponent>> + Send + Sync>;
-type UpdateFn = Box<dyn Fn(&mut hecs::World, f32, Arc<SharedGraphicsContext>) + Send + Sync>;
+type UpdateFn = Box<dyn Fn(&mut hecs::World, &mut PhysicsState, f32, Arc<SharedGraphicsContext>) + Send + Sync>;
 type DefaultFn = Box<dyn Fn() -> Box<dyn SerializedComponent> + Send + Sync>;
 type RemoveFn = Box<dyn Fn(&mut hecs::World, hecs::Entity) + Send + Sync>;
 type FindFn = Box<dyn Fn(&hecs::World) -> Vec<hecs::Entity> + Send + Sync>;
-type InspectFn = Box<dyn Fn(&mut hecs::World, hecs::Entity, &mut egui::Ui) + Send + Sync>;
+type InspectFn = Box<dyn Fn(&mut hecs::World, hecs::Entity, &mut egui::Ui, Arc<SharedGraphicsContext>) + Send + Sync>;
 
 // fn inspect(&mut self, ui: &mut egui::Ui);
 
@@ -140,18 +143,18 @@ impl ComponentRegistry {
                 .collect()
         }));
 
-        self.updaters.insert(type_id, Box::new(|world, dt, graphics| {
+        self.updaters.insert(type_id, Box::new(|world, physics, dt, graphics| {
             let world_ptr = world as *mut hecs::World; // safe assuming world is kept at the DropbearAppBuilder application level (lifetime)
             let mut query = world.query::<(hecs::Entity, &mut T)>();
             for (entity, component) in query.iter() {
                 let world_ref = unsafe { &*world_ptr };
-                component.update_component(world_ref, entity, dt, graphics.clone());
+                component.update_component(world_ref, physics, entity, dt, graphics.clone());
             }
         }));
 
-        self.inspectors.insert(type_id, Box::new(|world, entity, ui| {
+        self.inspectors.insert(type_id, Box::new(|world, entity, ui, graphics| {
             if let Ok(mut comp) = world.get::<&mut T>(entity) {
-                comp.inspect(ui);
+                comp.inspect(ui, graphics);
             }
         }));
     }
@@ -323,11 +326,12 @@ impl ComponentRegistry {
     pub fn update_components(
         &self,
         world: &mut hecs::World,
+        physics: &mut PhysicsState,
         dt: f32,
         graphics: Arc<SharedGraphicsContext>,
     ) {
         for updater in self.updaters.values() {
-            updater(world, dt, graphics.clone());
+            updater(world, physics, dt, graphics.clone());
         }
     }
 
@@ -337,6 +341,7 @@ impl ComponentRegistry {
         world: &mut hecs::World,
         entity: hecs::Entity,
         ui: &mut egui::Ui,
+        graphics: Arc<SharedGraphicsContext>,
     ) {
         if let Ok(mut label) = world.get::<&mut crate::states::Label>(entity) {
             ui.horizontal(|ui| {
@@ -357,7 +362,7 @@ impl ComponentRegistry {
             };
 
             if let Some(inspector) = self.inspectors.get(&type_id) {
-                inspector(world, entity, ui);
+                inspector(world, entity, ui, graphics.clone());
             } else {
                 ui.label(format!("{} (no inspector)", desc.type_name));
             }
@@ -426,10 +431,6 @@ pub trait Component: Sync + Send {
 
     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::RequiredComponentTypes>;
-
     /// Converts [`Self::SerializedForm`] into a [`Component`] instance that can be added to
     /// `hecs::EntityBuilder` during scene initialisation.
     fn init<'a>(
@@ -438,21 +439,21 @@ pub trait Component: Sync + Send {
     ) -> ComponentInitFuture<'a, 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>);
+    fn update_component(&mut self, world: &hecs::World, physics: &mut PhysicsState, 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>;
 }
 
-#[typetag::serde]
-impl SerializedComponent for SerializedMeshRenderer {}
-
 pub trait InspectableComponent: Send + Sync {
     /// In the editor, how the component will be represented in the `Resource Viewer` dock.
-    fn inspect(&mut self, ui: &mut egui::Ui);
+    fn inspect(&mut self, ui: &mut egui::Ui, graphics: Arc<SharedGraphicsContext>);
 }
 
+#[typetag::serde]
+impl SerializedComponent for SerializedMeshRenderer {}
+
 // sample for MeshRenderer
 impl Component for MeshRenderer {
     type SerializedForm = SerializedMeshRenderer;
@@ -467,13 +468,6 @@ impl Component for MeshRenderer {
         }
     }
 
-    async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
-    where
-        Self: Sized
-    {
-        Ok((MeshRenderer::from_handle(Handle::NULL), ))
-    }
-
     fn init<'a>(
         ser: &'a Self::SerializedForm,
         graphics: Arc<SharedGraphicsContext>,
@@ -486,14 +480,14 @@ impl Component for MeshRenderer {
                     log::debug!("ResourceReferenceType is None, setting to `Handle::NULL`");
                     Handle::NULL
                 }
-                ResourceReferenceType::File(_) => {
+                ResourceReferenceType::File(reference) => {
                     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()),
+                        Some(reference),
                         ASSET_REGISTRY.clone(),
                     )
                     .await?
@@ -503,7 +497,7 @@ impl Component for MeshRenderer {
                     Model::load_from_memory_raw(
                         graphics.clone(),
                         bytes,
-                        Some(ser.label.as_str()),
+                        None,
                         ASSET_REGISTRY.clone(),
                     )
                     .await?
@@ -600,7 +594,7 @@ impl Component for MeshRenderer {
         })
     }
 
-    fn update_component(&mut self, world: &World, entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {
+    fn update_component(&mut self, world: &World, _physics: &mut PhysicsState, entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {
         if let Ok(transform) = world.query_one::<&EntityTransform>(entity).get() {
             self.update(&transform.propagate(&world, entity))
         }
@@ -705,9 +699,399 @@ impl Component for MeshRenderer {
 }
 
 impl InspectableComponent for MeshRenderer {
-    fn inspect(&mut self, ui: &mut egui::Ui) {
+    fn inspect(&mut self, ui: &mut egui::Ui, graphics: Arc<SharedGraphicsContext>) {
+        fn is_probably_model_uri(uri: &str) -> bool {
+            let uri = uri.to_ascii_lowercase();
+            uri.ends_with(".glb")
+                || uri.ends_with(".gltf")
+                || uri.ends_with(".obj")
+                || uri.ends_with(".fbx")
+        }
+
+        let apply_cuboid = |renderer: &mut MeshRenderer, size: [f32; 3]| {
+            let size_vec = glam::DVec3::new(size[0] as f64, size[1] as f64, size[2] as f64);
+            let handle = ProcedurallyGeneratedObject::cuboid(size_vec).build_model(
+                graphics.clone(),
+                None,
+                Some("Cuboid"),
+                ASSET_REGISTRY.clone(),
+            );
+
+            let size_bits = [size[0].to_bits(), size[1].to_bits(), size[2].to_bits()];
+            {
+                let mut registry = ASSET_REGISTRY.write();
+                if let Some(model) = registry.get_model(handle).cloned() {
+                    let mut model = model;
+                    model.path = ResourceReference::from_reference(ResourceReferenceType::ProcObj(
+                        ProcObj::Cuboid { size_bits },
+                    ));
+                    registry.update_model(handle, model);
+                }
+            }
+
+            renderer.set_model(handle);
+            renderer.reset_texture_override();
+        };
+
         CollapsingHeader::new("Mesh Renderer").show(ui, |ui| {
-            ui.label("Not implemented yet (MeshRenderer)");
+            let registry = ASSET_REGISTRY.read();
+            let current_model = registry.get_model(self.model()).cloned();
+            let model_list = registry.list_models();
+            drop(registry);
+
+            let model_reference = current_model
+                .as_ref()
+                .map(|model| model.path.clone())
+                .unwrap_or_default();
+            let model_label = current_model
+                .as_ref()
+                .map(|model| model.label.clone())
+                .unwrap_or_else(|| "None".to_string());
+
+            let model_title = match &model_reference.ref_type {
+                ResourceReferenceType::None => "None".to_string(),
+                ResourceReferenceType::ProcObj(ProcObj::Cuboid { .. }) => "Cuboid".to_string(),
+                _ => model_label,
+            };
+
+            ui.vertical(|ui| {
+                let expand_id = ui.make_persistent_id(format!("mesh_renderer_expand_{}", self.model().id));
+                let mut expanded = ui
+                    .ctx()
+                    .data_mut(|d| d.get_temp::<bool>(expand_id).unwrap_or(false));
+
+                let mut selected_model: Option<Handle<Model>> = None;
+                let mut choose_proc_cuboid = false;
+                let mut choose_none = false;
+
+                let (rect, response) = ui.allocate_exact_size(
+                    egui::vec2(ui.available_width(), 72.0),
+                    egui::Sense::click(),
+                );
+
+                let fill = if response.hovered() {
+                    ui.visuals().widgets.hovered.bg_fill
+                } else {
+                    ui.visuals().widgets.inactive.bg_fill
+                };
+
+                ui.painter().rect_filled(rect, 4.0, fill);
+                ui.painter().rect_stroke(
+                    rect,
+                    4.0,
+                    ui.visuals().widgets.inactive.bg_stroke,
+                    egui::StrokeKind::Inside,
+                );
+
+                let mut card_ui = ui.new_child(
+                    UiBuilder::new()
+                        .layout(egui::Layout::top_down(egui::Align::Min))
+                        .max_rect(rect),
+                );
+
+                card_ui.horizontal(|ui| {
+                    let arrow = if expanded { "v" } else { ">" };
+                    if ui.button(arrow).clicked() {
+                        expanded = !expanded;
+                    }
+
+                    ui.vertical(|ui| {
+                        ui.label(RichText::new(&model_title).strong());
+                        ui.label(
+                            RichText::new("Drop a model from the Asset Viewer")
+                                .small()
+                                .color(ui.visuals().weak_text_color()),
+                        );
+                    });
+
+                    ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
+                        ComboBox::from_id_salt("mesh_renderer_model_picker")
+                            .selected_text(&model_title)
+                            .show_ui(ui, |ui| {
+                                if ui
+                                    .selectable_label(
+                                        matches!(model_reference.ref_type, ResourceReferenceType::None),
+                                        "None",
+                                    )
+                                    .clicked()
+                                {
+                                    choose_none = true;
+                                }
+
+                                ui.separator();
+
+                                if ui
+                                    .selectable_label(
+                                        matches!(
+                                            model_reference.ref_type,
+                                            ResourceReferenceType::ProcObj(ProcObj::Cuboid { .. })
+                                        ),
+                                        "Cuboid",
+                                    )
+                                    .clicked()
+                                {
+                                    choose_proc_cuboid = true;
+                                }
+
+                                ui.separator();
+
+                                for (handle, label, _) in &model_list {
+                                    if handle.is_null() {
+                                        continue;
+                                    }
+                                    let is_selected = self.model() == *handle;
+                                    if ui.selectable_label(is_selected, label).clicked() {
+                                        selected_model = Some(*handle);
+                                    }
+                                }
+                            });
+                    });
+                });
+
+                let pointer_released = ui.input(|i| i.pointer.any_released());
+                if pointer_released && response.hovered() {
+                    let drag_id = egui::Id::new(DRAGGED_ASSET_ID);
+                    let dragged_reference = ui
+                        .ctx()
+                        .data_mut(|d| d.get_temp::<Option<ResourceReference>>(drag_id).unwrap_or(None));
+                    if let Some(reference) = dragged_reference {
+                        if let Some(uri) = reference.as_uri() {
+                            if is_probably_model_uri(uri) {
+                                if let Some(handle) =
+                                    ASSET_REGISTRY.read().get_model_handle_by_reference(&reference)
+                                {
+                                    self.set_model(handle);
+                                    self.reset_texture_override();
+                                }
+                                ui.ctx().data_mut(|d| {
+                                    d.insert_temp(drag_id, None::<ResourceReference>)
+                                });
+                            }
+                        }
+                    }
+                }
+
+                ui.ctx().data_mut(|d| d.insert_temp(expand_id, expanded));
+
+                if choose_proc_cuboid {
+                    let default_size = match &model_reference.ref_type {
+                        ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits }) => [
+                            f32::from_bits(size_bits[0]),
+                            f32::from_bits(size_bits[1]),
+                            f32::from_bits(size_bits[2]),
+                        ],
+                        _ => [1.0, 1.0, 1.0],
+                    };
+                    apply_cuboid(self, default_size);
+                } else if choose_none {
+                    self.set_model(Handle::NULL);
+                    self.reset_texture_override();
+                } else if let Some(handle) = selected_model {
+                    self.set_model(handle);
+                    self.reset_texture_override();
+                }
+
+                if expanded {
+                    ui.add_space(6.0);
+
+                    if let ResourceReferenceType::File(reference) = &model_reference.ref_type {
+                        if is_probably_model_uri(reference) {
+                            let mut import_scale = self.import_scale();
+                            ui.horizontal(|ui| {
+                                ui.label("Import Scale");
+                                let resp = ui.add(
+                                    DragValue::new(&mut import_scale)
+                                        .speed(0.01)
+                                        .range(0.0001..=10_000.0),
+                                );
+
+                                if resp.changed() {
+                                    self.set_import_scale(import_scale);
+                                }
+
+                                if ui.button("Reset").clicked() {
+                                    self.set_import_scale(1.0);
+                                }
+                            });
+                            ui.add_space(6.0);
+                        }
+                    }
+
+                    if let ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits }) = &model_reference.ref_type {
+                        let mut size = [
+                            f32::from_bits(size_bits[0]),
+                            f32::from_bits(size_bits[1]),
+                            f32::from_bits(size_bits[2]),
+                        ];
+
+                        ui.label(RichText::new("Cuboid").strong());
+                        ui.horizontal(|ui| {
+                            ui.label("Extents:");
+                            let mut changed = false;
+                            ui.label("X");
+                            changed |= ui
+                                .add(DragValue::new(&mut size[0]).speed(0.05).range(0.01..=10_000.0))
+                                .changed();
+                            ui.label("Y");
+                            changed |= ui
+                                .add(DragValue::new(&mut size[1]).speed(0.05).range(0.01..=10_000.0))
+                                .changed();
+                            ui.label("Z");
+                            changed |= ui
+                                .add(DragValue::new(&mut size[2]).speed(0.05).range(0.01..=10_000.0))
+                                .changed();
+
+                            if changed {
+                                apply_cuboid(self, size);
+                            }
+                        });
+
+                        ui.separator();
+                    }
+
+                    ui.label("Materials");
+
+                    for (material_name, material) in self.material_snapshot.iter_mut() {
+                        ui.separator();
+                        ui.label(material_name.as_str());
+
+                        let mut tint_rgb = [material.tint[0], material.tint[1], material.tint[2]];
+                        if egui::color_picker::color_edit_button_rgb(ui, &mut tint_rgb).changed() {
+                            material.tint[0] = tint_rgb[0];
+                            material.tint[1] = tint_rgb[1];
+                            material.tint[2] = tint_rgb[2];
+                        }
+
+                        let mut emissive_rgb = [
+                            material.emissive_factor[0],
+                            material.emissive_factor[1],
+                            material.emissive_factor[2],
+                        ];
+                        if egui::color_picker::color_edit_button_rgb(ui, &mut emissive_rgb).changed() {
+                            material.emissive_factor[0] = emissive_rgb[0];
+                            material.emissive_factor[1] = emissive_rgb[1];
+                            material.emissive_factor[2] = emissive_rgb[2];
+                        }
+
+                        ui.horizontal(|ui| {
+                            ui.label("Metallic");
+                            ui.add(DragValue::new(&mut material.metallic_factor)
+                                .speed(0.01)
+                                .range(0.0..=1.0));
+                        });
+
+                        ui.horizontal(|ui| {
+                            ui.label("Roughness");
+                            ui.add(DragValue::new(&mut material.roughness_factor)
+                                .speed(0.01)
+                                .range(0.0..=1.0));
+                        });
+
+                        ui.horizontal(|ui| {
+                            ui.label("Occlusion Strength");
+                            ui.add(DragValue::new(&mut material.occlusion_strength)
+                                .speed(0.01)
+                                .range(0.0..=1.0));
+                        });
+
+                        ui.horizontal(|ui| {
+                            ui.label("Normal Scale");
+                            ui.add(DragValue::new(&mut material.normal_scale)
+                                .speed(0.01)
+                                .range(0.0..=10.0));
+                        });
+
+                        ui.horizontal(|ui| {
+                            ui.label("Alpha Mode");
+                            egui::ComboBox::from_id_salt(format!("alpha_mode_{}", material_name))
+                                .selected_text(match material.alpha_mode {
+                                    dropbear_engine::model::AlphaMode::Opaque => "Opaque",
+                                    dropbear_engine::model::AlphaMode::Mask => "Mask",
+                                    dropbear_engine::model::AlphaMode::Blend => "Blend",
+                                })
+                                .show_ui(ui, |ui| {
+                                    ui.selectable_value(
+                                        &mut material.alpha_mode,
+                                        dropbear_engine::model::AlphaMode::Opaque,
+                                        "Opaque",
+                                    );
+                                    ui.selectable_value(
+                                        &mut material.alpha_mode,
+                                        dropbear_engine::model::AlphaMode::Mask,
+                                        "Mask",
+                                    );
+                                    ui.selectable_value(
+                                        &mut material.alpha_mode,
+                                        dropbear_engine::model::AlphaMode::Blend,
+                                        "Blend",
+                                    );
+                                });
+                        });
+
+                        let mut cutoff = material.alpha_cutoff.unwrap_or(0.5);
+                        ui.horizontal(|ui| {
+                            ui.label("Alpha Cutoff");
+                            if ui.add(DragValue::new(&mut cutoff)
+                                .speed(0.01)
+                                .range(0.0..=1.0))
+                                .changed()
+                            {
+                                material.alpha_cutoff = Some(cutoff);
+                            }
+                        });
+
+                        ui.horizontal(|ui| {
+                            ui.label("Double Sided");
+                            ui.checkbox(&mut material.double_sided, "");
+                        });
+
+                        ui.horizontal(|ui| {
+                            ui.label("Wrap");
+                            egui::ComboBox::from_id_salt(format!("wrap_mode_{}", material_name))
+                                .selected_text(match material.wrap_mode {
+                                    dropbear_engine::texture::TextureWrapMode::Repeat => "Repeat",
+                                    dropbear_engine::texture::TextureWrapMode::Clamp => "Clamp",
+                                })
+                                .show_ui(ui, |ui| {
+                                    ui.selectable_value(
+                                        &mut material.wrap_mode,
+                                        dropbear_engine::texture::TextureWrapMode::Repeat,
+                                        "Repeat",
+                                    );
+                                    ui.selectable_value(
+                                        &mut material.wrap_mode,
+                                        dropbear_engine::texture::TextureWrapMode::Clamp,
+                                        "Clamp",
+                                    );
+                                });
+                        });
+
+                        ui.horizontal(|ui| {
+                            ui.label("UV Tiling");
+                            ui.add(
+                                DragValue::new(&mut material.uv_tiling[0])
+                                    .speed(0.05)
+                                    .range(0.01..=10_000.0),
+                            );
+                            ui.label("x");
+                            ui.add(
+                                DragValue::new(&mut material.uv_tiling[1])
+                                    .speed(0.05)
+                                    .range(0.01..=10_000.0),
+                            );
+                        });
+
+                        let mut texture_tag = material.texture_tag.clone().unwrap_or_default();
+                        if ui.text_edit_singleline(&mut texture_tag).changed() {
+                            material.texture_tag = if texture_tag.trim().is_empty() {
+                                None
+                            } else {
+                                Some(texture_tag)
+                            };
+                        }
+                    }
+                }
+            });
         });
     }
 }
diff --git a/crates/eucalyptus-core/src/lib.rs b/crates/eucalyptus-core/src/lib.rs
index b3f2f23..1e0907e 100644
--- a/crates/eucalyptus-core/src/lib.rs
+++ b/crates/eucalyptus-core/src/lib.rs
@@ -31,12 +31,11 @@ use dropbear_engine::camera::Camera;
 use dropbear_engine::entity::{EntityTransform, MeshRenderer};
 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, SerializedLight, Script, SerializedMeshRenderer};
+use crate::states::{Script};
 
 /// The appdata directory for storing any information.
 ///
diff --git a/crates/eucalyptus-core/src/lighting.rs b/crates/eucalyptus-core/src/lighting.rs
index 43dcf98..47c6ca4 100644
--- a/crates/eucalyptus-core/src/lighting.rs
+++ b/crates/eucalyptus-core/src/lighting.rs
@@ -1,10 +1,11 @@
 use std::sync::Arc;
-use egui::{CollapsingHeader, Ui};
+use egui::{CollapsingHeader, DragValue, 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::asset::ASSET_REGISTRY;
 use dropbear_engine::entity::{EntityTransform, Transform};
 use dropbear_engine::lighting::{Light, LightComponent, LightType};
 use glam::{DQuat, DVec3};
@@ -31,15 +32,6 @@ impl Component for Light {
         }
     }
 
-    async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
-    where
-        Self: Sized
-    {
-        let comp = LightComponent::default();
-        let light = Light::new(graphics.clone(), comp.clone(), None).await;
-        Ok((light, comp))
-    }
-
     fn init<'a>(
         ser: &'a Self::SerializedForm,
         graphics: Arc<SharedGraphicsContext>,
@@ -55,7 +47,7 @@ impl Component for Light {
         })
     }
 
-    fn update_component(&mut self, world: &World, entity: Entity, _dt: f32, graphics: Arc<SharedGraphicsContext>) {
+    fn update_component(&mut self, world: &World, _physics: &mut crate::physics::PhysicsState, entity: Entity, _dt: f32, graphics: Arc<SharedGraphicsContext>) {
         if let Ok(comp) = world.query_one::<&LightComponent>(entity).get() {
             self.update(&graphics, comp);
         }
@@ -76,9 +68,89 @@ impl Component for Light {
 }
 
 impl InspectableComponent for Light {
-    fn inspect(&mut self, ui: &mut Ui) {
+    fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
         CollapsingHeader::new("Light").default_open(true).show(ui, |ui| {
-            ui.label("Not implemented yet"); 
+            ui.horizontal(|ui| {
+                ui.label("Label");
+                ui.text_edit_singleline(&mut self.label);
+            });
+
+            let registry = ASSET_REGISTRY.read();
+            let current_label = registry
+                .get_label_from_model_handle(self.cube_model)
+                .unwrap_or_else(|| "(unlabeled)".to_string());
+
+            ui.horizontal(|ui| {
+                ui.label("Cube Model");
+                ui.label(current_label.as_str());
+            });
+
+            let label_id = ui.make_persistent_id("light_cube_model_label");
+            let mut model_label = ui
+                .data_mut(|d| d.get_temp::<String>(label_id))
+                .unwrap_or_else(|| current_label.clone());
+
+            let mut invalid_label = false;
+            ui.horizontal(|ui| {
+                ui.label("Model Label");
+                let response = ui.text_edit_singleline(&mut model_label);
+                if response.changed() {
+                    ui.data_mut(|d| d.insert_temp(label_id, model_label.clone()));
+                }
+
+                if ui.button("Apply").clicked() {
+                    if let Some(handle) = registry.get_model_handle_from_label(&model_label) {
+                        self.cube_model = handle;
+                        ui.data_mut(|d| d.insert_temp(label_id, model_label.clone()));
+                    } else {
+                        invalid_label = true;
+                    }
+                }
+            });
+
+            if invalid_label {
+                ui.label("Unknown model label");
+            }
+
+            ui.add_space(6.0);
+            ui.label("Uniform");
+
+            ui.horizontal(|ui| {
+                ui.label("Position");
+                ui.add(DragValue::new(&mut self.uniform.position[0]).speed(0.01));
+                ui.add(DragValue::new(&mut self.uniform.position[1]).speed(0.01));
+                ui.add(DragValue::new(&mut self.uniform.position[2]).speed(0.01));
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Direction");
+                ui.add(DragValue::new(&mut self.uniform.direction[0]).speed(0.01));
+                ui.add(DragValue::new(&mut self.uniform.direction[1]).speed(0.01));
+                ui.add(DragValue::new(&mut self.uniform.direction[2]).speed(0.01));
+            });
+
+            let mut colour_rgb = [
+                self.uniform.colour[0],
+                self.uniform.colour[1],
+                self.uniform.colour[2],
+            ];
+            if egui::color_picker::color_edit_button_rgb(ui, &mut colour_rgb).changed() {
+                self.uniform.colour[0] = colour_rgb[0];
+                self.uniform.colour[1] = colour_rgb[1];
+                self.uniform.colour[2] = colour_rgb[2];
+            }
+
+            ui.horizontal(|ui| {
+                ui.label("Attenuation");
+                ui.add(DragValue::new(&mut self.uniform.constant).speed(0.01));
+                ui.add(DragValue::new(&mut self.uniform.linear).speed(0.01));
+                ui.add(DragValue::new(&mut self.uniform.quadratic).speed(0.01));
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Cutoff");
+                ui.add(DragValue::new(&mut self.uniform.cutoff).speed(0.01));
+            });
         });
     }
 }
diff --git a/crates/eucalyptus-core/src/physics/collider.rs b/crates/eucalyptus-core/src/physics/collider.rs
index a1e5488..d7790e1 100644
--- a/crates/eucalyptus-core/src/physics/collider.rs
+++ b/crates/eucalyptus-core/src/physics/collider.rs
@@ -27,7 +27,7 @@ use ::jni::JNIEnv;
 use rapier3d::prelude::ColliderBuilder;
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
-use egui::{CollapsingHeader, Ui};
+use egui::{CollapsingHeader, ComboBox, Ui};
 use crate::physics::collider::shared::{get_collider, get_collider_mut};
 use crate::scripting::native::DropbearNativeError;
 use crate::scripting::result::DropbearNativeResult;
@@ -72,13 +72,6 @@ impl Component for ColliderGroup {
         }
     }
 
-    async fn first_time(_: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
-    where
-        Self: Sized
-    {
-        Ok((Self::new(), ))
-    }
-
     fn init<'a>(
         ser: &'a Self::SerializedForm,
         _graphics: Arc<SharedGraphicsContext>,
@@ -86,7 +79,17 @@ impl Component for ColliderGroup {
         Box::pin(async move { Ok((ser.clone(), )) })
     }
 
-    fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
+    fn update_component(&mut self, world: &World, _physics: &mut PhysicsState, entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {
+        let Ok(label) = world.get::<&Label>(entity) else {
+            return;
+        };
+
+        for collider in &mut self.colliders {
+            if collider.entity != *label {
+                collider.entity = Label::new(label.as_str());
+            }
+        }
+    }
 
     fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
@@ -94,9 +97,34 @@ impl Component for ColliderGroup {
 }
 
 impl InspectableComponent for ColliderGroup {
-    fn inspect(&mut self, ui: &mut Ui) {
+    fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
         CollapsingHeader::new("Colliders").default_open(true).show(ui, |ui| {
-            ui.label("Not implemented yet!");
+            let mut remove_index: Option<usize> = None;
+
+            for (index, collider) in self.colliders.iter_mut().enumerate() {
+                ui.push_id(index, |ui| {
+                    CollapsingHeader::new(format!("Collider {}", index + 1))
+                        .default_open(true)
+                        .show(ui, |ui| {
+                            collider.inspect(ui);
+
+                            ui.add_space(6.0);
+                            if ui.button("Remove Collider").clicked() {
+                                remove_index = Some(index);
+                            }
+                        });
+                });
+
+                ui.add_space(6.0);
+            }
+
+            if let Some(index) = remove_index {
+                self.colliders.remove(index);
+            }
+
+            if ui.button("Add Collider").clicked() {
+                self.colliders.push(Collider::new());
+            }
         });
     }
 }
@@ -140,6 +168,163 @@ pub struct Collider {
     pub rotation: [f32; 3],
 }
 
+impl Collider {
+    pub fn inspect(&mut self, ui: &mut egui::Ui) {
+        ui.vertical(|ui| {
+            ui.label("Shape:");
+            let current_shape = self.shape_type_name();
+            ComboBox::from_id_salt("collider_shape")
+                .selected_text(current_shape)
+                .show_ui(ui, |ui| {
+                    if ui.selectable_label(current_shape == "Box", "Box").clicked() {
+                        if current_shape != "Box" {
+                            self.shape = ColliderShape::Box { half_extents: [0.5, 0.5, 0.5].into() };
+                        }
+                    }
+                    if ui.selectable_label(current_shape == "Sphere", "Sphere").clicked() {
+                        if current_shape != "Sphere" {
+                            self.shape = ColliderShape::Sphere { radius: 0.5 };
+                        }
+                    }
+                    if ui.selectable_label(current_shape == "Capsule", "Capsule").clicked() {
+                        if current_shape != "Capsule" {
+                            self.shape = ColliderShape::Capsule { half_height: 0.5, radius: 0.25 };
+                        }
+                    }
+                    if ui.selectable_label(current_shape == "Cylinder", "Cylinder").clicked() {
+                        if current_shape != "Cylinder" {
+                            self.shape = ColliderShape::Cylinder { half_height: 0.5, radius: 0.25 };
+                        }
+                    }
+                    if ui.selectable_label(current_shape == "Cone", "Cone").clicked() {
+                        if current_shape != "Cone" {
+                            self.shape = ColliderShape::Cone { half_height: 0.5, radius: 0.25 };
+                        }
+                    }
+                });
+
+            ui.add_space(8.0);
+
+            match &mut self.shape {
+                ColliderShape::Box { half_extents } => {
+                    ui.label("Half Extents:");
+                    ui.horizontal(|ui| {
+                        ui.label("X:");
+                        ui.add(egui::DragValue::new(&mut half_extents.x)
+                            .speed(0.01));
+                        ui.label("Y:");
+                        ui.add(egui::DragValue::new(&mut half_extents.y)
+                            .speed(0.01));
+                        ui.label("Z:");
+                        ui.add(egui::DragValue::new(&mut half_extents.z)
+                            .speed(0.01));
+                    });
+                }
+                ColliderShape::Sphere { radius } => {
+                    ui.horizontal(|ui| {
+                        ui.label("Radius:");
+                        ui.add(egui::DragValue::new(radius)
+                            .speed(0.01));
+                    });
+                }
+                ColliderShape::Capsule { half_height, radius } => {
+                    ui.horizontal(|ui| {
+                        ui.label("Half Height:");
+                        ui.add(egui::DragValue::new(half_height)
+                            .speed(0.01));
+                    });
+                    ui.horizontal(|ui| {
+                        ui.label("Radius:");
+                        ui.add(egui::DragValue::new(radius)
+                            .speed(0.01));
+                    });
+                }
+                ColliderShape::Cylinder { half_height, radius } => {
+                    ui.horizontal(|ui| {
+                        ui.label("Half Height:");
+                        ui.add(egui::DragValue::new(half_height)
+                            .speed(0.01));
+                    });
+                    ui.horizontal(|ui| {
+                        ui.label("Radius:");
+                        ui.add(egui::DragValue::new(radius)
+                            .speed(0.01));
+                    });
+                }
+                ColliderShape::Cone { half_height, radius } => {
+                    ui.horizontal(|ui| {
+                        ui.label("Half Height:");
+                        ui.add(egui::DragValue::new(half_height)
+                            .speed(0.01));
+                    });
+                    ui.horizontal(|ui| {
+                        ui.label("Radius:");
+                        ui.add(egui::DragValue::new(radius)
+                            .speed(0.01));
+                    });
+                }
+            }
+
+            ui.add_space(8.0);
+
+            ui.separator();
+            ui.label("Physical Properties:");
+
+            ui.horizontal(|ui| {
+                ui.label("Density:");
+                ui.add(egui::DragValue::new(&mut self.density)
+                    .speed(0.01));
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Friction:");
+                ui.add(egui::Slider::new(&mut self.friction, 0.0..=2.0));
+            });
+
+            ui.horizontal(|ui| {
+                ui.label("Restitution:");
+                ui.add(egui::Slider::new(&mut self.restitution, 0.0..=1.0));
+            });
+
+            ui.checkbox(&mut self.is_sensor, "Is Sensor (No physical response)");
+
+            ui.add_space(8.0);
+
+            ui.separator();
+            ui.label("Local Offset:");
+
+            ui.label("Translation:");
+            ui.horizontal(|ui| {
+                ui.label("X:");
+                ui.add(egui::DragValue::new(&mut self.translation[0]).speed(0.01));
+                ui.label("Y:");
+                ui.add(egui::DragValue::new(&mut self.translation[1]).speed(0.01));
+                ui.label("Z:");
+                ui.add(egui::DragValue::new(&mut self.translation[2]).speed(0.01));
+            });
+
+            ui.label("Rotation (degrees):");
+            ui.horizontal(|ui| {
+                ui.label("X:");
+                let mut deg_x = self.rotation[0].to_degrees();
+                if ui.add(egui::DragValue::new(&mut deg_x).speed(1.0)).changed() {
+                    self.rotation[0] = deg_x.to_radians();
+                }
+                ui.label("Y:");
+                let mut deg_y = self.rotation[1].to_degrees();
+                if ui.add(egui::DragValue::new(&mut deg_y).speed(1.0)).changed() {
+                    self.rotation[1] = deg_y.to_radians();
+                }
+                ui.label("Z:");
+                let mut deg_z = self.rotation[2].to_degrees();
+                if ui.add(egui::DragValue::new(&mut deg_z).speed(1.0)).changed() {
+                    self.rotation[2] = deg_z.to_radians();
+                }
+            });
+        });
+    }
+}
+
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum ColliderShapeType {
     Box,
diff --git a/crates/eucalyptus-core/src/physics/kcc.rs b/crates/eucalyptus-core/src/physics/kcc.rs
index 4cf20f1..8abc131 100644
--- a/crates/eucalyptus-core/src/physics/kcc.rs
+++ b/crates/eucalyptus-core/src/physics/kcc.rs
@@ -3,12 +3,12 @@
 
 pub mod character_collision;
 
-use rapier3d::control::{CharacterCollision, KinematicCharacterController};
+use rapier3d::control::{CharacterAutostep, CharacterCollision, CharacterLength, KinematicCharacterController};
 use serde::{Deserialize, Serialize};
 use crate::states::Label;
 use std::any::Any;
 use std::sync::Arc;
-use egui::Ui;
+use egui::{ComboBox, DragValue, Ui};
 use hecs::{Entity, World};
 use crate::physics::PhysicsState;
 use crate::scripting::jni::utils::ToJObject;
@@ -18,6 +18,7 @@ use crate::types::{IndexNative, NQuaternion, NVector3};
 use jni::objects::{JObject, JValue};
 use jni::JNIEnv;
 use rapier3d::dynamics::RigidBodyType;
+use rapier3d::na::{UnitVector3, Vector3};
 use rapier3d::math::Rotation;
 use rapier3d::prelude::QueryFilter;
 use dropbear_engine::animation::AnimationComponent;
@@ -50,13 +51,6 @@ impl Component for KCC {
         }
     }
 
-    async fn first_time(_: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
-    where
-        Self: Sized
-    {
-        Ok((Self::default(), ))
-    }
-
     fn init<'a>(
         ser: &'a Self::SerializedForm,
         _: Arc<SharedGraphicsContext>,
@@ -64,7 +58,7 @@ impl Component for KCC {
         Box::pin(async move { Ok((ser.clone(), )) })
     }
 
-    fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
+    fn update_component(&mut self, _world: &World, _physics: &mut PhysicsState, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
 
     fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
@@ -72,9 +66,125 @@ impl Component for KCC {
 }
 
 impl InspectableComponent for KCC {
-    fn inspect(&mut self, ui: &mut Ui) {
+    fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
         egui::CollapsingHeader::new("Kinematic Character Controller").default_open(true).show(ui, |ui| {
-            ui.label("Not implemented yet!")
+            fn edit_character_length(ui: &mut Ui, id_salt: impl std::hash::Hash, value: &mut CharacterLength, text: &str) {
+                ui.horizontal(|ui| {
+                    ui.label(text);
+
+                    let (mut kind, mut v) = match *value {
+                        CharacterLength::Absolute(x) => (0, x),
+                        CharacterLength::Relative(x) => (1, x),
+                    };
+
+                    ComboBox::from_id_salt(id_salt)
+                        .selected_text(match kind {
+                            0 => "Absolute",
+                            _ => "Relative",
+                        })
+                        .show_ui(ui, |ui| {
+                            ui.selectable_value(&mut kind, 0, "Absolute");
+                            ui.selectable_value(&mut kind, 1, "Relative");
+                        });
+
+                    ui.add(DragValue::new(&mut v).speed(0.01));
+
+                    *value = if kind == 0 {
+                        CharacterLength::Absolute(v)
+                    } else {
+                        CharacterLength::Relative(v)
+                    };
+                });
+            }
+
+            ui.vertical(|ui| {
+                ui.label("Up Vector:");
+                let up = self.controller.up;
+                let mut up_v = Vector3::new(up.x, up.y, up.z);
+
+                ui.horizontal(|ui| {
+                    ui.label("X:");
+                    ui.add(DragValue::new(&mut up_v.x).speed(0.01));
+                    ui.label("Y:");
+                    ui.add(DragValue::new(&mut up_v.y).speed(0.01));
+                    ui.label("Z:");
+                    ui.add(DragValue::new(&mut up_v.z).speed(0.01));
+                });
+
+                if up_v.norm_squared() > 0.0 {
+                    self.controller.up = UnitVector3::new_normalize(up_v).into();
+                }
+
+                ui.add_space(8.0);
+
+                edit_character_length(ui, "kcc_offset", &mut self.controller.offset, "Offset:");
+
+                ui.add_space(8.0);
+                ui.separator();
+
+                ui.checkbox(&mut self.controller.slide, "Slide against floor?");
+
+                ui.label("Slope Angles (degrees):");
+                ui.horizontal(|ui| {
+                    ui.label("Max climb:");
+                    let mut deg = self.controller.max_slope_climb_angle.to_degrees();
+                    if ui.add(DragValue::new(&mut deg).speed(1.0)).changed() {
+                        self.controller.max_slope_climb_angle = deg.to_radians();
+                    }
+                });
+                ui.horizontal(|ui| {
+                    ui.label("Min slide:");
+                    let mut deg = self.controller.min_slope_slide_angle.to_degrees();
+                    if ui.add(DragValue::new(&mut deg).speed(1.0)).changed() {
+                        self.controller.min_slope_slide_angle = deg.to_radians();
+                    }
+                });
+
+                ui.add_space(8.0);
+
+                ui.horizontal(|ui| {
+                    ui.label("Normal nudge:");
+                    ui.add(
+                        egui::Slider::new(&mut self.controller.normal_nudge_factor, 0.001..=0.5)
+                            .logarithmic(true)
+                            .smallest_positive(0.001)
+                            .largest_finite(0.5)
+                            .suffix(" m")
+                    );
+                });
+
+                ui.add_space(8.0);
+                ui.separator();
+
+                let mut enable_snap = self.controller.snap_to_ground.is_some();
+                ui.checkbox(&mut enable_snap, "Snap to ground");
+                if enable_snap && self.controller.snap_to_ground.is_none() {
+                    self.controller.snap_to_ground = Some(CharacterLength::Absolute(0.2));
+                } else if !enable_snap {
+                    self.controller.snap_to_ground = None;
+                }
+
+                if let Some(ref mut snap) = self.controller.snap_to_ground {
+                    edit_character_length(ui, "kcc_snap_to_ground", snap, "Snap distance:");
+                }
+
+                ui.add_space(8.0);
+                ui.separator();
+
+                let mut enable_autostep = self.controller.autostep.is_some();
+                ui.checkbox(&mut enable_autostep, "Enable autostep");
+                if enable_autostep && self.controller.autostep.is_none() {
+                    self.controller.autostep = Some(CharacterAutostep::default());
+                } else if !enable_autostep {
+                    self.controller.autostep = None;
+                }
+
+                if let Some(step) = &mut self.controller.autostep {
+                    edit_character_length(ui, "kcc_autostep_max_height", &mut step.max_height, "Max height:");
+                    edit_character_length(ui, "kcc_autostep_min_width", &mut step.min_width, "Min width:");
+                    ui.checkbox(&mut step.include_dynamic_bodies, "Include dynamic bodies");
+                }
+            });
         });
     }
 }
diff --git a/crates/eucalyptus-core/src/physics/rigidbody.rs b/crates/eucalyptus-core/src/physics/rigidbody.rs
index eba8c0a..5f5844b 100644
--- a/crates/eucalyptus-core/src/physics/rigidbody.rs
+++ b/crates/eucalyptus-core/src/physics/rigidbody.rs
@@ -8,12 +8,12 @@ use std::any::Any;
 use std::sync::Arc;
 use ::jni::objects::{JObject, JValue};
 use ::jni::JNIEnv;
-use egui::{CollapsingHeader, Ui};
+use egui::{CollapsingHeader, ComboBox, DragValue, 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::component::{Component, ComponentDescriptor, InspectableComponent, SerializedComponent};
 use crate::types::{IndexNative, NCollider, RigidBodyContext, NVector3};
 use crate::physics::PhysicsState;
 use crate::ptr::{PhysicsStatePtr, WorldPtr};
@@ -202,13 +202,6 @@ impl Component for RigidBody {
 		}
 	}
 
-	async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
-	where
-		Self: Sized
-	{
-		Ok((Self::default(), ))
-	}
-
 	fn init<'a>(
 		ser: &'a Self::SerializedForm,
 		_graphics: Arc<SharedGraphicsContext>,
@@ -216,17 +209,98 @@ impl Component for RigidBody {
 		Box::pin(async move { Ok((ser.clone(), )) })
 	}
 
-	fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
+	fn update_component(&mut self, _world: &World, _physics: &mut PhysicsState, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
 
 	fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
 		Box::new(self.clone())
 	}
 }
 
-impl crate::component::InspectableComponent for RigidBody {
-	fn inspect(&mut self, ui: &mut Ui) {
+impl InspectableComponent for RigidBody {
+	fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
 		CollapsingHeader::new("RigidBody").default_open(true).show(ui, |ui| {
-			ui.label("Not implemented yet!");
+			ui.vertical(|ui| {
+				let mut selected = self.mode;
+				ComboBox::from_id_salt("rb")
+					.selected_text(format!("{:?}", self.mode))
+					.show_ui(ui, |ui| {
+						ui.selectable_value(&mut selected, RigidBodyMode::Dynamic, "Dynamic");
+						ui.selectable_value(&mut selected, RigidBodyMode::Fixed, "Fixed");
+						ui.selectable_value(&mut selected, RigidBodyMode::KinematicPosition, "Kinematic Position");
+						ui.selectable_value(&mut selected, RigidBodyMode::KinematicVelocity, "Kinematic Velocity");
+					});
+
+				if selected != self.mode {
+					self.mode = selected;
+				}
+
+				ui.add_space(8.0);
+
+				ui.horizontal(|ui| {
+					ui.label("Gravity Scale:");
+					ui.add(DragValue::new(&mut self.gravity_scale)
+						.speed(0.1)
+						.range(0.0..=10.0));
+				});
+
+				ui.checkbox(&mut self.can_sleep, "Can Sleep");
+				ui.checkbox(&mut self.sleeping, "Initially sleeping?");
+				ui.checkbox(&mut self.ccd_enabled, "CCD Enabled");
+
+				ui.add_space(8.0);
+
+				ui.label("Linear Velocity:");
+				ui.horizontal(|ui| {
+					ui.label("X:");
+					ui.add(DragValue::new(&mut self.linvel[0]).speed(0.1));
+					ui.label("Y:");
+					ui.add(DragValue::new(&mut self.linvel[1]).speed(0.1));
+					ui.label("Z:");
+					ui.add(DragValue::new(&mut self.linvel[2]).speed(0.1));
+				});
+
+				ui.label("Angular Velocity:");
+				ui.horizontal(|ui| {
+					ui.label("X:");
+					ui.add(DragValue::new(&mut self.angvel[0]).speed(0.1));
+					ui.label("Y:");
+					ui.add(DragValue::new(&mut self.angvel[1]).speed(0.1));
+					ui.label("Z:");
+					ui.add(DragValue::new(&mut self.angvel[2]).speed(0.1));
+				});
+
+				ui.add_space(8.0);
+
+				ui.horizontal(|ui| {
+					ui.label("Linear Damping:");
+					ui.add(DragValue::new(&mut self.linear_damping)
+						.speed(0.01)
+						.range(0.0..=10.0));
+				});
+
+				ui.horizontal(|ui| {
+					ui.label("Angular Damping:");
+					ui.add(DragValue::new(&mut self.angular_damping)
+						.speed(0.01)
+						.range(0.0..=10.0));
+				});
+
+				ui.add_space(8.0);
+
+				ui.label("Lock Translation:");
+				ui.horizontal(|ui| {
+					ui.checkbox(&mut self.lock_translation.x, "X");
+					ui.checkbox(&mut self.lock_translation.y, "Y");
+					ui.checkbox(&mut self.lock_translation.z, "Z");
+				});
+
+				ui.label("Lock Rotation:");
+				ui.horizontal(|ui| {
+					ui.checkbox(&mut self.lock_rotation.x, "X");
+					ui.checkbox(&mut self.lock_rotation.y, "Y");
+					ui.checkbox(&mut self.lock_rotation.z, "Z");
+				});
+			});
 		});
 	}
 }
diff --git a/crates/eucalyptus-core/src/properties.rs b/crates/eucalyptus-core/src/properties.rs
index c2bb0fd..d1a9711 100644
--- a/crates/eucalyptus-core/src/properties.rs
+++ b/crates/eucalyptus-core/src/properties.rs
@@ -37,13 +37,6 @@ impl Component for CustomProperties {
         }
     }
 
-    async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
-    where
-        Self: Sized
-    {
-        Ok((Self::new(), ))
-    }
-
     fn init<'a>(
         ser: &'a Self::SerializedForm,
         _graphics: Arc<SharedGraphicsContext>,
@@ -51,7 +44,7 @@ impl Component for CustomProperties {
         Box::pin(async move { Ok((ser.clone(), )) })
     }
 
-    fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
+    fn update_component(&mut self, _world: &World, _physics: &mut crate::physics::PhysicsState, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
 
     fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
@@ -59,7 +52,7 @@ impl Component for CustomProperties {
 }
 
 impl InspectableComponent for CustomProperties {
-    fn inspect(&mut self, ui: &mut Ui) {
+    fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
         CollapsingHeader::new("Custom Properties").default_open(true).show(ui, |ui| {
             ui.vertical(|ui| {
                 Grid::new("properties").striped(true).show(ui, |ui| {
diff --git a/crates/eucalyptus-core/src/states.rs b/crates/eucalyptus-core/src/states.rs
index c60e60f..265bee0 100644
--- a/crates/eucalyptus-core/src/states.rs
+++ b/crates/eucalyptus-core/src/states.rs
@@ -174,13 +174,6 @@ impl Component for Script {
         }
     }
 
-    async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
-    where
-        Self: Sized
-    {
-        Ok((Self::default(), ))
-    }
-
     fn init<'a>(
         ser: &'a Self::SerializedForm,
         _graphics: Arc<SharedGraphicsContext>,
@@ -188,7 +181,7 @@ impl Component for Script {
         Box::pin(async move { Ok((ser.clone(), )) })
     }
 
-    fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
+    fn update_component(&mut self, _world: &World, _physics: &mut crate::physics::PhysicsState, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {}
 
     fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
@@ -196,30 +189,32 @@ impl Component for Script {
 }
 
 impl InspectableComponent for Script {
-    fn inspect(&mut self, ui: &mut 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())
-                }
-            });
+    fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
+        CollapsingHeader::new("Scripting").default_open(true).show(ui, |ui| {
+            CollapsingHeader::new("Tags")
+                .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())
+                    }
+                });
+        });
     }
 }
 
diff --git a/crates/eucalyptus-core/src/transform.rs b/crates/eucalyptus-core/src/transform.rs
index 2bc8500..9f807cd 100644
--- a/crates/eucalyptus-core/src/transform.rs
+++ b/crates/eucalyptus-core/src/transform.rs
@@ -30,10 +30,6 @@ impl Component for EntityTransform {
         }
     }
 
-    async fn first_time(_: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes> {
-        Ok((Self::default(), ))
-    }
-
     fn init<'a>(
         ser: &'a Self::SerializedForm,
         _: Arc<SharedGraphicsContext>,
@@ -41,7 +37,7 @@ impl Component for EntityTransform {
         Box::pin(async move { Ok((ser.clone(), )) })
     }
 
-    fn update_component(&mut self, _: &World, _: Entity, _: f32, _: Arc<SharedGraphicsContext>) {}
+    fn update_component(&mut self, _: &World, _physics: &mut crate::physics::PhysicsState, _: Entity, _: f32, _: Arc<SharedGraphicsContext>) {}
 
     fn save(&self, _: &World, _: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
@@ -49,7 +45,7 @@ impl Component for EntityTransform {
 }
 
 impl InspectableComponent for EntityTransform {
-    fn inspect(&mut self, ui: &mut Ui) {
+    fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) {
         CollapsingHeader::new("Entity Transform").show(ui, |ui| {
             CollapsingHeader::new("Local").show(ui, |ui| {
                 self.local_mut().inspect(ui);
@@ -283,7 +279,7 @@ fn propogate_transform(
     #[dropbear_macro::entity]
     entity: hecs::Entity,
 ) -> DropbearNativeResult<NTransform> {
-    if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) {
+    if let Ok(et) = world.get::<&mut EntityTransform>(entity) {
         let result = et.propagate(world, entity);
         Ok(result.into())
     } else {
diff --git a/crates/eucalyptus-editor/src/editor/asset_viewer.rs b/crates/eucalyptus-editor/src/editor/asset_viewer.rs
index d3b530f..adb9553 100644
--- a/crates/eucalyptus-editor/src/editor/asset_viewer.rs
+++ b/crates/eucalyptus-editor/src/editor/asset_viewer.rs
@@ -6,6 +6,7 @@ use eucalyptus_core::states::PROJECT;
 use hecs::Entity;
 
 use crate::editor::{ComponentNodeSelection, DraggedAsset, EditorTabViewer, FsEntry, StaticallyKept, TABS_GLOBAL};
+use eucalyptus_core::component::DRAGGED_ASSET_ID;
 
 impl<'a> EditorTabViewer<'a> {
     pub(crate) fn show_asset_viewer(&mut self, ui: &mut egui::Ui) {
@@ -41,7 +42,10 @@ impl<'a> EditorTabViewer<'a> {
 
                     if let Some(&node_id) = dragged.source.first() {
                         if let Some(asset) = cfg.asset_node_assets.get(&node_id).cloned() {
-                            cfg.dragged_asset = Some(asset);
+                            cfg.dragged_asset = Some(asset.clone());
+                            ui.ctx().data_mut(|d| {
+                                d.insert_temp(egui::Id::new(DRAGGED_ASSET_ID), Some(asset.path.clone()))
+                            });
                         }
                     }
                 }
diff --git a/crates/eucalyptus-editor/src/editor/dock.rs b/crates/eucalyptus-editor/src/editor/dock.rs
index 0db1e9f..221f15c 100644
--- a/crates/eucalyptus-editor/src/editor/dock.rs
+++ b/crates/eucalyptus-editor/src/editor/dock.rs
@@ -22,6 +22,7 @@ use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode, GizmoOrientation};
 pub struct EditorTabViewer<'a> {
     pub view: egui::TextureId,
     pub tex_size: Extent3d,
+    pub graphics: Arc<SharedGraphicsContext>,
     pub gizmo: &'a mut Gizmo,
     pub world: &'a mut World,
     pub selected_entity: &'a mut Option<Entity>,
@@ -223,7 +224,6 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                 self.build_console(ui);
             }
             EditorTab::Console => {
-                ui.heading("Console");
                 ui.separator();
 
                 ui.horizontal(|ui| {
diff --git a/crates/eucalyptus-editor/src/editor/input.rs b/crates/eucalyptus-editor/src/editor/input.rs
index 4359a3b..ef61f48 100644
--- a/crates/eucalyptus-editor/src/editor/input.rs
+++ b/crates/eucalyptus-editor/src/editor/input.rs
@@ -171,16 +171,9 @@ impl Keyboard for Editor {
                                 return;
                             };
 
-                            let mut components = self
+                            let components = self
                                 .component_registry
                                 .extract_all_components(self.world.as_ref(), *entity);
-                            components.retain(|component| {
-                                self.component_registry
-                                    .id_for_component(component.as_ref())
-                                    .and_then(|id| self.component_registry.get_descriptor_by_numeric_id(id))
-                                    .map(|desc| desc.fqtn != "dropbear_engine::entity::EntityTransform")
-                                    .unwrap_or(true)
-                            });
                             let s_entity = SceneEntity {
                                 label: Label::new(label.as_str()),
                                 components,
diff --git a/crates/eucalyptus-editor/src/editor/mod.rs b/crates/eucalyptus-editor/src/editor/mod.rs
index a580b3d..b0dd5c5 100644
--- a/crates/eucalyptus-editor/src/editor/mod.rs
+++ b/crates/eucalyptus-editor/src/editor/mod.rs
@@ -66,6 +66,7 @@ use dropbear_engine::pipelines::{DropbearShaderPipeline};
 use dropbear_engine::pipelines::shader::MainRenderPipeline;
 use dropbear_engine::pipelines::GlobalsUniform;
 use dropbear_engine::sky::{HdrLoader, SkyPipeline, DEFAULT_SKY_TEXTURE};
+use eucalyptus_core::physics::PhysicsState;
 use eucalyptus_core::physics::collider::{ColliderShapeKey, WireframeGeometry};
 use eucalyptus_core::physics::collider::shader::ColliderInstanceRaw;
 use eucalyptus_core::physics::collider::shader::ColliderWireframePipeline;
@@ -77,6 +78,7 @@ use crate::editor::settings::project::ProjectSettingsWindow;
 pub struct Editor {
     pub scene_command: SceneCommand,
     pub world: Box<World>,
+    pub physics_state: PhysicsState,
     pub dock_state: DockState<EditorTab>,
     pub texture_id: Option<egui::TextureId>,
     pub size: Extent3d,
@@ -226,6 +228,7 @@ impl Editor {
             // is_cursor_locked: false,
             window: None,
             world: Box::new(World::new()),
+            physics_state: PhysicsState::new(),
             show_new_project: false,
             project_name: String::new(),
             project_path: Arc::new(Mutex::new(None)),
@@ -793,7 +796,7 @@ impl Editor {
         Ok(())
     }
 
-    pub fn show_ui(&mut self, ctx: &Context) {
+    pub fn show_ui(&mut self, ctx: &Context, graphics: Arc<SharedGraphicsContext>) {
         if let Some(scene_name) = self.pending_scene_creation.take() {
             let result = self.create_new_scene(scene_name.as_str());
             self.new_scene_name.clear();
@@ -1091,6 +1094,7 @@ impl Editor {
                     ui,
                     &mut EditorTabViewer {
                         view,
+                        graphics: graphics.clone(),
                         gizmo: &mut self.gizmo,
                         tex_size: self.size,
                         world: &mut self.world,
diff --git a/crates/eucalyptus-editor/src/editor/resource.rs b/crates/eucalyptus-editor/src/editor/resource.rs
index d55ff10..cdac237 100644
--- a/crates/eucalyptus-editor/src/editor/resource.rs
+++ b/crates/eucalyptus-editor/src/editor/resource.rs
@@ -14,7 +14,7 @@ impl<'a> EditorTabViewer<'a> {
                 ui.separator();
 
                 self.component_registry
-                    .inspect_components(self.world, inspect_entity, ui);
+                    .inspect_components(self.world, inspect_entity, ui, self.graphics.clone());
             }
         } else if !local_scene_settings {
             ui.label("No entity selected, therefore no info to provide. Go on, what are you waiting for? Click an entity!");
diff --git a/crates/eucalyptus-editor/src/editor/scene.rs b/crates/eucalyptus-editor/src/editor/scene.rs
index f9cee19..c8c3c85 100644
--- a/crates/eucalyptus-editor/src/editor/scene.rs
+++ b/crates/eucalyptus-editor/src/editor/scene.rs
@@ -122,7 +122,7 @@ impl Scene for Editor {
         }
 
         self.component_registry
-            .update_components(self.world.as_mut(), dt, graphics.clone());
+            .update_components(self.world.as_mut(), &mut self.physics_state, dt, graphics.clone());
 
         if !self.is_world_loaded.is_fully_loaded() {
             log::debug!("Scene is not fully loaded, initialising...");
@@ -784,7 +784,7 @@ impl Editor {
         self.texture_id = Some(*graphics.texture_id.clone());
         self.window = Some(graphics.window.clone());
         
-        self.show_ui(&graphics.get_egui_context());
+        self.show_ui(&graphics.get_egui_context(), graphics.clone());
         eucalyptus_core::logging::render(&graphics.get_egui_context());
     }
 }
\ No newline at end of file
diff --git a/crates/eucalyptus-editor/src/physics/collider.rs b/crates/eucalyptus-editor/src/physics/collider.rs
index 9c7909c..215d4ee 100644
--- a/crates/eucalyptus-editor/src/physics/collider.rs
+++ b/crates/eucalyptus-editor/src/physics/collider.rs
@@ -14,159 +14,6 @@ impl InspectableComponent for Collider {
         _signal: &mut Signal,
         label: &mut String
     ) {
-        ui.vertical(|ui| {
-            self.entity = Label::new(label.clone());
-
-            ui.label("Shape:");
-            let current_shape = self.shape_type_name();
-            ComboBox::from_id_salt("collider_shape")
-                .selected_text(current_shape)
-                .show_ui(ui, |ui| {
-                    if ui.selectable_label(current_shape == "Box", "Box").clicked() {
-                        if current_shape != "Box" {
-                            self.shape = ColliderShape::Box { half_extents: [0.5, 0.5, 0.5] };
-                        }
-                    }
-                    if ui.selectable_label(current_shape == "Sphere", "Sphere").clicked() {
-                        if current_shape != "Sphere" {
-                            self.shape = ColliderShape::Sphere { radius: 0.5 };
-                        }
-                    }
-                    if ui.selectable_label(current_shape == "Capsule", "Capsule").clicked() {
-                        if current_shape != "Capsule" {
-                            self.shape = ColliderShape::Capsule { half_height: 0.5, radius: 0.25 };
-                        }
-                    }
-                    if ui.selectable_label(current_shape == "Cylinder", "Cylinder").clicked() {
-                        if current_shape != "Cylinder" {
-                            self.shape = ColliderShape::Cylinder { half_height: 0.5, radius: 0.25 };
-                        }
-                    }
-                    if ui.selectable_label(current_shape == "Cone", "Cone").clicked() {
-                        if current_shape != "Cone" {
-                            self.shape = ColliderShape::Cone { half_height: 0.5, radius: 0.25 };
-                        }
-                    }
-                });
-
-            ui.add_space(8.0);
-
-            match &mut self.shape {
-                ColliderShape::Box { half_extents } => {
-                    ui.label("Half Extents:");
-                    ui.horizontal(|ui| {
-                        ui.label("X:");
-                        ui.add(egui::DragValue::new(&mut half_extents[0])
-                            .speed(0.01));
-                        ui.label("Y:");
-                        ui.add(egui::DragValue::new(&mut half_extents[1])
-                            .speed(0.01));
-                        ui.label("Z:");
-                        ui.add(egui::DragValue::new(&mut half_extents[2])
-                            .speed(0.01));
-                    });
-                }
-                ColliderShape::Sphere { radius } => {
-                    ui.horizontal(|ui| {
-                        ui.label("Radius:");
-                        ui.add(egui::DragValue::new(radius)
-                            .speed(0.01));
-                    });
-                }
-                ColliderShape::Capsule { half_height, radius } => {
-                    ui.horizontal(|ui| {
-                        ui.label("Half Height:");
-                        ui.add(egui::DragValue::new(half_height)
-                            .speed(0.01));
-                    });
-                    ui.horizontal(|ui| {
-                        ui.label("Radius:");
-                        ui.add(egui::DragValue::new(radius)
-                            .speed(0.01));
-                    });
-                }
-                ColliderShape::Cylinder { half_height, radius } => {
-                    ui.horizontal(|ui| {
-                        ui.label("Half Height:");
-                        ui.add(egui::DragValue::new(half_height)
-                            .speed(0.01));
-                    });
-                    ui.horizontal(|ui| {
-                        ui.label("Radius:");
-                        ui.add(egui::DragValue::new(radius)
-                            .speed(0.01));
-                    });
-                }
-                ColliderShape::Cone { half_height, radius } => {
-                    ui.horizontal(|ui| {
-                        ui.label("Half Height:");
-                        ui.add(egui::DragValue::new(half_height)
-                            .speed(0.01));
-                    });
-                    ui.horizontal(|ui| {
-                        ui.label("Radius:");
-                        ui.add(egui::DragValue::new(radius)
-                            .speed(0.01));
-                    });
-                }
-            }
-
-            ui.add_space(8.0);
-
-            ui.separator();
-            ui.label("Physical Properties:");
-
-            ui.horizontal(|ui| {
-                ui.label("Density:");
-                ui.add(egui::DragValue::new(&mut self.density)
-                    .speed(0.01));
-            });
-
-            ui.horizontal(|ui| {
-                ui.label("Friction:");
-                ui.add(egui::Slider::new(&mut self.friction, 0.0..=2.0));
-            });
-
-            ui.horizontal(|ui| {
-                ui.label("Restitution:");
-                ui.add(egui::Slider::new(&mut self.restitution, 0.0..=1.0));
-            });
-
-            ui.checkbox(&mut self.is_sensor, "Is Sensor (No physical response)");
-
-            ui.add_space(8.0);
-
-            ui.separator();
-            ui.label("Local Offset:");
-
-            ui.label("Translation:");
-            ui.horizontal(|ui| {
-                ui.label("X:");
-                ui.add(egui::DragValue::new(&mut self.translation[0]).speed(0.01));
-                ui.label("Y:");
-                ui.add(egui::DragValue::new(&mut self.translation[1]).speed(0.01));
-                ui.label("Z:");
-                ui.add(egui::DragValue::new(&mut self.translation[2]).speed(0.01));
-            });
-
-            ui.label("Rotation (degrees):");
-            ui.horizontal(|ui| {
-                ui.label("X:");
-                let mut deg_x = self.rotation[0].to_degrees();
-                if ui.add(egui::DragValue::new(&mut deg_x).speed(1.0)).changed() {
-                    self.rotation[0] = deg_x.to_radians();
-                }
-                ui.label("Y:");
-                let mut deg_y = self.rotation[1].to_degrees();
-                if ui.add(egui::DragValue::new(&mut deg_y).speed(1.0)).changed() {
-                    self.rotation[1] = deg_y.to_radians();
-                }
-                ui.label("Z:");
-                let mut deg_z = self.rotation[2].to_degrees();
-                if ui.add(egui::DragValue::new(&mut deg_z).speed(1.0)).changed() {
-                    self.rotation[2] = deg_z.to_radians();
-                }
-            });
-        });
+        
     }
 }
\ No newline at end of file
diff --git a/crates/redback-runtime/src/scene.rs b/crates/redback-runtime/src/scene.rs
index ef4c56f..7b3a49f 100644
--- a/crates/redback-runtime/src/scene.rs
+++ b/crates/redback-runtime/src/scene.rs
@@ -333,7 +333,7 @@ impl Scene for PlayMode {
         }
 
         self.component_registry
-            .update_components(self.world.as_mut(), dt, graphics.clone());
+            .update_components(self.world.as_mut(), &mut self.physics_state, dt, graphics.clone());
 
         
 
diff --git a/include/dropbear.h b/include/dropbear.h
index 2197270..85f3ff8 100644
--- a/include/dropbear.h
+++ b/include/dropbear.h
@@ -179,99 +179,96 @@ typedef struct NChannelValuesFfi {
 
 typedef NChannelValuesFfi NChannelValues;
 
-typedef void* InputStatePtr;
-
 typedef struct IndexNative {
     uint32_t index;
     uint32_t generation;
 } IndexNative;
 
+typedef struct IndexNativeArray {
+    IndexNative* values;
+    size_t length;
+    size_t capacity;
+} IndexNativeArray;
+
+typedef struct CharacterCollisionArray {
+    uint64_t entity_id;
+    IndexNativeArray collisions;
+} CharacterCollisionArray;
+
 typedef struct NCollider {
     IndexNative index;
     uint64_t entity_id;
     uint32_t id;
 } NCollider;
 
-typedef struct RayHit {
+typedef struct NShapeCastHit {
     NCollider collider;
     double distance;
-} RayHit;
-
-typedef void* SceneLoaderPtr;
-
-typedef struct RigidBodyContext {
-    IndexNative index;
-    uint64_t entity_id;
-} RigidBodyContext;
-
-typedef void* CommandBufferPtr;
+    NVector3 witness1;
+    NVector3 witness2;
+    NVector3 normal1;
+    NVector3 normal2;
+    NShapeCastStatus status;
+} NShapeCastHit;
 
-typedef struct NVector4 {
-    double x;
-    double y;
-    double z;
-    double w;
-} NVector4;
+typedef void* PhysicsStatePtr;
 
 typedef struct NVector2 {
     double x;
     double y;
 } NVector2;
 
-typedef struct i32Array {
-    int32_t* values;
-    size_t length;
-    size_t capacity;
-} i32Array;
-
-typedef struct NModelVertex {
+typedef struct NTransform {
     NVector3 position;
-    NVector3 normal;
-    NVector4 tangent;
-    NVector2 tex_coords0;
-    NVector2 tex_coords1;
-    NVector4 colour0;
-    i32Array joints0;
-    NVector4 weights0;
-} NModelVertex;
+    NQuaternion rotation;
+    NVector3 scale;
+} NTransform;
 
-typedef struct NModelVertexArray {
-    NModelVertex* values;
-    size_t length;
-    size_t capacity;
-} NModelVertexArray;
+typedef struct NRange {
+    float start;
+    float end;
+} NRange;
 
-typedef struct NMesh {
-    const char* name;
-    int32_t num_elements;
-    int32_t material_index;
-    NModelVertexArray vertices;
-} NMesh;
+typedef void* InputStatePtr;
 
-typedef struct NMeshArray {
-    NMesh* values;
+typedef void* SceneLoaderPtr;
+
+typedef struct f64Array {
+    double* values;
     size_t length;
     size_t capacity;
-} NMeshArray;
+} f64Array;
 
-typedef struct f64ArrayArray {
-    double* values;
+typedef struct NAnimationChannel {
+    int32_t target_node;
+    f64Array times;
+    NChannelValues values;
+    NAnimationInterpolation interpolation;
+} NAnimationChannel;
+
+typedef struct NAnimationChannelArray {
+    NAnimationChannel* values;
     size_t length;
     size_t capacity;
-} f64ArrayArray;
+} NAnimationChannelArray;
 
-typedef struct NSkin {
+typedef struct NAnimation {
     const char* name;
-    i32Array joints;
-    f64ArrayArray inverse_bind_matrices;
-    const int32_t* skeleton_root;
-} NSkin;
+    NAnimationChannelArray channels;
+    float duration;
+} NAnimation;
 
-typedef struct NSkinArray {
-    NSkin* values;
+typedef struct NAnimationArray {
+    NAnimation* values;
     size_t length;
     size_t capacity;
-} NSkinArray;
+} NAnimationArray;
+
+typedef struct i32Array {
+    int32_t* values;
+    size_t length;
+    size_t capacity;
+} i32Array;
 
 typedef struct NNodeTransform {
     NVector3 translation;
@@ -292,105 +289,107 @@ typedef struct NNodeArray {
     size_t capacity;
 } NNodeArray;
 
-typedef void* AssetRegistryPtr;
-
 typedef void* GraphicsContextPtr;
 
-typedef struct u64Array {
-    uint64_t* values;
+typedef struct f64ArrayArray {
+    double* values;
     size_t length;
     size_t capacity;
-} u64Array;
-
-typedef struct ConnectedGamepadIds {
-    u64Array ids;
-} ConnectedGamepadIds;
+} f64ArrayArray;
 
-typedef void* WorldPtr;
+typedef struct NSkin {
+    const char* name;
+    i32Array joints;
+    f64ArrayArray inverse_bind_matrices;
+    const int32_t* skeleton_root;
+} NSkin;
 
-typedef struct f64Array {
-    double* values;
+typedef struct NSkinArray {
+    NSkin* values;
     size_t length;
     size_t capacity;
-} f64Array;
+} NSkinArray;
 
-typedef struct NAnimationChannel {
-    int32_t target_node;
-    f64Array times;
-    NChannelValues values;
-    NAnimationInterpolation interpolation;
-} NAnimationChannel;
+typedef struct NAttenuation {
+    float constant;
+    float linear;
+    float quadratic;
+} NAttenuation;
 
-typedef struct NAnimationChannelArray {
-    NAnimationChannel* values;
-    size_t length;
-    size_t capacity;
-} NAnimationChannelArray;
+typedef void* AssetRegistryPtr;
 
-typedef struct NAnimation {
-    const char* name;
-    NAnimationChannelArray channels;
-    float duration;
-} NAnimation;
+typedef struct NColour {
+    uint8_t r;
+    uint8_t g;
+    uint8_t b;
+    uint8_t a;
+} NColour;
 
-typedef struct NAnimationArray {
-    NAnimation* values;
+typedef struct NVector4 {
+    double x;
+    double y;
+    double z;
+    double w;
+} NVector4;
+
+typedef struct NModelVertex {
+    NVector3 position;
+    NVector3 normal;
+    NVector4 tangent;
+    NVector2 tex_coords0;
+    NVector2 tex_coords1;
+    NVector4 colour0;
+    i32Array joints0;
+    NVector4 weights0;
+} NModelVertex;
+
+typedef struct NModelVertexArray {
+    NModelVertex* values;
     size_t length;
     size_t capacity;
-} NAnimationArray;
+} NModelVertexArray;
 
-typedef struct NColliderArray {
-    NCollider* values;
+typedef struct NMesh {
+    const char* name;
+    int32_t num_elements;
+    int32_t material_index;
+    NModelVertexArray vertices;
+} NMesh;
+
+typedef struct NMeshArray {
+    NMesh* values;
     size_t length;
     size_t capacity;
-} NColliderArray;
+} NMeshArray;
 
-typedef struct IndexNativeArray {
-    IndexNative* values;
+typedef struct u64Array {
+    uint64_t* values;
     size_t length;
     size_t capacity;
-} IndexNativeArray;
-
-typedef struct CharacterCollisionArray {
-    uint64_t entity_id;
-    IndexNativeArray collisions;
-} CharacterCollisionArray;
-
-typedef void* PhysicsStatePtr;
+} u64Array;
 
-typedef struct AxisLock {
-    bool x;
-    bool y;
-    bool z;
-} AxisLock;
+typedef struct ConnectedGamepadIds {
+    u64Array ids;
+} ConnectedGamepadIds;
 
-typedef struct NRange {
-    float start;
-    float end;
-} NRange;
+typedef void* CommandBufferPtr;
 
-typedef struct NTransform {
-    NVector3 position;
-    NQuaternion rotation;
-    NVector3 scale;
-} NTransform;
+typedef struct NColliderArray {
+    NCollider* values;
+    size_t length;
+    size_t capacity;
+} NColliderArray;
 
-typedef struct NColour {
-    uint8_t r;
-    uint8_t g;
-    uint8_t b;
-    uint8_t a;
-} NColour;
+typedef struct Progress {
+    size_t current;
+    size_t total;
+    const char* message;
+} Progress;
 
-typedef struct NShapeCastHit {
+typedef struct RayHit {
     NCollider collider;
     double distance;
-    NVector3 witness1;
-    NVector3 witness2;
-    NVector3 normal1;
-    NVector3 normal2;
-    NShapeCastStatus status;
-} NShapeCastHit;
+} RayHit;
 
 typedef struct NMaterial {
     const char* name;
@@ -416,17 +415,18 @@ typedef struct NMaterialArray {
     size_t capacity;
 } NMaterialArray;
 
-typedef struct NAttenuation {
-    float constant;
-    float linear;
-    float quadratic;
-} NAttenuation;
+typedef struct RigidBodyContext {
+    IndexNative index;
+    uint64_t entity_id;
+} RigidBodyContext;
 
-typedef struct Progress {
-    size_t current;
-    size_t total;
-    const char* message;
-} Progress;
+typedef struct AxisLock {
+    bool x;
+    bool y;
+    bool z;
+} AxisLock;
+
+typedef void* WorldPtr;
 
 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);