kitgit

tirbofish/dropbear · diff

b9424d9 · tk

Merge pull request #62 from 4tkbytes/entity-split refactor: changing from AdoptedEntity to more component based system

Unverified

diff --git a/Cargo.toml b/Cargo.toml
index 52e9f71..ea72445 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -60,6 +60,8 @@ libloading = "0.8"
 indexmap = "2.11"
 sha2 = "0.10"
 wesl = "0.2"
+dashmap = "6.1"
+open = "5.3"
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/dropbear-engine/Cargo.toml b/dropbear-engine/Cargo.toml
index 78ec532..d9dc7a5 100644
--- a/dropbear-engine/Cargo.toml
+++ b/dropbear-engine/Cargo.toml
@@ -39,6 +39,7 @@ backtrace.workspace = true
 os_info.workspace = true
 rustc_version_runtime.workspace = true
 ron.workspace = true
+dashmap.workspace = true
 
 [target.'cfg(not(target_os = "android"))'.dependencies]
 rfd.workspace = true
diff --git a/dropbear-engine/src/asset.rs b/dropbear-engine/src/asset.rs
new file mode 100644
index 0000000..1c5d958
--- /dev/null
+++ b/dropbear-engine/src/asset.rs
@@ -0,0 +1,77 @@
+use std::sync::Arc;
+
+use dashmap::DashMap;
+
+use crate::model::{Material, MaterialComponent, Mesh, MeshComponent};
+
+/// A typedef for a Asset handle.
+pub type Handle = u64;
+
+/// A cache that holds all the assets loaded at that moment in time.
+pub struct AssetCache {
+    materials: DashMap<MaterialComponent, Arc<Material>>,
+    meshes: DashMap<MeshComponent, Arc<Mesh>>,
+}
+
+impl AssetCache {
+    pub fn new() -> Self {
+        Self {
+            materials: DashMap::new(),
+            meshes: DashMap::new(),
+        }
+    }
+
+    /// Fetches the material based off the handle.
+    ///
+    /// If it doesn't exist, it will run the loader as a function.
+    pub fn get_or_load_material<F>(
+        &self,
+        handle: MaterialComponent,
+        loader: F,
+    ) -> anyhow::Result<Arc<Material>>
+    where
+        F: FnOnce() -> anyhow::Result<Material>,
+    {
+        if let Some(existing) = self.materials.get(&handle) {
+            return Ok(existing.clone());
+        }
+
+        let material = Arc::new(loader()?);
+
+        match self.materials.entry(handle) {
+            dashmap::mapref::entry::Entry::Occupied(entry) => Ok(entry.get().clone()),
+            dashmap::mapref::entry::Entry::Vacant(entry) => {
+                entry.insert(material.clone());
+                Ok(material)
+            }
+        }
+    }
+
+    /// Fetches the model based off the handle.
+    ///
+    /// If it doesn't exist, it will run the loader as a function.
+    pub fn get_or_load_mesh<F>(&self, handle: MeshComponent, loader: F) -> anyhow::Result<Arc<Mesh>>
+    where
+        F: FnOnce() -> anyhow::Result<Mesh>,
+    {
+        if let Some(existing) = self.meshes.get(&handle) {
+            return Ok(existing.clone());
+        }
+
+        let mesh = Arc::new(loader()?);
+
+        match self.meshes.entry(handle) {
+            dashmap::mapref::entry::Entry::Occupied(entry) => Ok(entry.get().clone()),
+            dashmap::mapref::entry::Entry::Vacant(entry) => {
+                entry.insert(mesh.clone());
+                Ok(mesh)
+            }
+        }
+    }
+
+    pub fn clear_everything(&mut self) {
+        self.materials.clear();
+        self.meshes.clear();
+        log::debug!("Cleared everything in the asset cache");
+    }
+}
diff --git a/dropbear-engine/src/camera.rs b/dropbear-engine/src/camera.rs
index acb07a4..160a5c8 100644
--- a/dropbear-engine/src/camera.rs
+++ b/dropbear-engine/src/camera.rs
@@ -3,6 +3,7 @@
 use std::sync::Arc;
 
 use glam::{DMat4, DQuat, DVec3, Mat4};
+use serde::{Deserialize, Serialize};
 use wgpu::{
     BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
     BindGroupLayoutEntry, BindingType, Buffer, BufferBindingType, ShaderStages,
@@ -19,6 +20,30 @@ pub const OPENGL_TO_WGPU_MATRIX: [[f64; 4]; 4] = [
     [0.0, 0.0, 0.5, 1.0],
 ];
 
+/// Shared tuning data for camera movement and projection.
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
+pub struct CameraSettings {
+    pub speed: f64,
+    pub sensitivity: f64,
+    pub fov_y: f64,
+}
+
+impl CameraSettings {
+    pub const fn new(speed: f64, sensitivity: f64, fov_y: f64) -> Self {
+        Self {
+            speed,
+            sensitivity,
+            fov_y,
+        }
+    }
+}
+
+impl Default for CameraSettings {
+    fn default() -> Self {
+        Self::new(1.0, 0.1, 45.0)
+    }
+}
+
 /// The basic values of a Camera.
 #[derive(Default, Debug, Clone)]
 pub struct Camera {
@@ -33,8 +58,6 @@ pub struct Camera {
     pub up: DVec3,
     /// Aspect ratio
     pub aspect: f64,
-    /// FOV of camera
-    pub fov_y: f64,
     /// Near buffer?
     pub znear: f64,
     /// Far buffer?
@@ -44,6 +67,9 @@ pub struct Camera {
     /// Pitch (rotation)
     pub pitch: f64,
 
+    /// Tuning values that control movement and projection
+    pub settings: CameraSettings,
+
     /// Uniform/interface for Rust and the GPU
     pub uniform: CameraUniform,
     buffer: Option<Buffer>,
@@ -51,11 +77,6 @@ pub struct Camera {
     layout: Option<BindGroupLayout>,
     bind_group: Option<BindGroup>,
 
-    /// Speed of the camera
-    pub speed: f64,
-    /// Sensitivity of the mouse for the camera
-    pub sensitivity: f64,
-
     /// View matrix
     pub view_mat: DMat4,
     /// Projection Matrix
@@ -68,11 +89,9 @@ pub struct CameraBuilder {
     pub target: DVec3,
     pub up: DVec3,
     pub aspect: f64,
-    pub fov_y: f64,
     pub znear: f64,
     pub zfar: f64,
-    pub speed: f64,
-    pub sensitivity: f64,
+    pub settings: CameraSettings,
 }
 
 impl Camera {
@@ -88,17 +107,15 @@ impl Camera {
             target: builder.target,
             up: builder.up,
             aspect: builder.aspect,
-            fov_y: builder.fov_y,
             znear: builder.znear,
             zfar: builder.zfar,
             uniform,
             buffer: None,
             layout: None,
             bind_group: None,
-            speed: builder.speed,
             yaw: 0.0,
             pitch: 0.0,
-            sensitivity: builder.sensitivity,
+            settings: builder.settings,
             label: if let Some(l) = label {
                 l.to_string()
             } else {
@@ -123,11 +140,9 @@ impl Camera {
                 target: DVec3::new(0.0, 0.0, 0.0),
                 up: DVec3::Y,
                 aspect: (graphics.screen_size.0 / graphics.screen_size.1).into(),
-                fov_y: 45.0,
                 znear: 0.1,
                 zfar: 100.0,
-                speed: 1.0,
-                sensitivity: 0.002,
+                settings: CameraSettings::new(1.0, 0.002, 45.0),
             },
             label,
         )
@@ -166,7 +181,7 @@ impl Camera {
         log::debug!("  Eye: {:?}", camera.eye);
         log::debug!("  Target: {:?}", camera.target);
         log::debug!("  Up: {:?}", camera.up);
-        log::debug!("  FOV Y: {}", camera.fov_y);
+        log::debug!("  FOV Y: {}", camera.settings.fov_y);
         log::debug!("  Aspect: {}", camera.aspect);
         log::debug!("  Z Near: {}", camera.znear);
         log::debug!("  Proj Mat finite: {}", camera.proj_mat.is_finite());
@@ -176,7 +191,7 @@ impl Camera {
     fn build_vp(&mut self) -> DMat4 {
         let view = DMat4::look_at_lh(self.eye, self.target, self.up);
         let proj = DMat4::perspective_infinite_reverse_lh(
-            self.fov_y.to_radians(),
+            self.settings.fov_y.to_radians(),
             self.aspect,
             self.znear,
         );
@@ -237,45 +252,45 @@ impl Camera {
 
     pub fn move_forwards(&mut self) {
         let forward = (self.target - self.eye).normalize();
-        self.eye += forward * self.speed;
-        self.target += forward * self.speed;
+        self.eye += forward * self.settings.speed;
+        self.target += forward * self.settings.speed;
     }
 
     pub fn move_back(&mut self) {
         let forward = (self.target - self.eye).normalize();
-        self.eye -= forward * self.speed;
-        self.target -= forward * self.speed;
+        self.eye -= forward * self.settings.speed;
+        self.target -= forward * self.settings.speed;
     }
 
     pub fn move_right(&mut self) {
         let forward = (self.target - self.eye).normalize();
         // LH: right = up.cross(forward)
         let right = self.up.cross(forward).normalize();
-        self.eye += right * self.speed;
-        self.target += right * self.speed;
+        self.eye += right * self.settings.speed;
+        self.target += right * self.settings.speed;
     }
 
     pub fn move_left(&mut self) {
         let forward = (self.target - self.eye).normalize();
         let right = self.up.cross(forward).normalize();
-        self.eye -= right * self.speed;
-        self.target -= right * self.speed;
+        self.eye -= right * self.settings.speed;
+        self.target -= right * self.settings.speed;
     }
 
     pub fn move_up(&mut self) {
         let up = self.up.normalize();
-        self.eye += up * self.speed;
-        self.target += up * self.speed;
+        self.eye += up * self.settings.speed;
+        self.target += up * self.settings.speed;
     }
 
     pub fn move_down(&mut self) {
         let up = self.up.normalize();
-        self.eye -= up * self.speed;
-        self.target -= up * self.speed;
+        self.eye -= up * self.settings.speed;
+        self.target -= up * self.settings.speed;
     }
 
     pub fn track_mouse_delta(&mut self, dx: f64, dy: f64) {
-        let sensitivity = self.sensitivity;
+        let sensitivity = self.settings.sensitivity;
         self.yaw -= dx * sensitivity;
         self.pitch -= dy * sensitivity;
         let max_pitch = std::f64::consts::FRAC_PI_2 - 0.01;
diff --git a/dropbear-engine/src/entity.rs b/dropbear-engine/src/entity.rs
index df351cd..de02ba2 100644
--- a/dropbear-engine/src/entity.rs
+++ b/dropbear-engine/src/entity.rs
@@ -1,15 +1,10 @@
-use futures::executor::block_on;
 use glam::{DMat4, DQuat, DVec3, Mat4};
 use serde::{Deserialize, Serialize};
-use std::{
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-use wgpu::{Buffer, util::DeviceExt};
+use std::{path::Path, sync::Arc};
 
 use crate::{
     graphics::{Instance, SharedGraphicsContext},
-    model::{LazyModel, LazyType, Model},
+    model::{LoadedModel, Model, ModelId},
 };
 
 /// A type that represents a position, rotation and scale of an entity
@@ -78,151 +73,64 @@ impl Transform {
     }
 }
 
-/// Creates a new adopted entity in a lazy method. It fetches the data first (which can be done on a separate
-/// thread). After, the [`LazyAdoptedEntity::poke()`] function can be called to convert the Lazy to a Real adopted entity.
-#[derive(Default)]
-pub struct LazyAdoptedEntity {
-    lazy_model: LazyModel,
-    #[allow(dead_code)]
-    label: String,
-}
-
-impl LazyAdoptedEntity {
-    /// Create a LazyAdoptedEntity from a file path (can be run on background thread)
-    pub async fn from_file(path: &PathBuf, label: Option<&str>) -> anyhow::Result<Self> {
-        let buffer = std::fs::read(path)?;
-        Self::from_memory(buffer, label).await
-    }
-
-    /// Create a LazyAdoptedEntity from memory buffer (can be run on background thread)
-    pub async fn from_memory(
-        buffer: impl AsRef<[u8]>,
-        label: Option<&str>,
-    ) -> anyhow::Result<Self> {
-        let lazy_model = Model::lazy_load(buffer, label).await?;
-        let label_str = label.unwrap_or("LazyAdoptedEntity").to_string();
-
-        Ok(Self {
-            lazy_model,
-            label: label_str,
-        })
-    }
-
-    /// Create a LazyAdoptedEntity from an existing LazyModel
-    pub fn from_lazy_model(lazy_model: LazyModel, label: Option<&str>) -> Self {
-        let label_str = label.unwrap_or("LazyAdoptedEntity").to_string();
-        Self {
-            lazy_model,
-            label: label_str,
-        }
-    }
-}
-
-impl LazyType for LazyAdoptedEntity {
-    type T = AdoptedEntity;
-
-    fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> {
-        let model = self.lazy_model.poke(graphics.clone())?;
-        Ok(block_on(AdoptedEntity::adopt(graphics, model)))
-    }
-}
-
 #[derive(Clone)]
-pub struct AdoptedEntity {
-    pub model: Arc<Model>,
-    pub previous_matrix: DMat4,
+pub struct MeshRenderer {
+    handle: LoadedModel,
     pub instance: Instance,
-    pub instance_buffer: Option<Buffer>,
-    pub dirty: bool,
-    last_frame_rendered: Option<u64>,
+    pub previous_matrix: DMat4,
     pub is_selected: bool,
 }
 
-impl AdoptedEntity {
-    pub async fn new(
+impl MeshRenderer {
+    pub async fn from_path(
         graphics: Arc<SharedGraphicsContext>,
         path: impl AsRef<Path>,
         label: Option<&str>,
     ) -> anyhow::Result<Self> {
         let path = path.as_ref().to_path_buf();
-        let model = Model::load(graphics.clone(), &path, label).await?;
-        Ok(Self::adopt(graphics, model).await)
-    }
-
-    pub async fn adopt(graphics: Arc<SharedGraphicsContext>, model: Model) -> Self {
-        let label = model.label.clone();
-        let instance = Instance::new(DVec3::ZERO, DQuat::IDENTITY, DVec3::ONE);
-        let initial_matrix = DMat4::IDENTITY; // Default; update in new() if transform provided
-        let instance_raw = instance.to_raw();
-        let instance_buffer =
-            graphics
-                .device
-                .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                    label: Some(&label),
-                    contents: bytemuck::cast_slice(&[instance_raw]),
-                    usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
-                });
+        let handle = Model::load(graphics, &path, label).await?;
+        Ok(Self::from_handle(handle))
+    }
 
+    pub fn from_handle(handle: LoadedModel) -> Self {
         Self {
-            model: Arc::new(model),
-            instance,
-            instance_buffer: Some(instance_buffer),
-            previous_matrix: initial_matrix,
-            dirty: true,
-            last_frame_rendered: None,
+            handle,
+            instance: Instance::new(DVec3::ZERO, DQuat::IDENTITY, DVec3::ONE),
+            previous_matrix: DMat4::IDENTITY,
             is_selected: false,
         }
     }
 
-    pub fn update(&mut self, graphics: Arc<SharedGraphicsContext>, transform: &Transform) {
-        let current_matrix = transform.matrix();
-        if self.previous_matrix != current_matrix {
-            self.instance = Instance::from_matrix(current_matrix);
-            let instance_raw = self.instance.to_raw();
-            if let Some(buffer) = &self.instance_buffer {
-                graphics
-                    .queue
-                    .write_buffer(buffer, 0, bytemuck::cast_slice(&[instance_raw]));
-            }
-            self.previous_matrix = current_matrix;
-        }
+    pub fn model(&self) -> Arc<Model> {
+        self.handle.get()
     }
 
-    pub fn mark_dirty(&mut self) {
-        self.dirty = true;
+    pub fn model_id(&self) -> ModelId {
+        self.handle.id()
     }
 
-    pub fn is_dirty(&self) -> bool {
-        self.dirty
+    pub fn handle(&self) -> &LoadedModel {
+        &self.handle
     }
 
-    pub fn flush_to_gpu(&mut self, graphics: Arc<SharedGraphicsContext>) {
-        if self.dirty {
-            let instance_raw = self.instance.to_raw();
-            if let Some(buffer) = &self.instance_buffer {
-                graphics
-                    .queue
-                    .write_buffer(buffer, 0, bytemuck::cast_slice(&[instance_raw]));
-            }
-            self.dirty = false;
-        }
+    pub fn handle_mut(&mut self) -> &mut LoadedModel {
+        &mut self.handle
     }
 
-    pub fn mark_rendered(&mut self, frame_number: u64) {
-        self.last_frame_rendered = Some(frame_number);
+    pub fn make_model_mut(&mut self) -> &mut Model {
+        self.handle.make_mut()
     }
 
-    pub fn was_recently_rendered(&self, current_frame: u64, max_frames_ago: u64) -> bool {
-        if let Some(last_frame) = self.last_frame_rendered {
-            current_frame - last_frame <= max_frames_ago
-        } else {
-            false
+    pub fn update(&mut self, transform: &Transform) {
+        let current_matrix = transform.matrix();
+        if self.previous_matrix != current_matrix {
+            self.instance = Instance::from_matrix(current_matrix);
+            self.previous_matrix = current_matrix;
         }
     }
 
-    pub fn get_instance_buffer(&mut self, graphics: Arc<SharedGraphicsContext>) -> Option<&Buffer> {
-        self.flush_to_gpu(graphics);
-        self.instance_buffer.as_ref()
+    pub fn set_handle(&mut self, handle: LoadedModel) {
+        self.handle = handle;
     }
 }
 
diff --git a/dropbear-engine/src/lib.rs b/dropbear-engine/src/lib.rs
index 08865a1..dd9506f 100644
--- a/dropbear-engine/src/lib.rs
+++ b/dropbear-engine/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod asset;
 pub mod attenuation;
 pub mod buffer;
 pub mod camera;
diff --git a/dropbear-engine/src/lighting.rs b/dropbear-engine/src/lighting.rs
index 2057965..0f33225 100644
--- a/dropbear-engine/src/lighting.rs
+++ b/dropbear-engine/src/lighting.rs
@@ -8,7 +8,6 @@ use wgpu::{
 
 use crate::attenuation::{Attenuation, RANGE_50};
 use crate::graphics::SharedGraphicsContext;
-use crate::model::{LazyModel, LazyType};
 use crate::shader::Shader;
 use crate::{
     camera::Camera,
@@ -151,15 +150,25 @@ impl Default for LightComponent {
 }
 
 impl LightComponent {
+    pub fn default_direction() -> DVec3 {
+        let dir = DVec3::new(-0.35, -1.0, -0.25);
+        dir.normalize()
+    }
+
     pub fn new(
         colour: DVec3,
         light_type: LightType,
         intensity: f32,
         attenuation: Option<Attenuation>,
     ) -> Self {
+        let direction = match light_type {
+            LightType::Directional | LightType::Spot => Self::default_direction(),
+            LightType::Point => DVec3::ZERO,
+        };
+
         Self {
             position: Default::default(),
-            direction: Default::default(),
+            direction,
             colour,
             light_type,
             intensity,
@@ -200,104 +209,6 @@ impl LightComponent {
     }
 }
 
-pub struct LazyLight {
-    light_component: LightComponent,
-    transform: Transform,
-    label: Option<String>,
-    cube_lazy_model: Option<LazyModel>,
-}
-
-impl LazyType for LazyLight {
-    type T = Light;
-
-    fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> {
-        let label_str = self.label.clone().unwrap_or_else(|| "Light".to_string());
-
-        let forward = DVec3::new(0.0, 0.0, -1.0);
-        let direction = self.transform.rotation * forward;
-
-        let uniform = LightUniform {
-            position: dvec3_to_uniform_array(self.transform.position),
-            direction: dvec3_direction_to_uniform_array(
-                direction,
-                self.light_component.outer_cutoff_angle,
-            ),
-            colour: dvec3_colour_to_uniform_array(
-                self.light_component.colour * self.light_component.intensity as f64,
-                self.light_component.light_type,
-            ),
-            constant: self.light_component.attenuation.constant,
-            linear: self.light_component.attenuation.linear,
-            quadratic: self.light_component.attenuation.quadratic,
-            cutoff: f32::cos(self.light_component.cutoff_angle.to_radians()),
-        };
-
-        let cube_model = Arc::new(if let Some(lazy_model) = self.cube_lazy_model {
-            lazy_model.poke(graphics.clone())?
-        } else {
-            anyhow::bail!(
-                "The light cube LazyModel has not been initialised yet. Use Light::new(/** params */).preload_cube_model() to preload it (which is required)"
-            );
-        });
-
-        let buffer = graphics.create_uniform(uniform, self.label.as_deref());
-
-        let layout = graphics
-            .device
-            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                entries: &[wgpu::BindGroupLayoutEntry {
-                    binding: 0,
-                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
-                    ty: wgpu::BindingType::Buffer {
-                        ty: wgpu::BufferBindingType::Uniform,
-                        has_dynamic_offset: false,
-                        min_binding_size: None,
-                    },
-                    count: None,
-                }],
-                label: self.label.as_deref(),
-            });
-
-        let bind_group = graphics
-            .device
-            .create_bind_group(&wgpu::BindGroupDescriptor {
-                layout: &layout,
-                entries: &[wgpu::BindGroupEntry {
-                    binding: 0,
-                    resource: buffer.as_entire_binding(),
-                }],
-                label: self.label.as_deref(),
-            });
-
-        let instance = Instance::new(
-            self.transform.position,
-            self.transform.rotation,
-            DVec3::new(0.25, 0.25, 0.25),
-        );
-
-        let instance_buffer =
-            graphics
-                .device
-                .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                    label: self.label.as_deref().or(Some("instance buffer")),
-                    contents: bytemuck::cast_slice(&[instance.to_raw()]),
-                    usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
-                });
-
-        log::debug!("Created new light [{}]", label_str);
-
-        Ok(Light {
-            uniform,
-            cube_model,
-            label: label_str,
-            buffer: Some(buffer),
-            layout: Some(layout),
-            bind_group: Some(bind_group),
-            instance_buffer: Some(instance_buffer),
-        })
-    }
-}
-
 #[derive(Clone)]
 pub struct Light {
     pub uniform: LightUniform,
@@ -310,28 +221,6 @@ pub struct Light {
 }
 
 impl Light {
-    pub async fn lazy_new(
-        light_component: LightComponent,
-        transform: Transform,
-        label: Option<&str>,
-    ) -> anyhow::Result<LazyLight> {
-        let mut result = LazyLight {
-            light_component,
-            transform,
-            label: label.map(|s| s.to_string()),
-            cube_lazy_model: None,
-        };
-        if result.cube_lazy_model.is_none() {
-            let lazy_model = Model::lazy_load(
-                include_bytes!("../../resources/models/cube.glb").to_vec(),
-                result.label.as_deref(),
-            )
-            .await?;
-            result.cube_lazy_model = Some(lazy_model);
-        }
-        Ok(result)
-    }
-
     pub async fn new(
         graphics: Arc<SharedGraphicsContext>,
         light: LightComponent,
@@ -356,15 +245,14 @@ impl Light {
 
         log::trace!("Created new light uniform");
 
-        let cube_model = Arc::new(
-            Model::load_from_memory(
-                graphics.clone(),
-                include_bytes!("../../resources/models/cube.glb").to_vec(),
-                label,
-            )
-            .await
-            .unwrap(),
-        );
+        let cube_model = Model::load_from_memory(
+            graphics.clone(),
+            include_bytes!("../../resources/models/cube.glb").to_vec(),
+            label,
+        )
+        .await
+        .expect("failed to load light cube model")
+        .get();
 
         let label_str = label.unwrap_or("Light").to_string();
 
diff --git a/dropbear-engine/src/model.rs b/dropbear-engine/src/model.rs
index 1901dda..b7295c2 100644
--- a/dropbear-engine/src/model.rs
+++ b/dropbear-engine/src/model.rs
@@ -3,11 +3,6 @@ use crate::utils::ResourceReference;
 use image::GenericImageView;
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
-// use russimp_ng::{
-//     Vector3D,
-//     material::{DataContent, TextureType},
-//     scene::{PostProcess, Scene},
-// };
 use rayon::prelude::*;
 use std::collections::HashMap;
 use std::hash::{DefaultHasher, Hash, Hasher};
@@ -19,12 +14,18 @@ use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, util::DeviceExt};
 pub const GREY_TEXTURE_BYTES: &[u8] = include_bytes!("../../resources/textures/grey.png");
 
 lazy_static! {
-    pub static ref MODEL_CACHE: Mutex<HashMap<String, Model>> = Mutex::new(HashMap::new());
+    pub static ref MODEL_CACHE: Mutex<HashMap<String, Arc<Model>>> = Mutex::new(HashMap::new());
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct ModelId(pub u64);
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct MaterialComponent(pub u64);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct MeshComponent(pub u64);
+
 #[derive(Clone)]
 pub struct Model {
     pub id: ModelId,
@@ -35,13 +36,47 @@ pub struct Model {
 }
 
 #[derive(Clone)]
+pub struct LoadedModel {
+    inner: Arc<Model>,
+}
+
+impl LoadedModel {
+    pub fn new(inner: Arc<Model>) -> Self {
+        Self { inner }
+    }
+
+    /// Returns the unique identifier of the underlying model asset.
+    pub fn id(&self) -> ModelId {
+        self.inner.id
+    }
+
+    /// Provides shared access to the underlying model.
+    pub fn get(&self) -> Arc<Model> {
+        Arc::clone(&self.inner)
+    }
+
+    /// Provides mutable access to the underlying model data, cloning if shared.
+    pub fn make_mut(&mut self) -> &mut Model {
+        Arc::make_mut(&mut self.inner)
+    }
+}
+
+impl std::ops::Deref for LoadedModel {
+    type Target = Model;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+#[derive(Clone)]
 pub struct Material {
     pub name: String,
     pub diffuse_texture: Texture,
     pub bind_group: wgpu::BindGroup,
 }
 
-#[derive(Clone, Hash)]
+#[derive(Clone)]
 pub struct Mesh {
     pub name: String,
     pub vertex_buffer: wgpu::Buffer,
@@ -50,311 +85,12 @@ pub struct Mesh {
     pub material: usize,
 }
 
-#[derive(Default, Clone)]
-pub struct ParsedModelData {
-    pub label: String,
-    pub path: ResourceReference,
-    pub mesh_data: Vec<ParsedMeshData>,
-    pub material_data: Vec<ParsedMaterialData>,
-}
-
-#[derive(Default, Clone)]
-pub struct ParsedMeshData {
-    pub name: String,
-    pub vertices: Vec<ModelVertex>,
-    pub indices: Vec<u32>,
-    pub material_index: usize,
-}
-
-#[derive(Default, Clone)]
-pub struct ParsedMaterialData {
-    pub name: String,
-    pub rgba_data: Vec<u8>,
-    pub dimensions: (u32, u32),
-}
-
-/// A type that is used to lazily load an object/struct.
-///
-/// It allows for separate threading loading.
-pub trait LazyType {
-    /// The data type of the lazy item
-    ///
-    /// For example, a [`LazyModel`] would have a [`Model`]
-    type T;
-
-    /// A function used to load the wgpu graphics items.
-    ///
-    /// This can be ran after the initial thread loading is completed.
-    /// # Parameters
-    /// * [`Arc<SharedGraphicsContext>`] - The graphics context. It can be shared between threads
-    ///   if required.
-    ///
-    /// # Returns
-    /// * [`Self::T`] - The data type of the item
-    fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T>;
-}
-
-/// Loads the model into memory but graphics functions are defined after the creation
-/// of the model
-#[derive(Default)]
-pub struct LazyModel {
-    parsed_data: ParsedModelData,
-}
-
-impl LazyType for LazyModel {
-    type T = Model;
-
-    fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> {
-        let start = Instant::now();
-
-        let mut hasher = DefaultHasher::new();
-
-        let cache_key = self.parsed_data.label.clone();
-        if let Some(cached_model) = MODEL_CACHE.lock().get(&cache_key) {
-            log::debug!("Model loaded from cache during poke: {:?}", cache_key);
-            return Ok(cached_model.clone());
-        }
-
-        log::debug!(
-            "Creating GPU resources for model: {:?}",
-            self.parsed_data.label
-        );
-
-        let mut materials = Vec::new();
-        for material_data in &self.parsed_data.material_data {
-            let texture_start = Instant::now();
-
-            let diffuse_texture = Texture::from_rgba_buffer(
-                graphics.clone(),
-                &material_data.rgba_data,
-                material_data.dimensions,
-            );
-
-            let bind_group = diffuse_texture.bind_group().to_owned();
-
-            materials.push(Material {
-                name: material_data.name.clone(),
-                diffuse_texture,
-                bind_group,
-            });
-
-            log::debug!(
-                "Created GPU texture for material '{}' in {:?}",
-                material_data.name,
-                texture_start.elapsed()
-            );
-        }
-
-        let mut meshes = Vec::new();
-        for mesh_data in &self.parsed_data.mesh_data {
-            for v in &mesh_data.vertices {
-                let _ = v.position.iter().map(|v| (*v as i32).hash(&mut hasher));
-                let _ = v.tex_coords.iter().map(|v| (*v as i32).hash(&mut hasher));
-                let _ = v.normal.iter().map(|v| (*v as i32).hash(&mut hasher));
-            }
-            mesh_data.indices.hash(&mut hasher);
-
-            let buffer_start = Instant::now();
-
-            let vertex_buffer =
-                graphics
-                    .clone()
-                    .device
-                    .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                        label: Some(&format!("{} Vertex Buffer", mesh_data.name)),
-                        contents: bytemuck::cast_slice(&mesh_data.vertices),
-                        usage: wgpu::BufferUsages::VERTEX,
-                    });
-
-            let index_buffer =
-                graphics
-                    .clone()
-                    .device
-                    .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                        label: Some(&format!("{} Index Buffer", mesh_data.name)),
-                        contents: bytemuck::cast_slice(&mesh_data.indices),
-                        usage: wgpu::BufferUsages::INDEX,
-                    });
-
-            meshes.push(Mesh {
-                name: mesh_data.name.clone(),
-                vertex_buffer,
-                index_buffer,
-                num_elements: mesh_data.indices.len() as u32,
-                material: mesh_data.material_index,
-            });
-
-            log::debug!(
-                "Created GPU buffers for mesh '{}' in {:?}",
-                mesh_data.name,
-                buffer_start.elapsed()
-            );
-        }
-
-        let model = Model {
-            meshes,
-            materials,
-            label: self.parsed_data.label.clone(),
-            path: self.parsed_data.path.clone(),
-            id: ModelId(hasher.finish()),
-        };
-
-        MODEL_CACHE.lock().insert(cache_key, model.clone());
-        log::debug!(
-            "Model GPU resource creation completed in {:?}",
-            start.elapsed()
-        );
-
-        Ok(model)
-    }
-}
-
 impl Model {
-    /// Creates a [`LazyModel`].
-    pub async fn lazy_load(
-        buffer: impl AsRef<[u8]>,
-        label: Option<&str>,
-    ) -> anyhow::Result<LazyModel> {
-        let start = Instant::now();
-        let label_str = label.unwrap_or("default");
-
-        log::debug!("Starting lazy load for model: {:?}", label_str);
-
-        let res_ref = ResourceReference::from_bytes(buffer.as_ref());
-        let (gltf, buffers, _images) = gltf::import_slice(buffer.as_ref())?;
-
-        let mut texture_data = Vec::new();
-        for material in gltf.materials() {
-            log::debug!("Processing material: {:?}", material.name());
-            let material_name = material.name().unwrap_or("Unnamed Material").to_string();
-
-            let image_data =
-                if let Some(pbr) = material.pbr_metallic_roughness().base_color_texture() {
-                    let texture_info = pbr.texture();
-                    let image = texture_info.source();
-                    match image.source() {
-                        gltf::image::Source::View { view, mime_type: _ } => {
-                            let buffer_data = &buffers[view.buffer().index()];
-                            let start = view.offset();
-                            let end = start + view.length();
-                            buffer_data[start..end].to_vec()
-                        }
-                        gltf::image::Source::Uri { uri, mime_type: _ } => {
-                            log::warn!("External URI textures not supported: {}", uri);
-                            GREY_TEXTURE_BYTES.to_vec()
-                        }
-                    }
-                } else {
-                    GREY_TEXTURE_BYTES.to_vec()
-                };
-
-            texture_data.push((material_name, image_data));
-        }
-
-        if texture_data.is_empty() {
-            texture_data.push(("Default".to_string(), GREY_TEXTURE_BYTES.to_vec()));
-        }
-
-        let parallel_start = Instant::now();
-        let processed_materials: Vec<_> = texture_data
-            .into_par_iter()
-            .map(|(material_name, image_data)| {
-                let material_start = Instant::now();
-
-                let diffuse_image =
-                    image::load_from_memory(&image_data).expect("Failed to load image from memory");
-                let diffuse_rgba = diffuse_image.to_rgba8();
-                let dimensions = diffuse_image.dimensions();
-
-                log::debug!(
-                    "Processed material '{}' in {:?}",
-                    material_name,
-                    material_start.elapsed()
-                );
-
-                ParsedMaterialData {
-                    name: material_name,
-                    rgba_data: diffuse_rgba.into_raw(),
-                    dimensions,
-                }
-            })
-            .collect();
-
-        log::debug!(
-            "Parallel material processing took: {:?}",
-            parallel_start.elapsed()
-        );
-
-        let mut mesh_data = Vec::new();
-        for mesh in gltf.meshes() {
-            log::debug!("Processing mesh: {:?}", mesh.name());
-            for primitive in mesh.primitives() {
-                let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
-
-                let positions: Vec<[f32; 3]> = reader
-                    .read_positions()
-                    .ok_or_else(|| anyhow::anyhow!("Mesh missing positions"))?
-                    .collect();
-
-                let normals: Vec<[f32; 3]> = reader
-                    .read_normals()
-                    .map(|iter| iter.collect())
-                    .unwrap_or_else(|| vec![[0.0, 1.0, 0.0]; positions.len()]);
-
-                let tex_coords: Vec<[f32; 2]> = reader
-                    .read_tex_coords(0)
-                    .map(|iter| iter.into_f32().collect())
-                    .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
-
-                let vertices: Vec<ModelVertex> = positions
-                    .iter()
-                    .zip(normals.iter())
-                    .zip(tex_coords.iter())
-                    .map(|((pos, norm), tex)| ModelVertex {
-                        position: *pos,
-                        normal: *norm,
-                        tex_coords: *tex,
-                    })
-                    .collect();
-
-                let indices: Vec<u32> = reader
-                    .read_indices()
-                    .ok_or_else(|| anyhow::anyhow!("Mesh missing indices"))?
-                    .into_u32()
-                    .collect();
-
-                let material_index = primitive.material().index().unwrap_or(0);
-
-                mesh_data.push(ParsedMeshData {
-                    name: mesh.name().unwrap_or("Unnamed Mesh").to_string(),
-                    vertices,
-                    indices,
-                    material_index,
-                });
-            }
-        }
-
-        let parsed_data = ParsedModelData {
-            label: label_str.to_string(),
-            path: res_ref,
-            mesh_data,
-            material_data: processed_materials,
-        };
-
-        log::debug!(
-            "Lazy load completed for model: {:?} in {:?}",
-            label_str,
-            start.elapsed()
-        );
-
-        Ok(LazyModel { parsed_data })
-    }
-
     pub async fn load_from_memory(
         graphics: Arc<SharedGraphicsContext>,
         buffer: impl AsRef<[u8]>,
         label: Option<&str>,
-    ) -> anyhow::Result<Model> {
+    ) -> anyhow::Result<LoadedModel> {
         let start = Instant::now();
         let mut hasher = DefaultHasher::new();
 
@@ -362,7 +98,7 @@ impl Model {
 
         if let Some(cached_model) = MODEL_CACHE.lock().get(&cache_key) {
             log::debug!("Model loaded from memory cache: {:?}", cache_key);
-            return Ok(cached_model.clone());
+            return Ok(LoadedModel::new(cached_model.clone()));
         }
 
         log::trace!(
@@ -532,27 +268,29 @@ impl Model {
         }
 
         log::debug!("Successfully loaded model [{:?}]", label);
-        let model = Model {
+        let model = Arc::new(Model {
             meshes,
             materials,
             label: label.unwrap_or("No named model").to_string(),
             path: res_ref,
             id: ModelId(hasher.finish()),
-        };
+        });
 
-        MODEL_CACHE.lock().insert(cache_key, model.clone());
+        MODEL_CACHE
+            .lock()
+            .insert(cache_key.clone(), Arc::clone(&model));
         log::trace!("==================== DONE ====================");
         log::debug!("Model cached from memory: {:?}", label);
         log::debug!("Took {:?} to load model: {:?}", start.elapsed(), label);
         log::trace!("==============================================");
-        Ok(model)
+        Ok(LoadedModel::new(model))
     }
 
     pub async fn load(
         graphics: Arc<SharedGraphicsContext>,
         path: &PathBuf,
         label: Option<&str>,
-    ) -> anyhow::Result<Model> {
+    ) -> anyhow::Result<LoadedModel> {
         let file_name = path.file_name();
         log::debug!("Loading model [{:?}]", file_name);
 
@@ -561,19 +299,34 @@ impl Model {
         log::debug!("Checking if model exists in cache");
         if let Some(cached_model) = MODEL_CACHE.lock().get(&path_str) {
             log::debug!("Model loaded from cache: {:?}", path_str);
-            return Ok(cached_model.clone());
+            return Ok(LoadedModel::new(cached_model.clone()));
         }
         log::debug!("Model does not exist in cache, loading memory...");
 
         log::debug!("Path of model: {}", path.display());
 
         let buffer = std::fs::read(path)?;
-        let mut model = Self::load_from_memory(graphics, buffer, label).await?;
-        model.path = ResourceReference::from_path(path)?;
+        let loaded = Self::load_from_memory(graphics, buffer, label).await?;
+
+        let mut model_clone: Model = (*loaded).clone();
+        if let Ok(reference) = ResourceReference::from_path(path) {
+            model_clone.path = reference;
+        }
+        if let Some(custom_label) = label {
+            model_clone.label = custom_label.to_string();
+        }
+
+        let updated = Arc::new(model_clone);
+        {
+            let mut cache = MODEL_CACHE.lock();
+            cache.insert(path_str, Arc::clone(&updated));
+            if let Some(custom_label) = label {
+                cache.insert(custom_label.to_string(), Arc::clone(&updated));
+            }
+        }
 
-        MODEL_CACHE.lock().insert(path_str, model.clone());
         log::debug!("Model cached and loaded: {:?}", file_name);
-        Ok(model)
+        Ok(LoadedModel::new(updated))
     }
 }
 
diff --git a/dropbear-engine/src/procedural/plane.rs b/dropbear-engine/src/procedural/plane.rs
index a019353..d7058f6 100644
--- a/dropbear-engine/src/procedural/plane.rs
+++ b/dropbear-engine/src/procedural/plane.rs
@@ -1,116 +1,17 @@
-use crate::entity::AdoptedEntity;
+//! A straight plane (and some components). Thats it.
+//!
+//! Inspiration taken from `https://github.com/4tkbytes/RedLight/blob/main/src/RedLight/Entities/Plane.cs`,
+//! my old game engine made in C sharp, where this is the plane "algorithm".
+
+use crate::entity::MeshRenderer;
 use crate::graphics::{SharedGraphicsContext, Texture};
-use crate::model::{LazyType, MODEL_CACHE, Material, Mesh, Model, ModelId, ModelVertex};
+use crate::model::{LoadedModel, MODEL_CACHE, Material, Mesh, Model, ModelId, ModelVertex};
 use crate::utils::{ResourceReference, ResourceReferenceType};
-use futures::executor::block_on;
-use image::GenericImageView;
 use std::hash::{DefaultHasher, Hash, Hasher};
-/// A straight plane (and some components). Thats it.
-///
-/// Inspiration taken from `https://github.com/4tkbytes/RedLight/blob/main/src/RedLight/Entities/Plane.cs`,
-/// my old game engine made in C sharp, where this is the plane "algorithm".
 use std::sync::Arc;
 use wgpu::{AddressMode, util::DeviceExt};
 
-/// Lazily creates a new Plane. This can only be accessed through the Default trait (which you shouldn't use),
-/// or the [`PlaneBuilder::lazy_build()`] (also taken from [`PlaneBuilder::new()`]).
-#[derive(Default)]
-pub struct LazyPlaneBuilder {
-    rgba_data: Vec<u8>,
-    dimensions: (u32, u32),
-    width: f32,
-    height: f32,
-    tiles_x: u32,
-    tiles_z: u32,
-    label: Option<String>,
-}
-
-impl LazyType for LazyPlaneBuilder {
-    type T = AdoptedEntity;
-
-    fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> {
-        let mut hasher = DefaultHasher::new();
-
-        let mut vertices = Vec::new();
-        let mut indices = Vec::new();
-
-        for z in 0..=1 {
-            for x in 0..=1 {
-                let position = [
-                    (x as f32 - 0.5) * self.width,
-                    0.0,
-                    (z as f32 - 0.5) * self.height,
-                ];
-                let normal = [0.0, 1.0, 0.0];
-                let tex_coords = [
-                    x as f32 * self.tiles_x as f32,
-                    z as f32 * self.tiles_z as f32,
-                ];
-
-                let _ = position.iter().map(|v| (*v as i32).hash(&mut hasher));
-                let _ = normal.iter().map(|v| (*v as i32).hash(&mut hasher));
-                let _ = tex_coords.iter().map(|v| (*v as i32).hash(&mut hasher));
-
-                vertices.push(ModelVertex {
-                    position,
-                    tex_coords,
-                    normal,
-                });
-            }
-        }
-
-        indices.extend_from_slice(&[0, 2, 1, 1, 2, 3]);
-        indices.hash(&mut hasher);
-
-        let vertex_buffer = graphics
-            .device
-            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                label: Some(&format!("{:?} Vertex Buffer", self.label.as_deref())),
-                contents: bytemuck::cast_slice(&vertices),
-                usage: wgpu::BufferUsages::VERTEX,
-            });
-        let index_buffer = graphics
-            .device
-            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                label: Some(&format!("{:?} Index Buffer", self.label.as_deref())),
-                contents: bytemuck::cast_slice(&indices),
-                usage: wgpu::BufferUsages::INDEX,
-            });
-
-        let mesh = Mesh {
-            name: "plane".to_string(),
-            vertex_buffer,
-            index_buffer,
-            num_elements: indices.len() as u32,
-            material: 0,
-        };
-
-        let diffuse_texture = Texture::new_with_sampler_with_rgba_buffer(
-            graphics.clone(),
-            &self.rgba_data,
-            self.dimensions,
-            AddressMode::Repeat,
-        );
-        let bind_group = diffuse_texture.bind_group().clone();
-        let material = Material {
-            name: "plane_material".to_string(),
-            diffuse_texture,
-            bind_group,
-        };
-
-        let model = Model {
-            label: self.label.as_deref().unwrap_or("Plane").to_string(),
-            path: ResourceReference::from_reference(ResourceReferenceType::Plane),
-            meshes: vec![mesh],
-            materials: vec![material],
-            id: ModelId(hasher.finish()),
-        };
-
-        Ok(block_on(AdoptedEntity::adopt(graphics, model)))
-    }
-}
-
-/// Creates a plane in the form of an AdoptedEntity.
+/// Creates a plane wrapped in a [`MeshRenderer`](crate::entity::MeshRenderer).
 pub struct PlaneBuilder {
     width: f32,
     height: f32,
@@ -146,37 +47,12 @@ impl PlaneBuilder {
         self
     }
 
-    pub async fn lazy_build(
-        mut self,
-        texture_bytes: &[u8],
-        label: Option<&str>,
-    ) -> anyhow::Result<LazyPlaneBuilder> {
-        if self.tiles_x == 0 && self.tiles_z == 0 {
-            self.tiles_x = self.width as u32;
-            self.tiles_z = self.height as u32;
-        }
-
-        let img = image::load_from_memory(texture_bytes)?;
-        let rgba = img.to_rgba8();
-        let dimensions = img.dimensions();
-
-        Ok(LazyPlaneBuilder {
-            rgba_data: rgba.into_raw(),
-            dimensions,
-            width: self.width,
-            height: self.height,
-            tiles_x: self.tiles_x,
-            tiles_z: self.tiles_z,
-            label: label.map(|s| s.to_string()),
-        })
-    }
-
     pub async fn build(
         mut self,
         graphics: Arc<SharedGraphicsContext>,
         texture_bytes: &[u8],
         label: Option<&str>,
-    ) -> anyhow::Result<AdoptedEntity> {
+    ) -> anyhow::Result<MeshRenderer> {
         let label = if let Some(label) = label {
             label.to_string()
         } else {
@@ -222,12 +98,11 @@ impl PlaneBuilder {
 
         let hash = hasher.finish();
 
-        let model = if let Some(cached_model) = MODEL_CACHE.lock().get(&label.clone()) {
-            log::debug!("Model loaded from cache: {:?}", label.clone());
-            Some(cached_model.clone())
-        } else {
-            None
-        };
+        if let Some(cached_model) = MODEL_CACHE.lock().get(&label) {
+            log::debug!("Model loaded from cache: {:?}", label);
+            let handle = LoadedModel::new(Arc::clone(cached_model));
+            return Ok(MeshRenderer::from_handle(handle));
+        }
 
         let vertex_buffer = graphics
             .device
@@ -261,20 +136,17 @@ impl PlaneBuilder {
             bind_group,
         };
 
-        let model = if let Some(m) = model {
-            m
-        } else {
-            let m = Model {
-                label: label.to_string(),
-                path: ResourceReference::from_reference(ResourceReferenceType::Plane),
-                meshes: vec![mesh],
-                materials: vec![material],
-                id: ModelId(hash),
-            };
-            MODEL_CACHE.lock().insert(label, m.clone());
-            m
-        };
+        let model = Arc::new(Model {
+            label: label.clone(),
+            path: ResourceReference::from_reference(ResourceReferenceType::Plane),
+            meshes: vec![mesh],
+            materials: vec![material],
+            id: ModelId(hash),
+        });
+
+        MODEL_CACHE.lock().insert(label, Arc::clone(&model));
 
-        Ok(AdoptedEntity::adopt(graphics, model).await)
+        let handle = LoadedModel::new(model);
+        Ok(MeshRenderer::from_handle(handle))
     }
 }
diff --git a/eucalyptus-core/src/camera.rs b/eucalyptus-core/src/camera.rs
index 31f0a25..c05419f 100644
--- a/eucalyptus-core/src/camera.rs
+++ b/eucalyptus-core/src/camera.rs
@@ -1,12 +1,10 @@
-use dropbear_engine::camera::Camera;
+use dropbear_engine::camera::{Camera, CameraSettings};
 use glam::DVec3;
 use serde::{Deserialize, Serialize};
 
 #[derive(Debug, Clone)]
 pub struct CameraComponent {
-    pub speed: f64,
-    pub sensitivity: f64,
-    pub fov_y: f64,
+    pub settings: CameraSettings,
     pub camera_type: CameraType,
     pub starting_camera: bool,
 }
@@ -20,18 +18,14 @@ impl Default for CameraComponent {
 impl CameraComponent {
     pub fn new() -> Self {
         Self {
-            speed: 5.0,
-            sensitivity: 0.1,
-            fov_y: 60.0,
+            settings: CameraSettings::new(5.0, 0.1, 60.0),
             camera_type: CameraType::Normal,
             starting_camera: false,
         }
     }
 
     pub fn update(&mut self, camera: &mut Camera) {
-        camera.speed = self.speed;
-        camera.sensitivity = self.sensitivity;
-        camera.fov_y = self.fov_y;
+        camera.settings = self.settings;
     }
 
     // setting camera offset is just adding the CameraFollowTarget struct
diff --git a/eucalyptus-core/src/lib.rs b/eucalyptus-core/src/lib.rs
index c6c97b6..31da7f7 100644
--- a/eucalyptus-core/src/lib.rs
+++ b/eucalyptus-core/src/lib.rs
@@ -12,6 +12,9 @@ pub mod window;
 
 pub use egui;
 
+/// The appdata directory for storing any information.
+///
+/// By default, most of its items are located in [`app_dirs2::AppDataType::UserData`].
 pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo {
     name: "Eucalyptus",
     author: "4tkbytes",
diff --git a/eucalyptus-core/src/scripting/jni/exports.rs b/eucalyptus-core/src/scripting/jni/exports.rs
index f4290b3..7c80252 100644
--- a/eucalyptus-core/src/scripting/jni/exports.rs
+++ b/eucalyptus-core/src/scripting/jni/exports.rs
@@ -7,7 +7,7 @@ use crate::states::{ModelProperties, Value};
 use crate::utils::keycode_from_ordinal;
 use crate::window::{GraphicsCommand, WindowCommand};
 use dropbear_engine::camera::Camera;
-use dropbear_engine::entity::{AdoptedEntity, Transform};
+use dropbear_engine::entity::{MeshRenderer, Transform};
 use glam::{DQuat, DVec3};
 use hecs::World;
 use jni::JNIEnv;
@@ -53,8 +53,8 @@ pub fn Java_com_dropbear_ffi_JNINative_getEntity(
 
     let world = unsafe { &mut *world };
 
-    for (id, entity) in world.query::<&AdoptedEntity>().iter() {
-        if entity.model.label == label_str {
+    for (id, renderer) in world.query::<&MeshRenderer>().iter() {
+        if renderer.handle().label == label_str {
             return id.id() as jlong;
         }
     }
@@ -81,7 +81,7 @@ pub fn Java_com_dropbear_ffi_JNINative_getTransform(
 
     let entity = unsafe { world.find_entity_from_id(entity_id as u32) };
 
-    if let Ok(mut q) = world.query_one::<(&AdoptedEntity, &Transform)>(entity)
+    if let Ok(mut q) = world.query_one::<(&MeshRenderer, &Transform)>(entity)
         && let Some((_, transform)) = q.get()
     {
         let new_transform = *transform;
@@ -479,7 +479,7 @@ pub fn Java_com_dropbear_ffi_JNINative_getStringProperty(
 
     let world = unsafe { &mut *world };
     let entity = unsafe { world.find_entity_from_id(entity_id as u32) };
-    if let Ok(mut q) = world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity)
+    if let Ok(mut q) = world.query_one::<(&MeshRenderer, &ModelProperties)>(entity)
         && let Some((_, props)) = q.get()
     {
         let string = env.get_string(&property_name);
@@ -548,7 +548,7 @@ pub fn Java_com_dropbear_ffi_JNINative_getIntProperty(
 
     let world = unsafe { &mut *world };
     let entity = unsafe { world.find_entity_from_id(entity_id as u32) };
-    if let Ok(mut q) = world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity)
+    if let Ok(mut q) = world.query_one::<(&MeshRenderer, &ModelProperties)>(entity)
         && let Some((_, props)) = q.get()
     {
         let string = env.get_string(&property_name);
@@ -609,7 +609,7 @@ pub fn Java_com_dropbear_ffi_JNINative_getLongProperty(
 
     let world = unsafe { &mut *world };
     let entity = unsafe { world.find_entity_from_id(entity_id as u32) };
-    if let Ok(mut q) = world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity)
+    if let Ok(mut q) = world.query_one::<(&MeshRenderer, &ModelProperties)>(entity)
         && let Some((_, props)) = q.get()
     {
         let string = env.get_string(&property_name);
@@ -667,7 +667,7 @@ pub fn Java_com_dropbear_ffi_JNINative_getFloatProperty(
 
     let world = unsafe { &mut *world };
     let entity = unsafe { world.find_entity_from_id(entity_id as u32) };
-    if let Ok(mut q) = world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity)
+    if let Ok(mut q) = world.query_one::<(&MeshRenderer, &ModelProperties)>(entity)
         && let Some((_, props)) = q.get()
     {
         let string = env.get_string(&property_name);
@@ -725,7 +725,7 @@ pub fn Java_com_dropbear_ffi_JNINative_getBoolProperty(
 
     let world = unsafe { &mut *world };
     let entity = unsafe { world.find_entity_from_id(entity_id as u32) };
-    if let Ok(mut q) = world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity)
+    if let Ok(mut q) = world.query_one::<(&MeshRenderer, &ModelProperties)>(entity)
         && let Some((_, props)) = q.get()
     {
         let string = env.get_string(&property_name);
@@ -789,7 +789,7 @@ pub fn Java_com_dropbear_ffi_JNINative_getVec3Property(
 
     let world = unsafe { &mut *world };
     let entity = unsafe { world.find_entity_from_id(entity_id as u32) };
-    if let Ok(mut q) = world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity)
+    if let Ok(mut q) = world.query_one::<(&MeshRenderer, &ModelProperties)>(entity)
         && let Some((_, props)) = q.get()
     {
         let string = env.get_string(&property_name);
@@ -889,7 +889,7 @@ pub fn Java_com_dropbear_ffi_JNINative_setStringProperty(
         return;
     };
 
-    if let Ok((_, props)) = world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    if let Ok((_, props)) = world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         props.set_property(key, Value::String(value));
     } else {
         eprintln!(
@@ -929,7 +929,7 @@ pub fn Java_com_dropbear_ffi_JNINative_setIntProperty(
         return;
     };
 
-    if let Ok((_, props)) = world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    if let Ok((_, props)) = world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         props.set_property(key, Value::Int(value as i64));
     } else {
         eprintln!(
@@ -971,7 +971,7 @@ pub fn Java_com_dropbear_ffi_JNINative_setLongProperty(
         return;
     };
 
-    if let Ok((_, props)) = world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    if let Ok((_, props)) = world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         props.set_property(key, Value::Int(value));
     } else {
         eprintln!(
@@ -1013,7 +1013,7 @@ pub fn Java_com_dropbear_ffi_JNINative_setFloatProperty(
         return;
     };
 
-    if let Ok((_, props)) = world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    if let Ok((_, props)) = world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         props.set_property(key, Value::Float(value));
     } else {
         eprintln!(
@@ -1057,7 +1057,7 @@ pub fn Java_com_dropbear_ffi_JNINative_setBoolProperty(
 
     let bool_value = value != 0;
 
-    if let Ok((_, props)) = world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    if let Ok((_, props)) = world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         props.set_property(key, Value::Bool(bool_value));
     } else {
         eprintln!(
@@ -1131,7 +1131,7 @@ pub fn Java_com_dropbear_ffi_JNINative_setVec3Property(
         return;
     }
 
-    if let Ok((_, props)) = world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    if let Ok((_, props)) = world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         props.set_property(key, Value::Vec3(values));
     } else {
         eprintln!(
@@ -1253,13 +1253,13 @@ pub fn Java_com_dropbear_ffi_JNINative_getCamera(
                 JValue::Object(&target),
                 JValue::Object(&up),
                 JValue::Double(cam.aspect),
-                JValue::Double(cam.fov_y),
+                JValue::Double(cam.settings.fov_y),
                 JValue::Double(cam.znear),
                 JValue::Double(cam.zfar),
                 JValue::Double(cam.yaw),
                 JValue::Double(cam.pitch),
-                JValue::Double(cam.speed),
-                JValue::Double(cam.sensitivity),
+                JValue::Double(cam.settings.speed),
+                JValue::Double(cam.settings.sensitivity),
             ],
         ) {
             v
@@ -1380,13 +1380,13 @@ pub fn Java_com_dropbear_ffi_JNINative_getAttachedCamera(
                 JValue::Object(&target),
                 JValue::Object(&up),
                 JValue::Double(cam.aspect),
-                JValue::Double(cam.fov_y),
+                JValue::Double(cam.settings.fov_y),
                 JValue::Double(cam.znear),
                 JValue::Double(cam.zfar),
                 JValue::Double(cam.yaw),
                 JValue::Double(cam.pitch),
-                JValue::Double(cam.speed),
-                JValue::Double(cam.sensitivity),
+                JValue::Double(cam.settings.speed),
+                JValue::Double(cam.settings.sensitivity),
             ],
         ) {
             v
@@ -1619,13 +1619,13 @@ pub fn Java_com_dropbear_ffi_JNINative_setCamera(
             cam.eye = eye.as_dvec3();
             cam.target = target.as_dvec3();
             cam.up = up.as_dvec3();
-            cam.fov_y = fov_y;
+            cam.settings.fov_y = fov_y;
             cam.znear = znear;
             cam.zfar = zfar;
             cam.yaw = yaw;
             cam.pitch = pitch;
-            cam.speed = speed;
-            cam.sensitivity = sensitivity;
+            cam.settings.speed = speed;
+            cam.settings.sensitivity = sensitivity;
         } else {
             eprintln!(
                 "[Java_com_dropbear_ffi_JNINative_setCamera] [ERROR] Entity does not have a Camera component"
diff --git a/eucalyptus-core/src/scripting/native/exports.rs b/eucalyptus-core/src/scripting/native/exports.rs
index 3510583..c49771b 100644
--- a/eucalyptus-core/src/scripting/native/exports.rs
+++ b/eucalyptus-core/src/scripting/native/exports.rs
@@ -6,7 +6,7 @@ use crate::states::{ModelProperties, Value};
 use crate::utils::keycode_from_ordinal;
 use crate::window::{GraphicsCommand, WindowCommand};
 use dropbear_engine::camera::Camera;
-use dropbear_engine::entity::{AdoptedEntity, Transform};
+use dropbear_engine::entity::{MeshRenderer, Transform};
 use glam::{DQuat, DVec3};
 use hecs::World;
 use std::ffi::{CStr, c_char};
@@ -32,8 +32,8 @@ pub unsafe extern "C" fn dropbear_get_entity(
         }
     };
 
-    for (id, entity) in world.query::<&AdoptedEntity>().iter() {
-        if entity.model.label == label_str {
+    for (id, renderer) in world.query::<&MeshRenderer>().iter() {
+        if renderer.handle().label == label_str {
             unsafe { *out_entity = id.id() as i64 };
             return 0;
         }
@@ -150,7 +150,7 @@ pub unsafe extern "C" fn dropbear_get_string_property(
         }
     };
 
-    match world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity) {
+    match world.query_one::<(&MeshRenderer, &ModelProperties)>(entity) {
         Ok(mut q) => {
             if let Some((_, props)) = q.get() {
                 if let Some(Value::String(val)) = props.get_property(label_str) {
@@ -202,7 +202,7 @@ pub unsafe extern "C" fn dropbear_get_int_property(
         Err(_) => return -108,
     };
 
-    match world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity) {
+    match world.query_one::<(&MeshRenderer, &ModelProperties)>(entity) {
         Ok(mut q) => {
             if let Some((_, props)) = q.get() {
                 if let Some(Value::Int(val)) = props.get_property(label_str) {
@@ -238,7 +238,7 @@ pub unsafe extern "C" fn dropbear_get_long_property(
         Err(_) => return -108,
     };
 
-    match world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity) {
+    match world.query_one::<(&MeshRenderer, &ModelProperties)>(entity) {
         Ok(mut q) => {
             if let Some((_, props)) = q.get() {
                 if let Some(Value::Int(val)) = props.get_property(label_str) {
@@ -274,7 +274,7 @@ pub unsafe extern "C" fn dropbear_get_float_property(
         Err(_) => return -108,
     };
 
-    match world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity) {
+    match world.query_one::<(&MeshRenderer, &ModelProperties)>(entity) {
         Ok(mut q) => {
             if let Some((_, props)) = q.get() {
                 if let Some(Value::Float(val)) = props.get_property(label_str) {
@@ -310,7 +310,7 @@ pub unsafe extern "C" fn dropbear_get_double_property(
         Err(_) => return -108,
     };
 
-    match world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity) {
+    match world.query_one::<(&MeshRenderer, &ModelProperties)>(entity) {
         Ok(mut q) => {
             if let Some((_, props)) = q.get() {
                 if let Some(Value::Float(val)) = props.get_property(label_str) {
@@ -346,7 +346,7 @@ pub unsafe extern "C" fn dropbear_get_bool_property(
         Err(_) => return -108,
     };
 
-    match world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity) {
+    match world.query_one::<(&MeshRenderer, &ModelProperties)>(entity) {
         Ok(mut q) => {
             if let Some((_, props)) = q.get() {
                 if let Some(Value::Bool(val)) = props.get_property(label_str) {
@@ -389,7 +389,7 @@ pub unsafe extern "C" fn dropbear_get_vec3_property(
         Err(_) => return -108,
     };
 
-    match world.query_one::<(&AdoptedEntity, &ModelProperties)>(entity) {
+    match world.query_one::<(&MeshRenderer, &ModelProperties)>(entity) {
         Ok(mut q) => {
             if let Some((_, props)) = q.get() {
                 if let Some(Value::Vec3([x, y, z])) = props.get_property(label_str) {
@@ -434,7 +434,7 @@ pub unsafe extern "C" fn dropbear_set_string_property(
         Err(_) => return -108,
     };
 
-    match world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    match world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         Ok((_, props)) => {
             props.set_property(label_str, Value::String(value_str));
             0
@@ -462,7 +462,7 @@ pub unsafe extern "C" fn dropbear_set_int_property(
         Err(_) => return -108,
     };
 
-    match world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    match world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         Ok((_, props)) => {
             props.set_property(label_str, Value::Int(value as i64));
             0
@@ -490,7 +490,7 @@ pub unsafe extern "C" fn dropbear_set_long_property(
         Err(_) => return -108,
     };
 
-    match world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    match world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         Ok((_, props)) => {
             props.set_property(label_str, Value::Int(value));
             0
@@ -518,7 +518,7 @@ pub unsafe extern "C" fn dropbear_set_float_property(
         Err(_) => return -108,
     };
 
-    match world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    match world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         Ok((_, props)) => {
             props.set_property(label_str, Value::Float(value as f64));
             0
@@ -546,7 +546,7 @@ pub unsafe extern "C" fn dropbear_set_double_property(
         Err(_) => return -108,
     };
 
-    match world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    match world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         Ok((_, props)) => {
             props.set_property(label_str, Value::Float(value));
             0
@@ -574,7 +574,7 @@ pub unsafe extern "C" fn dropbear_set_bool_property(
         Err(_) => return -108,
     };
 
-    match world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    match world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         Ok((_, props)) => {
             props.set_property(label_str, Value::Bool(value != 0));
             0
@@ -604,7 +604,7 @@ pub unsafe extern "C" fn dropbear_set_vec3_property(
         Err(_) => return -108,
     };
 
-    match world.query_one_mut::<(&AdoptedEntity, &mut ModelProperties)>(entity) {
+    match world.query_one_mut::<(&MeshRenderer, &mut ModelProperties)>(entity) {
         Ok((_, props)) => {
             props.set_property(label_str, Value::Vec3([x, y, z]));
             0
@@ -829,13 +829,13 @@ pub unsafe extern "C" fn dropbear_get_camera(
             };
 
             (*out_camera).aspect = cam.aspect;
-            (*out_camera).fov_y = cam.fov_y;
+            (*out_camera).fov_y = cam.settings.fov_y;
             (*out_camera).znear = cam.znear;
             (*out_camera).zfar = cam.zfar;
             (*out_camera).yaw = cam.yaw;
             (*out_camera).pitch = cam.pitch;
-            (*out_camera).speed = cam.speed;
-            (*out_camera).sensitivity = cam.sensitivity;
+            (*out_camera).speed = cam.settings.speed;
+            (*out_camera).sensitivity = cam.settings.sensitivity;
         }
 
         return 0;
@@ -897,13 +897,13 @@ pub unsafe extern "C" fn dropbear_get_attached_camera(
                     };
 
                     (*out_camera).aspect = cam.aspect;
-                    (*out_camera).fov_y = cam.fov_y;
+                    (*out_camera).fov_y = cam.settings.fov_y;
                     (*out_camera).znear = cam.znear;
                     (*out_camera).zfar = cam.zfar;
                     (*out_camera).yaw = cam.yaw;
                     (*out_camera).pitch = cam.pitch;
-                    (*out_camera).speed = cam.speed;
-                    (*out_camera).sensitivity = cam.sensitivity;
+                    (*out_camera).speed = cam.settings.speed;
+                    (*out_camera).sensitivity = cam.settings.sensitivity;
                 }
 
                 0
@@ -955,13 +955,13 @@ pub unsafe extern "C" fn dropbear_set_camera(
             );
 
             cam.aspect = cam_data.aspect;
-            cam.fov_y = cam_data.fov_y;
+            cam.settings.fov_y = cam_data.fov_y;
             cam.znear = cam_data.znear;
             cam.zfar = cam_data.zfar;
             cam.yaw = cam_data.yaw;
             cam.pitch = cam_data.pitch;
-            cam.speed = cam_data.speed;
-            cam.sensitivity = cam_data.sensitivity;
+            cam.settings.speed = cam_data.speed;
+            cam.settings.sensitivity = cam_data.sensitivity;
 
             0
         }
diff --git a/eucalyptus-core/src/spawn.rs b/eucalyptus-core/src/spawn.rs
index 304b24b..d7e26ed 100644
--- a/eucalyptus-core/src/spawn.rs
+++ b/eucalyptus-core/src/spawn.rs
@@ -23,7 +23,7 @@ pub struct PendingSpawn {
     pub properties: ModelProperties,
     /// An optional future handle to an object.
     ///
-    /// If one is specified, it is assumed that the returned object is an [`AdoptedEntity`](dropbear_engine::entity::AdoptedEntity).
+    /// If one is specified, it is assumed that the returned object is a [`MeshRenderer`](dropbear_engine::entity::MeshRenderer).
     ///
     /// If one is NOT specified, it will be created based off the information provided. It is **recommended** to set it to [`None`].
     pub handle: Option<FutureHandle>,
diff --git a/eucalyptus-core/src/states.rs b/eucalyptus-core/src/states.rs
index 06691cf..58e8026 100644
--- a/eucalyptus-core/src/states.rs
+++ b/eucalyptus-core/src/states.rs
@@ -1,8 +1,8 @@
 use crate::camera::{CameraComponent, CameraType};
 use crate::utils::PROTO_TEXTURE;
 use chrono::Utc;
-use dropbear_engine::camera::{Camera, CameraBuilder};
-use dropbear_engine::entity::{AdoptedEntity, Transform};
+use dropbear_engine::camera::{Camera, CameraBuilder, CameraSettings};
+use dropbear_engine::entity::{MeshRenderer, Transform};
 use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::lighting::{Light, LightComponent};
 use dropbear_engine::model::Model;
@@ -10,7 +10,7 @@ use dropbear_engine::procedural::plane::PlaneBuilder;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use egui::Ui;
 use egui_dock_fork::DockState;
-use glam::DVec3;
+use glam::{DQuat, DVec3};
 use once_cell::sync::Lazy;
 use parking_lot::RwLock;
 use rayon::prelude::*;
@@ -560,15 +560,15 @@ impl EntityNode {
         let mut nodes = Vec::new();
         let mut handled = std::collections::HashSet::new();
 
-        for (id, (script, _transform, adopted)) in world
+        for (id, (script, _transform, renderer)) in world
             .query::<(
                 &ScriptComponent,
                 &dropbear_engine::entity::Transform,
-                &dropbear_engine::entity::AdoptedEntity,
+                &dropbear_engine::entity::MeshRenderer,
             )>()
             .iter()
         {
-            let name = adopted.model.label.clone();
+            let name = renderer.handle().label.clone();
             let mut children = vec![
                 EntityNode::Entity {
                     id,
@@ -599,17 +599,17 @@ impl EntityNode {
         }
 
         // Handle single entities (and potentially cameras)
-        for (id, (_, adopted)) in world
+        for (id, (_, renderer)) in world
             .query::<(
                 &dropbear_engine::entity::Transform,
-                &dropbear_engine::entity::AdoptedEntity,
+                &dropbear_engine::entity::MeshRenderer,
             )>()
             .iter()
         {
             if handled.contains(&id) {
                 continue;
             }
-            let name = adopted.model.label.clone();
+            let name = renderer.handle().label.clone();
 
             // Check if this entity has camera components
             if let Ok(mut camera_query) = world.query_one::<(&Camera, &CameraComponent)>(id) {
@@ -655,9 +655,9 @@ impl EntityNode {
             handled.insert(id);
         }
 
-        // Handle standalone cameras (cameras without AdoptedEntity - like viewport cameras)
+        // Handle standalone cameras (cameras without MeshRenderer - like viewport cameras)
         for (entity, (camera, component)) in world.query::<(&Camera, &CameraComponent)>().iter() {
-            if world.get::<&AdoptedEntity>(entity).is_err() {
+            if world.get::<&MeshRenderer>(entity).is_err() {
                 nodes.push(EntityNode::Camera {
                     id: entity,
                     name: camera.label.clone(),
@@ -706,8 +706,8 @@ impl Default for CameraConfig {
             // follow_offset: None,
             label: String::new(),
             camera_type: CameraType::Normal,
-            speed: default.speed as f32,
-            sensitivity: default.sensitivity as f32,
+            speed: default.settings.speed as f32,
+            sensitivity: default.settings.sensitivity as f32,
             starting_camera: false,
         }
     }
@@ -726,11 +726,11 @@ impl CameraConfig {
             camera_type: component.camera_type,
             up: camera.up.to_array(),
             aspect: camera.aspect,
-            fov: camera.fov_y as f32,
+            fov: camera.settings.fov_y as f32,
             near: camera.znear as f32,
             far: camera.zfar as f32,
-            speed: component.speed as f32,
-            sensitivity: component.sensitivity as f32,
+            speed: component.settings.speed as f32,
+            sensitivity: component.settings.sensitivity as f32,
             // follow_target_entity_label: follow_target.map(|target| target.follow_target.clone()),
             // follow_offset: follow_target.map(|target| target.offset.to_array()),
             starting_camera: component.starting_camera,
@@ -1016,10 +1016,14 @@ impl SceneConfig {
                         reference
                     );
 
-                    let adopted =
-                        AdoptedEntity::new(graphics.clone(), &path, Some(&entity_config.label))
-                            .await?;
+                    let mut renderer = MeshRenderer::from_path(
+                        graphics.clone(),
+                        &path,
+                        Some(&entity_config.label),
+                    )
+                    .await?;
                     let transform = entity_config.transform;
+                    renderer.update(&transform);
 
                     let _entity = if let Some(camera_config) = &entity_config.camera {
                         let camera = Camera::new(
@@ -1029,19 +1033,23 @@ impl SceneConfig {
                                 target: DVec3::from_array(camera_config.target),
                                 up: DVec3::from_array(camera_config.up),
                                 aspect: camera_config.aspect,
-                                fov_y: camera_config.fov as f64,
                                 znear: camera_config.near as f64,
                                 zfar: camera_config.far as f64,
-                                speed: camera_config.speed as f64,
-                                sensitivity: camera_config.sensitivity as f64,
+                                settings: CameraSettings::new(
+                                    camera_config.speed as f64,
+                                    camera_config.sensitivity as f64,
+                                    camera_config.fov as f64,
+                                ),
                             },
                             Some(&camera_config.label),
                         );
 
                         let camera_component = CameraComponent {
-                            speed: camera_config.speed as f64,
-                            sensitivity: camera_config.sensitivity as f64,
-                            fov_y: camera_config.fov as f64,
+                            settings: CameraSettings::new(
+                                camera_config.speed as f64,
+                                camera_config.sensitivity as f64,
+                                camera_config.fov as f64,
+                            ),
                             camera_type: camera_config.camera_type,
                             starting_camera: camera_config.starting_camera,
                         };
@@ -1051,7 +1059,7 @@ impl SceneConfig {
                                 tags: script_config.tags.clone(),
                             };
                             world.spawn((
-                                adopted,
+                                renderer,
                                 transform,
                                 script,
                                 entity_config.properties.clone(),
@@ -1060,7 +1068,7 @@ impl SceneConfig {
                             ))
                         } else {
                             world.spawn((
-                                adopted,
+                                renderer,
                                 transform,
                                 entity_config.properties.clone(),
                                 camera,
@@ -1071,9 +1079,14 @@ impl SceneConfig {
                         let script = ScriptComponent {
                             tags: script_config.tags.clone(),
                         };
-                        world.spawn((adopted, transform, script, entity_config.properties.clone()))
+                        world.spawn((
+                            renderer,
+                            transform,
+                            script,
+                            entity_config.properties.clone(),
+                        ))
                     } else {
-                        world.spawn((adopted, transform, entity_config.properties.clone()))
+                        world.spawn((renderer, transform, entity_config.properties.clone()))
                     };
 
                     Ok(())
@@ -1088,9 +1101,10 @@ impl SceneConfig {
                         Some(&entity_config.label),
                     )
                     .await?;
-                    let adopted = AdoptedEntity::adopt(graphics.clone(), model).await;
+                    let mut renderer = MeshRenderer::from_handle(model);
 
                     let transform = entity_config.transform;
+                    renderer.update(&transform);
 
                     let _entity = if let Some(camera_config) = &entity_config.camera {
                         // Entity has camera components
@@ -1101,19 +1115,23 @@ impl SceneConfig {
                                 target: DVec3::from_array(camera_config.target),
                                 up: DVec3::from_array(camera_config.up),
                                 aspect: camera_config.aspect,
-                                fov_y: camera_config.fov as f64,
                                 znear: camera_config.near as f64,
                                 zfar: camera_config.far as f64,
-                                speed: camera_config.speed as f64,
-                                sensitivity: camera_config.sensitivity as f64,
+                                settings: CameraSettings::new(
+                                    camera_config.speed as f64,
+                                    camera_config.sensitivity as f64,
+                                    camera_config.fov as f64,
+                                ),
                             },
                             Some(&camera_config.label),
                         );
 
                         let camera_component = CameraComponent {
-                            speed: camera_config.speed as f64,
-                            sensitivity: camera_config.sensitivity as f64,
-                            fov_y: camera_config.fov as f64,
+                            settings: CameraSettings::new(
+                                camera_config.speed as f64,
+                                camera_config.sensitivity as f64,
+                                camera_config.fov as f64,
+                            ),
                             camera_type: camera_config.camera_type,
                             starting_camera: camera_config.starting_camera,
                         };
@@ -1123,7 +1141,7 @@ impl SceneConfig {
                                 tags: script_config.tags.clone(),
                             };
                             world.spawn((
-                                adopted,
+                                renderer,
                                 transform,
                                 script,
                                 entity_config.properties.clone(),
@@ -1132,7 +1150,7 @@ impl SceneConfig {
                             ))
                         } else {
                             world.spawn((
-                                adopted,
+                                renderer,
                                 transform,
                                 entity_config.properties.clone(),
                                 camera,
@@ -1146,13 +1164,13 @@ impl SceneConfig {
                                 tags: script_config.tags.clone(),
                             };
                             world.spawn((
-                                adopted,
+                                renderer,
                                 transform,
                                 script,
                                 entity_config.properties.clone(),
                             ))
                         } else {
-                            world.spawn((adopted, transform, entity_config.properties.clone()))
+                            world.spawn((renderer, transform, entity_config.properties.clone()))
                         }
                     };
 
@@ -1201,19 +1219,23 @@ impl SceneConfig {
                                 target: DVec3::from_array(camera_config.target),
                                 up: DVec3::from_array(camera_config.up),
                                 aspect: camera_config.aspect,
-                                fov_y: camera_config.fov as f64,
                                 znear: camera_config.near as f64,
                                 zfar: camera_config.far as f64,
-                                speed: camera_config.speed as f64,
-                                sensitivity: camera_config.sensitivity as f64,
+                                settings: CameraSettings::new(
+                                    camera_config.speed as f64,
+                                    camera_config.sensitivity as f64,
+                                    camera_config.fov as f64,
+                                ),
                             },
                             Some(&camera_config.label),
                         );
 
                         let camera_component = CameraComponent {
-                            speed: camera_config.speed as f64,
-                            sensitivity: camera_config.sensitivity as f64,
-                            fov_y: camera_config.fov as f64,
+                            settings: CameraSettings::new(
+                                camera_config.speed as f64,
+                                camera_config.sensitivity as f64,
+                                camera_config.fov as f64,
+                            ),
                             camera_type: camera_config.camera_type,
                             starting_camera: camera_config.starting_camera,
                         };
@@ -1320,19 +1342,23 @@ impl SceneConfig {
                     target: DVec3::from_array(camera_config.target),
                     up: DVec3::from_array(camera_config.up),
                     aspect: camera_config.aspect,
-                    fov_y: camera_config.fov as f64,
                     znear: camera_config.near as f64,
                     zfar: camera_config.far as f64,
-                    speed: camera_config.speed as f64,
-                    sensitivity: camera_config.sensitivity as f64,
+                    settings: CameraSettings::new(
+                        camera_config.speed as f64,
+                        camera_config.sensitivity as f64,
+                        camera_config.fov as f64,
+                    ),
                 },
                 Some(&camera_config.label),
             );
 
             let component = CameraComponent {
-                speed: camera_config.speed as f64,
-                sensitivity: camera_config.sensitivity as f64,
-                fov_y: camera_config.fov as f64,
+                settings: CameraSettings::new(
+                    camera_config.speed as f64,
+                    camera_config.sensitivity as f64,
+                    camera_config.fov as f64,
+                ),
                 camera_type: camera_config.camera_type,
                 starting_camera: camera_config.starting_camera,
             };
@@ -1360,8 +1386,12 @@ impl SceneConfig {
                     });
                 }
                 let comp = LightComponent::directional(glam::DVec3::ONE, 1.0);
+                let light_direction = LightComponent::default_direction();
+                let rotation =
+                    DQuat::from_rotation_arc(DVec3::new(0.0, 0.0, -1.0), light_direction);
                 let trans = Transform {
                     position: glam::DVec3::new(2.0, 4.0, 2.0),
+                    rotation,
                     ..Default::default()
                 };
                 let light =
@@ -1484,6 +1514,7 @@ pub enum EditorTab {
     ResourceInspector, // left side,
     ModelEntityList,   // right side,
     Viewport,          // middle,
+    ErrorConsole,
     Plugin(usize),
 }
 
diff --git a/eucalyptus-editor/Cargo.toml b/eucalyptus-editor/Cargo.toml
index 4671451..ca55172 100644
--- a/eucalyptus-editor/Cargo.toml
+++ b/eucalyptus-editor/Cargo.toml
@@ -37,6 +37,8 @@ tokio.workspace = true
 crossbeam-channel.workspace = true
 libloading.workspace = true
 indexmap.workspace = true
+open.workspace = true
+rustc_version_runtime.workspace = true
 
 [target.'cfg(not(target_os = "android"))'.dependencies]
 rfd.workspace = true
diff --git a/eucalyptus-editor/src/camera.rs b/eucalyptus-editor/src/camera.rs
index 679b129..58c95cc 100644
--- a/eucalyptus-editor/src/camera.rs
+++ b/eucalyptus-editor/src/camera.rs
@@ -103,7 +103,7 @@ impl InspectableComponent for CameraComponent {
                     if !matches!(self.camera_type, CameraType::Player) {
                         egui::ComboBox::from_id_salt(
                             "i aint r kelly the way i take the piss ; \
-                        but im mj coz my shots don't eva miss",
+                        but im mj, my shots don't eva miss",
                         )
                         .selected_text(format!("{:?}", self.camera_type))
                         .show_ui(ui, |ui| {
@@ -119,7 +119,7 @@ impl InspectableComponent for CameraComponent {
                     ui.horizontal(|ui| {
                         ui.label("Speed:");
                         ui.add(
-                            egui::DragValue::new(&mut self.speed)
+                            egui::DragValue::new(&mut self.settings.speed)
                                 .speed(0.1)
                                 .range(0.1..=20.0),
                         );
@@ -128,7 +128,7 @@ impl InspectableComponent for CameraComponent {
                     ui.horizontal(|ui| {
                         ui.label("Sensitivity:");
                         ui.add(
-                            egui::DragValue::new(&mut self.sensitivity)
+                            egui::DragValue::new(&mut self.settings.sensitivity)
                                 .speed(0.0001)
                                 .range(0.0001..=1.0),
                         );
@@ -136,7 +136,9 @@ impl InspectableComponent for CameraComponent {
 
                     ui.horizontal(|ui| {
                         ui.label("FOV:");
-                        ui.add(egui::Slider::new(&mut self.fov_y, 10.0..=120.0).suffix("°"));
+                        ui.add(
+                            egui::Slider::new(&mut self.settings.fov_y, 10.0..=120.0).suffix("°"),
+                        );
                     });
                 });
         });
diff --git a/eucalyptus-editor/src/editor/component.rs b/eucalyptus-editor/src/editor/component.rs
index c8c5434..3356e2f 100644
--- a/eucalyptus-editor/src/editor/component.rs
+++ b/eucalyptus-editor/src/editor/component.rs
@@ -2,14 +2,13 @@
 
 use crate::editor::{EntityType, Signal, StaticallyKept, UndoableAction};
 use dropbear_engine::attenuation::ATTENUATION_PRESETS;
-use dropbear_engine::entity::{AdoptedEntity, Transform};
+use dropbear_engine::entity::{MeshRenderer, Transform};
 use dropbear_engine::lighting::{Light, LightComponent, LightType};
 use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, TextEdit, Ui};
 use eucalyptus_core::states::{ModelProperties, Property, ScriptComponent, Value};
 use eucalyptus_core::warn;
 use glam::{DVec3, Vec3};
 use hecs::Entity;
-use std::sync::Arc;
 use std::time::Instant;
 
 /// A trait that can added to any component that allows you to inspect the value in the editor.
@@ -640,7 +639,7 @@ impl InspectableComponent for ScriptComponent {
     }
 }
 
-impl InspectableComponent for AdoptedEntity {
+impl InspectableComponent for MeshRenderer {
     fn inspect(
         &mut self,
         entity: &mut Entity,
@@ -655,12 +654,12 @@ impl InspectableComponent for AdoptedEntity {
             ui.horizontal(|ui| {
                 ui.label("Name: ");
 
-                let resp = ui.text_edit_singleline(&mut Arc::make_mut(&mut self.model).label);
+                let resp = ui.text_edit_singleline(&mut self.make_model_mut().label);
 
                 if resp.changed() {
                     if cfg.old_label_entity.is_none() {
                         cfg.old_label_entity = Some(*entity);
-                        cfg.label_original = Some(self.model.label.clone());
+                        cfg.label_original = Some(self.handle().label.clone());
                     }
                     cfg.label_last_edit = Some(Instant::now());
                 }
diff --git a/eucalyptus-editor/src/editor/dock.rs b/eucalyptus-editor/src/editor/dock.rs
index 21d3fad..e9d64a5 100644
--- a/eucalyptus-editor/src/editor/dock.rs
+++ b/eucalyptus-editor/src/editor/dock.rs
@@ -2,6 +2,7 @@ use super::*;
 use crate::editor::ViewportMode;
 use std::{
     collections::{HashMap, HashSet},
+    path::PathBuf,
     sync::LazyLock,
 };
 
@@ -9,11 +10,10 @@ use crate::editor::component::InspectableComponent;
 use crate::plugin::PluginRegistry;
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
 use dropbear_engine::{
-    entity::Transform,
+    entity::{MeshRenderer, Transform},
     lighting::{Light, LightComponent},
 };
-use egui;
-use egui::CollapsingHeader;
+use egui::{self, CollapsingHeader, Margin, RichText};
 use egui_dock_fork::TabViewer;
 use egui_extras;
 use eucalyptus_core::APP_INFO;
@@ -37,6 +37,7 @@ pub struct EditorTabViewer<'a> {
     pub editor_mode: &'a mut EditorState,
     pub active_camera: &'a mut Arc<Mutex<Option<Entity>>>,
     pub plugin_registry: &'a mut PluginRegistry,
+    pub build_logs: &'a mut Vec<String>,
     // "wah wah its unsafe, its using raw pointers" shut the fuck up if it breaks i will know
     pub editor: *mut Editor,
 }
@@ -125,6 +126,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                     "Unknown Plugin Name".into()
                 }
             }
+            EditorTab::ErrorConsole => "Error Console".into(),
         }
     }
 
@@ -231,7 +233,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                         );
 
                                         let proj_matrix = glam::DMat4::perspective_lh(
-                                            camera.fov_y.to_radians(),
+                                            camera.settings.fov_y.to_radians(),
                                             camera.aspect,
                                             camera.znear,
                                             camera.zfar,
@@ -298,7 +300,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                             {
                                                 for (entity_id, (transform, _)) in self
                                                     .world
-                                                    .query::<(&Transform, &AdoptedEntity)>()
+                                                    .query::<(&Transform, &MeshRenderer)>()
                                                     .iter()
                                                 {
                                                     entity_count += 1;
@@ -745,7 +747,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                 if let Some(entity) = self.selected_entity {
                     let mut local_set_initial_camera = false;
                     if let Ok(mut q) = self.world.query_one::<(
-                        &mut AdoptedEntity,
+                        &mut MeshRenderer,
                         Option<&mut Transform>,
                         Option<&mut ModelProperties>,
                         Option<&mut ScriptComponent>,
@@ -781,7 +783,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                     ui,
                                     self.undo_stack,
                                     self.signal,
-                                    &mut Arc::make_mut(&mut e.model).label,
+                                    &mut e.make_model_mut().label,
                                 );
                             }
 
@@ -793,7 +795,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                     ui,
                                     self.undo_stack,
                                     self.signal,
-                                    &mut Arc::make_mut(&mut e.model).label,
+                                    &mut e.make_model_mut().label,
                                 );
                             }
 
@@ -894,7 +896,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                     ui,
                                     self.undo_stack,
                                     self.signal,
-                                    &mut Arc::make_mut(&mut e.model).label,
+                                    &mut e.make_model_mut().label,
                                 );
                             }
 
@@ -991,7 +993,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                         // Option<&mut CameraFollowTarget>,
                     )>(*entity)
                         && let Some((camera, camera_component)) = q.get()
-                        && self.world.get::<&AdoptedEntity>(*entity).is_err()
+                        && self.world.get::<&MeshRenderer>(*entity).is_err()
                     {
                         ui.vertical(|ui| {
                             camera.inspect(
@@ -1067,6 +1069,141 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                     );
                 }
             }
+            EditorTab::ErrorConsole => {
+                enum ErrorLevel {
+                    Warn,
+                    Error,
+                }
+
+                struct ConsoleItem {
+                    id: u64,
+                    error_level: ErrorLevel,
+                    msg: String,
+                    file_location: Option<PathBuf>,
+                    line_ref: Option<String>,
+                }
+
+                fn analyse_error(log: &Vec<String>) -> Vec<ConsoleItem> {
+                    fn parse_compiler_location(
+                        line: &str,
+                    ) -> Option<(ErrorLevel, PathBuf, String)> {
+                        let trimmed = line.trim_start();
+                        let (error_level, rest) =
+                            if let Some(r) = trimmed.strip_prefix("e: file:///") {
+                                (ErrorLevel::Error, r)
+                            } else if let Some(r) = trimmed.strip_prefix("w: file:///") {
+                                (ErrorLevel::Warn, r)
+                            } else {
+                                return None;
+                            };
+
+                        let location = rest.split_whitespace().next()?;
+
+                        let mut segments = location.rsplitn(3, ':');
+                        let column = segments.next()?;
+                        let row = segments.next()?;
+                        let path = segments.next()?;
+
+                        Some((error_level, PathBuf::from(path), format!("{row}:{column}")))
+                    }
+
+                    let mut list: Vec<ConsoleItem> = Vec::new();
+                    let index = 0;
+                    for line in log {
+                        if line.contains("The required library") {
+                            list.push(ConsoleItem {
+                                error_level: ErrorLevel::Error,
+                                msg: line.clone(),
+                                file_location: None,
+                                line_ref: None,
+                                id: index + 1,
+                            });
+                        }
+
+                        if let Some((error_level, path, loc)) = parse_compiler_location(line) {
+                            list.push(ConsoleItem {
+                                error_level,
+                                msg: line.clone(),
+                                file_location: Some(path),
+                                line_ref: Some(loc),
+                                id: index + 1,
+                            });
+                        }
+
+                        // thats it for now
+                    }
+                    list
+                }
+
+                let logs = analyse_error(&self.build_logs);
+
+                egui::ScrollArea::vertical()
+                    .auto_shrink([false, false])
+                    .show(ui, |ui| {
+                        if logs.is_empty() {
+                            ui.label("Build output will appear here once available.");
+                            return;
+                        }
+
+                        for item in &logs {
+                            let (bg_color, text_color) = match item.error_level {
+                                ErrorLevel::Error => (
+                                    egui::Color32::from_rgb(60, 20, 20),
+                                    egui::Color32::from_rgb(255, 200, 200),
+                                ),
+                                ErrorLevel::Warn => (
+                                    egui::Color32::from_rgb(40, 40, 10),
+                                    egui::Color32::from_rgb(255, 255, 200),
+                                ),
+                            };
+
+                            let available_width = ui.available_width();
+                            let frame = egui::Frame::new()
+                                .inner_margin(Margin::symmetric(8, 6))
+                                .fill(bg_color)
+                                .stroke(egui::Stroke::new(1.0, text_color));
+
+                            let response = frame
+                                .show(ui, |ui| {
+                                    ui.set_width(available_width - 10.0);
+                                    ui.horizontal(|ui| {
+                                        ui.label(RichText::new(&item.msg).color(text_color));
+                                    });
+                                })
+                                .response;
+
+                            if response.clicked() {
+                                log::debug!("Log item clicked: {}", &item.id);
+                                if let (Some(path), Some(loc)) =
+                                    (&item.file_location, &item.line_ref)
+                                {
+                                    let location_arg = format!("{}:{}", path.display(), loc);
+
+                                    match std::process::Command::new("code")
+                                        .args(["-g", &location_arg])
+                                        .spawn()
+                                        .map(|_| ())
+                                    {
+                                        Ok(()) => {
+                                            log::info!(
+                                                "Launched Visual Studio Code at the error: {}",
+                                                &location_arg
+                                            );
+                                        }
+                                        Err(e) => {
+                                            warn!(
+                                                "Failed to open '{}' in VS Code: {}",
+                                                location_arg, e
+                                            );
+                                        }
+                                    }
+                                }
+                            }
+
+                            ui.add_space(4.0);
+                        }
+                    });
+            }
         }
 
         let mut menu_action: Option<EditorTabMenuAction> = None;
@@ -1117,6 +1254,10 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                 menu_action = Some(EditorTabMenuAction::ViewportOption);
                             }
                         }
+                        EditorTab::ErrorConsole => {
+                            ui.set_min_width(150.0);
+                            ui.label("No actions available");
+                        }
                         EditorTab::Plugin(dock_info) => {
                             if self.editor.is_null() {
                                 panic!("Editor pointer is null, unexpected behaviour");
@@ -1193,7 +1334,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                         log::debug!("Add Component clicked");
                         if let Some(entity) = self.selected_entity {
                             {
-                                if let Ok(mut q) = self.world.query_one::<&AdoptedEntity>(*entity)
+                                if let Ok(mut q) = self.world.query_one::<&MeshRenderer>(*entity)
                                     && q.get().is_some()
                                 {
                                     log::debug!("Queried selected entity, it is an entity");
diff --git a/eucalyptus-editor/src/editor/input.rs b/eucalyptus-editor/src/editor/input.rs
index 2101d2f..e2bcbce 100644
--- a/eucalyptus-editor/src/editor/input.rs
+++ b/eucalyptus-editor/src/editor/input.rs
@@ -1,6 +1,6 @@
 use super::*;
 use dropbear_engine::{
-    entity::{AdoptedEntity, Transform},
+    entity::{MeshRenderer, Transform},
     input::{Controller, Keyboard, Mouse},
 };
 use eucalyptus_core::success_without_console;
@@ -131,14 +131,14 @@ impl Keyboard for Editor {
                         if let Some(entity) = &self.selected_entity {
                             let query = self
                                 .world
-                                .query_one::<(&AdoptedEntity, &Transform, &ModelProperties)>(
+                                .query_one::<(&MeshRenderer, &Transform, &ModelProperties)>(
                                     *entity,
                                 );
                             if let Ok(mut q) = query {
-                                if let Some((e, t, props)) = q.get() {
+                                if let Some((renderer, t, props)) = q.get() {
                                     let s_entity = SceneEntity {
-                                        model_path: e.model.path.clone(),
-                                        label: e.model.label.clone(),
+                                        model_path: renderer.handle().path.clone(),
+                                        label: renderer.handle().label.clone(),
                                         transform: *t,
                                         properties: props.clone(),
                                         script: None,
@@ -273,7 +273,10 @@ impl Mouse for Editor {
                 && let Some((camera, _)) = q.get()
             {
                 if let Some((dx, dy)) = delta {
-                    camera.track_mouse_delta(dx * camera.sensitivity, dy * camera.sensitivity);
+                    camera.track_mouse_delta(
+                        dx * camera.settings.sensitivity,
+                        dy * camera.settings.sensitivity,
+                    );
                     self.input_state.mouse_delta = Some((dx, dy));
                 } else {
                     log_once::warn_once!("Unable to track mouse delta, attempting fallback");
@@ -281,7 +284,10 @@ impl Mouse for Editor {
                     if let Some(old_mouse_pos) = self.input_state.last_mouse_pos {
                         let dx = position.x - old_mouse_pos.0;
                         let dy = position.y - old_mouse_pos.1;
-                        camera.track_mouse_delta(dx * camera.sensitivity, dy * camera.sensitivity);
+                        camera.track_mouse_delta(
+                            dx * camera.settings.sensitivity,
+                            dy * camera.settings.sensitivity,
+                        );
                         self.input_state.mouse_delta = Some((dx, dy));
                         log_once::debug_once!("Fallback mouse tracking used");
                     } else {
diff --git a/eucalyptus-editor/src/editor/mod.rs b/eucalyptus-editor/src/editor/mod.rs
index 2bfcc48..93c5a98 100644
--- a/eucalyptus-editor/src/editor/mod.rs
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -14,7 +14,7 @@ use crossbeam_channel::Receiver;
 use dropbear_engine::shader::Shader;
 use dropbear_engine::{
     camera::Camera,
-    entity::{AdoptedEntity, Transform},
+    entity::{MeshRenderer, Transform},
     future::FutureHandle,
     graphics::{RenderContext, SharedGraphicsContext},
     lighting::{Light, LightManager},
@@ -23,6 +23,7 @@ use dropbear_engine::{
 };
 use egui::{self, Context};
 use egui_dock_fork::{DockArea, DockState, NodeIndex, Style};
+use eucalyptus_core::APP_INFO;
 use eucalyptus_core::{
     camera::{CameraComponent, CameraType, DebugCamera},
     fatal, info,
@@ -131,6 +132,9 @@ pub struct Editor {
     current_scene_name: Option<String>,
     pending_scene_load: Option<PendingSceneLoad>,
     pending_scene_creation: Option<String>,
+
+    // about
+    show_about: bool,
 }
 
 impl Editor {
@@ -220,6 +224,7 @@ impl Editor {
             current_scene_name: None,
             pending_scene_load: None,
             pending_scene_creation: None,
+            show_about: false,
         })
     }
 
@@ -254,10 +259,10 @@ impl Editor {
         scene.lights.clear();
         scene.cameras.clear();
 
-        for (id, (adopted, transform, properties, script)) in self
+        for (id, (renderer, transform, properties, script)) in self
             .world
             .query::<(
-                &AdoptedEntity,
+                &MeshRenderer,
                 Option<&Transform>,
                 &ModelProperties,
                 Option<&ScriptComponent>,
@@ -279,8 +284,8 @@ impl Editor {
             };
 
             let scene_entity = SceneEntity {
-                model_path: adopted.model.path.clone(),
-                label: adopted.model.label.clone(),
+                model_path: renderer.handle().path.clone(),
+                label: renderer.handle().label.clone(),
                 transform,
                 properties: properties.clone(),
                 script: script.cloned(),
@@ -289,7 +294,7 @@ impl Editor {
             };
 
             scene.entities.push(scene_entity);
-            log::debug!("Pushed entity: {}", adopted.model.label);
+            log::debug!("Pushed entity: {}", renderer.handle().label);
         }
 
         for (id, (light_component, transform, light)) in self
@@ -314,7 +319,7 @@ impl Editor {
         }
 
         for (id, (camera, component)) in self.world.query::<(&Camera, &CameraComponent)>().iter() {
-            if self.world.get::<&AdoptedEntity>(id).is_err() {
+            if self.world.get::<&MeshRenderer>(id).is_err() {
                 let camera_config = CameraConfig::from_ecs_camera(camera, component);
                 scene.cameras.push(camera_config);
                 log::debug!("Pushed standalone camera into cameras: {}", camera.label);
@@ -753,6 +758,15 @@ impl Editor {
                         }
                         success!("Successfully saved project");
                     }
+                    if ui.button("Reveal project").clicked() {
+                        let project_path = {
+                            PROJECT.read().project_path.clone()
+                        };
+                        match open::that(project_path) {
+                            Ok(()) => info!("Revealed project"),
+                            Err(e) => warn!("Unable to open project: {}", e),
+                        }
+                    }
                     if ui.button("Project Settings").clicked() {};
                     if matches!(self.editor_state, EditorState::Playing) {
                         if ui.button("Stop").clicked() {
@@ -793,12 +807,12 @@ impl Editor {
                 ui.menu_button("Edit", |ui| {
                     if ui.button("Copy").clicked() {
                         if let Some(entity) = &self.selected_entity {
-                            let query = self.world.query_one::<(&AdoptedEntity, &Transform, &ModelProperties)>(*entity);
+                            let query = self.world.query_one::<(&MeshRenderer, &Transform, &ModelProperties)>(*entity);
                             if let Ok(mut q) = query {
                                 if let Some((e, t, props)) = q.get() {
                                     let s_entity = states::SceneEntity {
-                                        model_path: e.model.path.clone(),
-                                        label: e.model.label.clone(),
+                                        model_path: e.handle().path.clone(),
+                                        label: e.handle().label.clone(),
                                         transform: *t,
                                         properties: props.clone(),
                                         script: None,
@@ -852,12 +866,42 @@ impl Editor {
                     if ui_window.button("Open Viewport").clicked() {
                         self.dock_state.push_to_focused_leaf(EditorTab::Viewport);
                     }
+                    if ui_window.button("Open Error Console").clicked() {
+                        self.dock_state.push_to_focused_leaf(EditorTab::ErrorConsole);
+                    }
+                    if self.plugin_registry.plugins.len() == 0 {
+                        ui_window.label(
+                            egui::RichText::new("No plugins ")
+                                .color(ui_window.visuals().weak_text_color())
+                        );
+                    }
                     for (i, (_, plugin)) in self.plugin_registry.plugins.iter().enumerate() {
                         if ui_window.button(format!("Open {}", plugin.display_name())).clicked() {
                             self.dock_state.push_to_focused_leaf(EditorTab::Plugin(i));
                         }
                     }
                 });
+
+                ui.menu_button("Help", |ui| {
+                    if ui.button("Show AppData folder").clicked() {
+                        match app_dirs2::app_root(app_dirs2::AppDataType::UserData, &APP_INFO) {
+                            Ok(val) => {
+                                match open::that(&val) {
+                                    Ok(()) => info!("Opened logs folder"),
+                                    Err(e) => fatal!("Unable to open {}: {}", val.display(), e)
+                                }
+                            },
+                            Err(e) => {
+                                fatal!("Unable to show logs: {}", e);
+                            },
+                        };
+                    }
+
+                    if ui.button("About").clicked() {
+                        self.show_about = true
+                    }
+                });
+
                 {
                     let cfg = PROJECT.read();
                     if cfg.editor_settings.is_debug_menu_shown {
@@ -889,6 +933,7 @@ impl Editor {
                         editor_mode: &mut self.editor_state,
                         plugin_registry: &mut self.plugin_registry,
                         editor: editor_ptr,
+                        build_logs: &mut self.build_logs,
                     },
                 );
         });
@@ -905,6 +950,47 @@ impl Editor {
             },
         );
 
+        egui::Window::new("About")
+            .resizable(false)
+            .collapsible(false)
+            .open(&mut self.show_about)
+            .show(&ctx, |ui| {
+                ui.vertical_centered(|ui| {
+                    ui.add_space(8.0);
+
+                    ui.heading("eucalyptus editor");
+                    ui.label(egui::RichText::new("Built on the dropbear engine").weak());
+
+                    ui.add_space(12.0);
+
+                    ui.label("Made with love by 4tkbytes ♥️");
+
+                    ui.add_space(12.0);
+
+                    ui.horizontal(|ui| {
+                        ui.label("Check out the repository at");
+                        if ui.label("https://github.com/4tkbytes/dropbear").clicked() {
+                            let _ = open::that("https://github.com/4tkbytes/dropbear");
+                        }
+                    });
+
+                    ui.add_space(12.0);
+
+                    ui.label(
+                        egui::RichText::new(format!(
+                            "Built on commit {} with rustc {}",
+                            env!("GIT_HASH"),
+                            rustc_version_runtime::version_meta().short_version_string
+                        ))
+                        .weak()
+                        .italics()
+                        .small(),
+                    );
+
+                    ui.add_space(8.0);
+                });
+            });
+
         if self.pending_scene_switch {
             self.scene_command = SceneCommand::SwitchScene("editor".to_string());
             self.pending_scene_switch = false;
@@ -996,7 +1082,7 @@ impl Editor {
 
         for (entity_id, (_, transform, properties)) in self
             .world
-            .query::<(&AdoptedEntity, &Transform, &ModelProperties)>()
+            .query::<(&MeshRenderer, &Transform, &ModelProperties)>()
             .iter()
         {
             let script = self
@@ -1229,6 +1315,7 @@ impl Editor {
             Ok(())
         } else {
             self.signal = Signal::None;
+            self.editor_state = EditorState::Editing;
             fatal!("Unable to build: No initial camera set");
             Err(anyhow::anyhow!("Unable to build: No initial camera set"))
         }
@@ -1381,9 +1468,9 @@ impl UndoableAction {
             }
             UndoableAction::Label(entity, original_label, entity_type) => match entity_type {
                 EntityType::Entity => {
-                    if let Ok(mut q) = world.query_one::<&mut AdoptedEntity>(*entity) {
-                        if let Some(adopted) = q.get() {
-                            Arc::make_mut(&mut adopted.model).label = original_label.clone();
+                    if let Ok(mut q) = world.query_one::<&mut MeshRenderer>(*entity) {
+                        if let Some(renderer) = q.get() {
+                            renderer.make_model_mut().label = original_label.clone();
                             log::debug!(
                                 "Reverted label for entity {:?} to '{}'",
                                 entity,
@@ -1462,7 +1549,7 @@ impl UndoableAction {
                             world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity)
                             && let Some((cam, comp)) = q.get()
                         {
-                            comp.speed = *speed;
+                            comp.settings.speed = *speed;
                             comp.update(cam);
                         }
                     }
@@ -1471,7 +1558,7 @@ impl UndoableAction {
                             world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity)
                             && let Some((cam, comp)) = q.get()
                         {
-                            comp.sensitivity = *sensitivity;
+                            comp.settings.sensitivity = *sensitivity;
                             comp.update(cam);
                         }
                     }
@@ -1480,7 +1567,7 @@ impl UndoableAction {
                             world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity)
                             && let Some((cam, comp)) = q.get()
                         {
-                            comp.fov_y = *fov;
+                            comp.settings.fov_y = *fov;
                             comp.update(cam);
                         }
                     }
diff --git a/eucalyptus-editor/src/editor/scene.rs b/eucalyptus-editor/src/editor/scene.rs
index a29bbe7..51f3abe 100644
--- a/eucalyptus-editor/src/editor/scene.rs
+++ b/eucalyptus-editor/src/editor/scene.rs
@@ -4,7 +4,7 @@ use crate::spawn::PendingSpawnController;
 use dropbear_engine::graphics::{InstanceRaw, RenderContext};
 use dropbear_engine::model::MODEL_CACHE;
 use dropbear_engine::{
-    entity::{AdoptedEntity, Transform},
+    entity::{MeshRenderer, Transform},
     lighting::{Light, LightComponent},
     model::{DrawLight, DrawModel},
     scene::{Scene, SceneCommand},
@@ -207,14 +207,14 @@ impl Scene for Editor {
         let _ = self.run_signal(graphics.shared.clone());
 
         if let Some(e) = self.previously_selected_entity
-            && let Ok(mut q) = self.world.query_one::<&mut AdoptedEntity>(e)
+            && let Ok(mut q) = self.world.query_one::<&mut MeshRenderer>(e)
             && let Some(entity) = q.get()
         {
             entity.is_selected = false
         }
 
         if let Some(e) = self.selected_entity
-            && let Ok(mut q) = self.world.query_one::<&mut AdoptedEntity>(e)
+            && let Ok(mut q) = self.world.query_one::<&mut MeshRenderer>(e)
             && let Some(entity) = q.get()
         {
             entity.is_selected = true
@@ -248,9 +248,9 @@ impl Scene for Editor {
 
         {
             {
-                let query = self.world.query_mut::<(&mut AdoptedEntity, &Transform)>();
-                for (_, (entity, transform)) in query {
-                    entity.update(graphics.shared.clone(), transform);
+                let query = self.world.query_mut::<(&mut MeshRenderer, &Transform)>();
+                for (_, (renderer, transform)) in query {
+                    renderer.update(transform);
                 }
             }
 
@@ -314,9 +314,9 @@ impl Scene for Editor {
 
                     let entities = {
                         let mut entities = Vec::new();
-                        let mut entity_query = self.world.query::<&AdoptedEntity>();
-                        for (_, entity) in entity_query.iter() {
-                            entities.push(entity.clone());
+                        let mut entity_query = self.world.query::<&MeshRenderer>();
+                        for (_, renderer) in entity_query.iter() {
+                            entities.push(renderer.clone());
                         }
                         entities
                     };
@@ -343,9 +343,9 @@ impl Scene for Editor {
                     }
 
                     let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new();
-                    for entity in &entities {
-                        let model_ptr = entity.model.id;
-                        let instance_raw = entity.instance.to_raw();
+                    for renderer in &entities {
+                        let model_ptr = renderer.model_id();
+                        let instance_raw = renderer.instance.to_raw();
                         model_batches
                             .entry(model_ptr)
                             .or_default()
@@ -384,7 +384,7 @@ impl Scene for Editor {
 
                                 // // outline rendering
                                 // let has_selected = entities.iter()
-                                //     .any(|e| e.model.id == model_ptr && e.is_selected);
+                                //     .any(|e| e.model_id() == model_ptr && e.is_selected);
                                 //
                                 // if has_selected && self.outline_pipeline.is_some() {
                                 //     let outline = self.outline_pipeline.as_ref().unwrap();
diff --git a/eucalyptus-editor/src/signal.rs b/eucalyptus-editor/src/signal.rs
index 61fd3a1..2b93557 100644
--- a/eucalyptus-editor/src/signal.rs
+++ b/eucalyptus-editor/src/signal.rs
@@ -2,7 +2,7 @@ use crate::editor::{
     ComponentType, Editor, EditorState, EntityType, PendingSpawn2, Signal, UndoableAction,
 };
 use dropbear_engine::camera::Camera;
-use dropbear_engine::entity::{AdoptedEntity, Transform};
+use dropbear_engine::entity::{MeshRenderer, Transform};
 use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::lighting::{Light, LightComponent};
 use dropbear_engine::utils::{ResourceReference, ResourceReferenceType};
@@ -10,7 +10,7 @@ use egui::{Align2, Image};
 use eucalyptus_core::camera::{CameraComponent, CameraType};
 use eucalyptus_core::scripting::{BuildStatus, build_jvm};
 use eucalyptus_core::spawn::{PendingSpawn, push_pending_spawn};
-use eucalyptus_core::states::{ModelProperties, PROJECT, ScriptComponent, Value};
+use eucalyptus_core::states::{EditorTab, ModelProperties, PROJECT, ScriptComponent, Value};
 use eucalyptus_core::{fatal, info, success, success_without_console, warn, warn_without_console};
 use std::path::PathBuf;
 use std::sync::Arc;
@@ -259,12 +259,18 @@ impl SignalController for Editor {
                                         self.editor_state = EditorState::Editing;
                                     }
                                 }
-                                BuildStatus::Failed(e) => {
-                                    let error_msg = format!("Build failed: {}", e);
+                                BuildStatus::Failed(_e) => {
+                                    let error_msg = format!("Build failed, check logs");
                                     self.build_logs.push(error_msg.clone());
 
                                     self.build_progress = 0.0;
-                                    fatal!("Failed to build gradle: {}", e);
+                                    fatal!("Failed to build gradle, check logs");
+
+                                    self.signal = Signal::None;
+                                    self.show_build_window = false;
+                                    self.editor_state = EditorState::Editing;
+                                    self.dock_state
+                                        .push_to_focused_leaf(EditorTab::ErrorConsole);
                                 }
                             }
                         }
@@ -435,9 +441,9 @@ impl SignalController for Editor {
             Signal::AddComponent(entity, e_type) => {
                 match e_type {
                     EntityType::Entity => {
-                        if let Ok(mut q) = self.world.query_one::<&AdoptedEntity>(*entity) {
-                            if let Some(e) = q.get() {
-                                let label = e.model.label.clone();
+                        if let Ok(mut q) = self.world.query_one::<&MeshRenderer>(*entity) {
+                            if let Some(renderer) = q.get() {
+                                let label = renderer.handle().label.clone();
                                 egui::Window::new(format!("Add component for {}", label))
                                     .title_bar(true)
                                     .open(&mut show)
@@ -683,13 +689,13 @@ impl SignalController for Editor {
                 log::debug!("====================");
                 let mut counter = 0;
                 for e in self.world.iter() {
-                    if let Some(entity) = e.get::<&AdoptedEntity>() {
+                    if let Some(renderer) = e.get::<&MeshRenderer>() {
                         log::info!(
                             "Model: {:?} with u32 id: {:?}",
-                            entity.model.label,
+                            renderer.handle().label,
                             e.entity().id()
                         );
-                        log::info!("  |-> Using model: {:?}", entity.model.id);
+                        log::info!("  |-> Using model: {:?}", renderer.model_id());
                     }
 
                     if let Some(entity) = e.get::<&Light>() {
diff --git a/eucalyptus-editor/src/spawn.rs b/eucalyptus-editor/src/spawn.rs
index dd9d19d..2f8f469 100644
--- a/eucalyptus-editor/src/spawn.rs
+++ b/eucalyptus-editor/src/spawn.rs
@@ -1,5 +1,5 @@
 use crate::editor::Editor;
-use dropbear_engine::entity::AdoptedEntity;
+use dropbear_engine::entity::MeshRenderer;
 use dropbear_engine::future::FutureQueue;
 use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::model::Model;
@@ -46,7 +46,8 @@ impl PendingSpawnController for Editor {
                                 _guard.project_path.clone()
                             };
                             let resource = path.join("resources").join(file);
-                            AdoptedEntity::new(graphics_clone, resource, Some(&asset_name)).await
+                            MeshRenderer::from_path(graphics_clone, resource, Some(&asset_name))
+                                .await
                         }
                         ResourceReferenceType::Bytes(bytes) => {
                             let model = Model::load_from_memory(
@@ -55,7 +56,7 @@ impl PendingSpawnController for Editor {
                                 Some(&asset_name),
                             )
                             .await?;
-                            Ok(AdoptedEntity::adopt(graphics_clone, model).await)
+                            Ok(MeshRenderer::from_handle(model))
                         }
                         ResourceReferenceType::Plane => {
                             let get_float = |key: &str| -> anyhow::Result<f32> {
@@ -110,7 +111,7 @@ impl PendingSpawnController for Editor {
                 log_once::debug_once!("Handle located");
                 if let Some(result) = queue.exchange_owned(handle) {
                     log_once::debug_once!("Loading done, located result");
-                    if let Ok(r) = result.downcast::<anyhow::Result<AdoptedEntity>>() {
+                    if let Ok(r) = result.downcast::<anyhow::Result<MeshRenderer>>() {
                         log_once::debug_once!("Result has been successfully downcasted");
                         match Arc::try_unwrap(r) {
                             Ok(entity) => match entity {
diff --git a/redback-runtime/build.rs b/redback-runtime/build.rs
index cf74f49..f328e4d 100644
--- a/redback-runtime/build.rs
+++ b/redback-runtime/build.rs
@@ -1,3 +1 @@
-fn main() {
-
-}
\ No newline at end of file
+fn main() {}
diff --git a/redback-runtime/src/main.rs b/redback-runtime/src/main.rs
index 5a2b998..7f755fb 100644
--- a/redback-runtime/src/main.rs
+++ b/redback-runtime/src/main.rs
@@ -1,4 +1,2 @@
 #[tokio::main]
-async fn main() {
-    
-}
\ No newline at end of file
+async fn main() {}