kitgit

tirbofish/dropbear · diff

2f1a9ea · tk

knocked off a lot of issues and features and bugs: worked on issues: - #55: Fix directional lighting - #57: Splitting AdoptedEntity into a Mesh type and a Material type to use as components - #58: Asset Server - #60 - Cannot rebuild project/enter play mode after gradle build error - #63: Error console dock also added an about screen i reckon its ready to merge back to main...

Unverified

diff --git a/Cargo.toml b/Cargo.toml
index 03a7d19..ea72445 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,6 +61,7 @@ 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/src/asset.rs b/dropbear-engine/src/asset.rs
index 58dad98..1c5d958 100644
--- a/dropbear-engine/src/asset.rs
+++ b/dropbear-engine/src/asset.rs
@@ -4,10 +4,10 @@ use dashmap::DashMap;
 
 use crate::model::{Material, MaterialComponent, Mesh, MeshComponent};
 
-/// A typedef for a Asset handle. 
-pub type Handle = u64; 
+/// A typedef for a Asset handle.
+pub type Handle = u64;
 
-/// A cache that holds all the assets loaded at that moment in time. 
+/// 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>>,
@@ -21,10 +21,14 @@ impl AssetCache {
         }
     }
 
-    /// 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>>
+    /// 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>,
     {
@@ -43,8 +47,8 @@ impl AssetCache {
         }
     }
 
-    /// Fetches the model based off the handle. 
-    /// 
+    /// 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
@@ -70,4 +74,4 @@ impl AssetCache {
         self.meshes.clear();
         log::debug!("Cleared everything in the asset cache");
     }
-}
\ No newline at end of file
+}
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 a93d47f..de02ba2 100644
--- a/dropbear-engine/src/entity.rs
+++ b/dropbear-engine/src/entity.rs
@@ -1,9 +1,6 @@
 use glam::{DMat4, DQuat, DVec3, Mat4};
 use serde::{Deserialize, Serialize};
-use std::{
-    path::Path,
-    sync::Arc,
-};
+use std::{path::Path, sync::Arc};
 
 use crate::{
     graphics::{Instance, SharedGraphicsContext},
diff --git a/dropbear-engine/src/lighting.rs b/dropbear-engine/src/lighting.rs
index f431e74..0f33225 100644
--- a/dropbear-engine/src/lighting.rs
+++ b/dropbear-engine/src/lighting.rs
@@ -150,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,
diff --git a/dropbear-engine/src/procedural/plane.rs b/dropbear-engine/src/procedural/plane.rs
index f037f70..d7058f6 100644
--- a/dropbear-engine/src/procedural/plane.rs
+++ b/dropbear-engine/src/procedural/plane.rs
@@ -144,9 +144,7 @@ impl PlaneBuilder {
             id: ModelId(hash),
         });
 
-        MODEL_CACHE
-            .lock()
-            .insert(label, Arc::clone(&model));
+        MODEL_CACHE.lock().insert(label, Arc::clone(&model));
 
         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 1b62d21..7c80252 100644
--- a/eucalyptus-core/src/scripting/jni/exports.rs
+++ b/eucalyptus-core/src/scripting/jni/exports.rs
@@ -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 4730da2..c49771b 100644
--- a/eucalyptus-core/src/scripting/native/exports.rs
+++ b/eucalyptus-core/src/scripting/native/exports.rs
@@ -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/states.rs b/eucalyptus-core/src/states.rs
index 9ae8481..58e8026 100644
--- a/eucalyptus-core/src/states.rs
+++ b/eucalyptus-core/src/states.rs
@@ -1,7 +1,7 @@
 use crate::camera::{CameraComponent, CameraType};
 use crate::utils::PROTO_TEXTURE;
 use chrono::Utc;
-use dropbear_engine::camera::{Camera, CameraBuilder};
+use dropbear_engine::camera::{Camera, CameraBuilder, CameraSettings};
 use dropbear_engine::entity::{MeshRenderer, Transform};
 use dropbear_engine::graphics::SharedGraphicsContext;
 use dropbear_engine::lighting::{Light, LightComponent};
@@ -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::*;
@@ -586,7 +586,7 @@ impl EntityNode {
                 children.push(EntityNode::Camera {
                     id,
                     name: camera.label.clone(),
-                            camera_type: component.camera_type,
+                    camera_type: component.camera_type,
                 });
             }
 
@@ -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,9 +1016,12 @@ impl SceneConfig {
                         reference
                     );
 
-                    let mut renderer =
-                        MeshRenderer::from_path(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);
 
@@ -1030,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,
                         };
@@ -1072,7 +1079,12 @@ impl SceneConfig {
                         let script = ScriptComponent {
                             tags: script_config.tags.clone(),
                         };
-                        world.spawn((renderer, transform, script, entity_config.properties.clone()))
+                        world.spawn((
+                            renderer,
+                            transform,
+                            script,
+                            entity_config.properties.clone(),
+                        ))
                     } else {
                         world.spawn((renderer, transform, entity_config.properties.clone()))
                     };
@@ -1103,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,
                         };
@@ -1203,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,
                         };
@@ -1322,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,
             };
@@ -1362,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 =
@@ -1486,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/dock.rs b/eucalyptus-editor/src/editor/dock.rs
index 6fb0caf..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,
 };
 
@@ -12,8 +13,7 @@ use dropbear_engine::{
     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,
@@ -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");
@@ -1207,8 +1348,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                     && q.get().is_some()
                                 {
                                     log::debug!("Queried selected entity, it is a light");
-                                    *self.signal =
-                                        Signal::AddComponent(*entity, EntityType::Light);
+                                    *self.signal = Signal::AddComponent(*entity, EntityType::Light);
                                 }
                             }
                         } else {
diff --git a/eucalyptus-editor/src/editor/input.rs b/eucalyptus-editor/src/editor/input.rs
index c9f47ad..e2bcbce 100644
--- a/eucalyptus-editor/src/editor/input.rs
+++ b/eucalyptus-editor/src/editor/input.rs
@@ -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 79358be..93c5a98 100644
--- a/eucalyptus-editor/src/editor/mod.rs
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -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,
         })
     }
 
@@ -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() {
@@ -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;
@@ -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"))
         }
@@ -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/signal.rs b/eucalyptus-editor/src/signal.rs
index 78bc80d..2b93557 100644
--- a/eucalyptus-editor/src/signal.rs
+++ b/eucalyptus-editor/src/signal.rs
@@ -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);
                                 }
                             }
                         }
diff --git a/eucalyptus-editor/src/spawn.rs b/eucalyptus-editor/src/spawn.rs
index 361bf3c..2f8f469 100644
--- a/eucalyptus-editor/src/spawn.rs
+++ b/eucalyptus-editor/src/spawn.rs
@@ -46,12 +46,8 @@ impl PendingSpawnController for Editor {
                                 _guard.project_path.clone()
                             };
                             let resource = path.join("resources").join(file);
-                            MeshRenderer::from_path(
-                                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(