tirbofish/dropbear · diff
Merge pull request #62 from 4tkbytes/entity-split
refactor: changing from AdoptedEntity to more component based system
Signature present but could not be verified.
Unverified
@@ -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" @@ -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 @@ -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"); + } +} @@ -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; @@ -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; } } @@ -1,3 +1,4 @@ +pub mod asset; pub mod attenuation; pub mod buffer; pub mod camera; @@ -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(); @@ -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)) } } @@ -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)) } } @@ -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 @@ -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", @@ -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" @@ -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 } @@ -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>, @@ -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), } @@ -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 @@ -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("°"), + ); }); }); }); @@ -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()); } @@ -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"); @@ -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 { @@ -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); } } @@ -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(); @@ -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>() { @@ -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 { @@ -1,3 +1 @@ -fn main() { - -} +fn main() {} @@ -1,4 +1,2 @@ #[tokio::main] -async fn main() { - -} +async fn main() {}