tirbofish/dropbear · diff
compiles, made a lot of efactoring...
Signature present but could not be verified.
Unverified
@@ -6,7 +6,17 @@ package.repository = "https://github.com/4tkbytes/dropbear" package.readme = "README.md" resolver = "3" -members = [ "dropbear-engine", "dropbear-macro", "dropbear-shader", "dropbear-traits", "dropbear_future-queue", "eucalyptus-core", "eucalyptus-editor", "magna-carta", "redback-runtime"] +members = [ + "dropbear-engine", + "dropbear-macro", + "dropbear-shader", + "dropbear-traits", + "dropbear_future-queue", + "eucalyptus-core", + "eucalyptus-editor", + "magna-carta", + "redback-runtime" +] [workspace.dependencies] anyhow = { version = "1.0", features = ["backtrace"] } @@ -32,7 +42,6 @@ gilrs = "0.11" git2 = { version = "0.20", features = ["vendored-openssl"] } glam = { version = "0.30", features = ["serde"] } hecs = { version = "0.10", features = ["serde"] } -lazy_static = "1.5" log = "0.4" log-once = "0.4" model_to_image = "0.1" @@ -67,6 +76,7 @@ memory-stats = "1.2" typetag = "0.2" syn = { version = "2.0", features = ["full"] } quote = "1.0" +egui_ltreeview = { version = "0.6", features = ["doc"] } [workspace.dependencies.image] version = "0.25" @@ -25,10 +25,21 @@ pub struct EntityTransform { } impl EntityTransform { + /// Creates a new [EntityTransform] from a local and world [Transform] pub fn new(local: Transform, world: Transform) -> Self { Self { local, world } } + /// Creates a new [EntityTransform] from a world [Transform] and a default local transform. + /// + /// This is best for situations where a local transform is not required. + pub fn new_from_world(world: Transform) -> Self { + Self { + world, + local: Transform::default() + } + } + /// Gets a reference to the local transform pub fn local(&self) -> &Transform { &self.local @@ -2,7 +2,6 @@ use dropbear_traits::SerializableComponent; use glam::{DMat4, DQuat, DVec3}; use std::fmt::{Display, Formatter}; use std::sync::Arc; -use serde::{Deserialize, Serialize}; use wgpu::{ BindGroup, BindGroupLayout, Buffer, BufferAddress, CompareFunction, DepthBiasState, RenderPipeline, StencilState, VertexBufferLayout, util::DeviceExt, @@ -4,6 +4,14 @@ /// ``` /// use eucalyptus_core::with_component; /// +/// use eucalyptus_core::scene::SceneEntity; +/// +/// struct Transform { +/// position: [f32; 3], +/// } +/// +/// let scene_entity = SceneEntity::default(); +/// /// with_component!(scene_entity, Transform, /*mut*/ |transform| { /// transform.position.x += 1.0; /// }); @@ -11,60 +19,43 @@ macro_rules! with_component { // immutable ($entity:expr, $comp_type:ty, $closure:expr) => { - if let Some(comp) = $entity.get_component::<$comp_type>() { + $crate::traits::SerializableComponent:: + if let Some(comp) = $entity.as_any().downcast_ref::<$comp_type>() { $closure(comp) } }; // mutable ($entity:expr, $comp_type:ty, mut $closure:expr) => { - if let Some(comp) = $entity.get_component_mut::<$comp_type>() { + if let Some(comp) = $entity.as_any_mut().downcast_mut()::<$comp_type>() { $closure(comp) } }; } -/// Get a component or return early from the function +/// Try to get a component, or execute else block /// /// # Usage /// ``` -/// use dropbear_engine::entity::MeshRenderer; -/// use eucalyptus_core::get_component; +/// use eucalyptus_core::if_component; /// -/// fn process_entity(entity: &SceneEntity) { -/// let renderer = get_component!(entity, MeshRenderer); -/// println!("Processing mesh: {:?}", renderer.handle); +/// use eucalyptus_core::scene::SceneEntity; +/// +/// struct Transform { +/// position: [f32; 3], /// } -#[macro_export] -macro_rules! get_component { - ($entity:expr, $comp_type:ty) => { - match $entity.get_component::<$comp_type>() { - Some(comp) => comp, - None => return, - } - }; - - ($entity:expr, $comp_type:ty, mut) => { - match $entity.get_component_mut::<$comp_type>() { - Some(comp) => comp, - None => return, - } - }; -} - -/// Try to get a component, or execute else block /// -/// # Usage -/// ``` -/// if_component!(scene_entity, Transform, |/*mut*/ transform| { -/// transform.position = Vec3::new(1.0, 2.0, 3.0); +/// let scene_entity = SceneEntity::default(); +/// +/// if_component!(scene_entity, Transform, /*mut*/ |transform| { +/// let position = transform.position.get(0); /// } else { /// println!("No transform found"); /// }); #[macro_export] macro_rules! if_component { ($entity:expr, $comp_type:ty, |$comp:ident| $then:block else $else:block) => { - if let Some($comp) = $entity.get_component::<$comp_type>() { + if let Some($comp) = $entity.as_any().downcast_ref::<$comp_type>() { $then } else { $else @@ -72,13 +63,13 @@ macro_rules! if_component { }; ($entity:expr, $comp_type:ty, |$comp:ident| $then:block) => { - if let Some($comp) = $entity.get_component::<$comp_type>() { + if let Some($comp) = $entity.as_any().downcast_ref::<$comp_type>() { $then } }; ($entity:expr, $comp_type:ty, |mut $comp:ident| $then:block else $else:block) => { - if let Some(mut $comp) = $entity.get_component_mut::<$comp_type>() { + if let Some(mut $comp) = $entity.as_any_mut().downcast_mut()::<$comp_type>() { $then } else { $else @@ -86,7 +77,7 @@ macro_rules! if_component { }; ($entity:expr, $comp_type:ty, |mut $comp:ident| $then:block) => { - if let Some(mut $comp) = $entity.get_component_mut::<$comp_type>() { + if let Some(mut $comp) = $entity.as_any_mut().downcast_mut()::<$comp_type>() { $then } }; @@ -0,0 +1,395 @@ +use std::path::{Path, PathBuf}; +use ron::ser::PrettyConfig; +use std::fs; +use serde::{Deserialize, Serialize}; +use egui_dock::DockState; +use chrono::Utc; +use crate::scene::SceneConfig; +use crate::states::{EditorSettings, EditorTab, File, Folder, Node, ResourceType, RESOURCES, SCENES, SOURCE}; + +/// The root config file, responsible for building and other metadata. +/// +/// # Location +/// This file is {project_name}.eucp and is located at {project_dir}/{project_name}.eucp +#[derive(Debug, Deserialize, Serialize, Default, Clone)] +pub struct ProjectConfig { + pub project_name: String, + pub project_path: PathBuf, + pub date_created: String, + pub date_last_accessed: String, + #[serde(default)] + pub dock_layout: Option<DockState<EditorTab>>, + #[serde(default)] + pub editor_settings: EditorSettings, + #[serde(default)] + pub last_opened_scene: Option<String>, +} + +impl ProjectConfig { + /// Creates a new instance of the ProjectConfig. This function is typically used when creating + /// a new project, with it creating new defaults for everything. + pub fn new(project_name: String, project_path: impl AsRef<Path>) -> Self { + let date_created = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S")); + let date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S")); + + let mut result = Self { + project_name, + project_path: project_path.as_ref().to_path_buf(), + date_created, + date_last_accessed, + editor_settings: Default::default(), + dock_layout: None, + last_opened_scene: None, + }; + let _ = result.load_config_to_memory(); + result + } + + /// This function writes the [`ProjectConfig`] struct (and other PathBufs) to a file of the choice + /// under the PathBuf path parameter. + /// + /// # Parameters + /// * path - The root **folder** of the project. + pub fn write_to(&mut self, path: impl AsRef<Path>) -> anyhow::Result<()> { + self.load_config_to_memory()?; + self.date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S")); + // self.assets = Assets::walk(path); + let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default()) + .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?; + let config_path = path + .as_ref() + .join(format!("{}.eucp", self.project_name.clone().to_lowercase())); + self.project_path = path.as_ref().to_path_buf(); + + fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?; + Ok(()) + } + + /// This function reads from the RON and traverses down the different folders to add more information + /// to the ProjectConfig, such as Assets location and other stuff. + /// + /// # Parameters + /// * path - The root config **file** for the project + pub fn read_from(path: impl AsRef<Path>) -> anyhow::Result<Self> { + let ron_str = fs::read_to_string(path.as_ref())?; + let mut config: ProjectConfig = ron::de::from_str(ron_str.as_str())?; + config.project_path = path.as_ref().parent().unwrap().to_path_buf(); + log::info!("Loaded project!"); + log::debug!("Loaded config info"); + log::debug!("Updating with new content"); + config.load_config_to_memory()?; + config.write_to_all()?; + log::debug!("Successfully updated!"); + Ok(config) + } + + /// This function loads a `source.eucc`, `resources.eucc` or a `{scene}.eucs` config file into memory, allowing + /// you to reference and load the nodes located inside them. + pub fn load_config_to_memory(&mut self) -> anyhow::Result<()> { + let project_root = PathBuf::from(&self.project_path); + + // resource config + match ResourceConfig::read_from(&project_root) { + Ok(resources) => { + let mut cfg = RESOURCES.write(); + *cfg = resources; + } + Err(e) => { + if let Some(io_err) = e.downcast_ref::<std::io::Error>() { + if io_err.kind() == std::io::ErrorKind::NotFound { + log::warn!("resources.eucc not found, creating default."); + let default = ResourceConfig { + path: project_root.join("resources"), + nodes: vec![], + }; + default.write_to(&project_root)?; + { + let mut cfg = RESOURCES.write(); + *cfg = default; + } + } else { + log::warn!("Failed to load resources.eucc: {}", e); + } + } else { + log::warn!("Failed to load resources.eucc: {}", e); + } + } + } + + // src config + let mut source_config = SOURCE.write(); + match SourceConfig::read_from(&project_root) { + Ok(source) => *source_config = source, + Err(e) => { + if let Some(io_err) = e.downcast_ref::<std::io::Error>() { + if io_err.kind() == std::io::ErrorKind::NotFound { + log::warn!("source.eucc not found, creating default."); + let default = SourceConfig { + path: project_root.join("src"), + nodes: vec![], + }; + default.write_to(&project_root)?; + *source_config = default; + } else { + log::warn!("Failed to load source.eucc: {}", e); + } + } else { + log::warn!("Failed to load source.eucc: {}", e); + } + } + } + + // scenes + let mut scene_configs = SCENES.write(); + scene_configs.clear(); + + // iterate through each scene file in the folder + let scene_folder = &project_root.join("scenes"); + + if !scene_folder.exists() { + fs::create_dir_all(scene_folder)?; + } + + for scene_entry in fs::read_dir(scene_folder)? { + let scene_entry = scene_entry?; + let path = scene_entry.path(); + + if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("eucs") { + match SceneConfig::read_from(&path) { + Ok(scene) => { + log::debug!("Loaded scene: {}", scene.scene_name); + scene_configs.push(scene); + } + Err(e) => { + if let Some(io_err) = e.downcast_ref::<std::io::Error>() { + if io_err.kind() == std::io::ErrorKind::NotFound { + log::warn!("Scene file {:?} not found", path); + } else { + panic!("Failed to load scene file {:?}: {}", path, e); + } + } else { + panic!("Failed to load scene file {:?}: {}", path, e); + } + } + } + } + } + + if scene_configs.is_empty() { + log::info!("No scenes found, creating default scene"); + let default_scene = + SceneConfig::new("Default".to_string(), scene_folder.join("default.eucs")); + default_scene.write_to(&project_root)?; + self.last_opened_scene = Some(default_scene.scene_name.clone()); + scene_configs.push(default_scene); + } + + if let Some(ref last_scene_name) = self.last_opened_scene { + if let Some(pos) = scene_configs + .iter() + .position(|scene| &scene.scene_name == last_scene_name) + { + if pos != 0 { + let scene = scene_configs.remove(pos); + scene_configs.insert(0, scene); + } + } else if let Some(first) = scene_configs.first() { + self.last_opened_scene = Some(first.scene_name.clone()); + } + } else if let Some(first) = scene_configs.first() { + self.last_opened_scene = Some(first.scene_name.clone()); + } + + Ok(()) + } + + /// # Parameters + /// * path - The root folder of the project + pub fn write_to_all(&mut self) -> anyhow::Result<()> { + let path = self.project_path.clone(); + + { + let resources_config = RESOURCES.read(); + resources_config.write_to(&path)?; + } + + { + let source_config = SOURCE.read(); + source_config.write_to(&path)?; + } + + { + let scene_configs = SCENES.read(); + for scene in scene_configs.iter() { + scene.write_to(&path)?; + } + } + + self.write_to(&path)?; + Ok(()) + } +} + +/// The resource config. +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct ResourceConfig { + /// The path to the resource folder. + pub path: PathBuf, + /// The files and folders of the assets + pub nodes: Vec<Node>, +} + +impl ResourceConfig { + /// # Parameters + /// - path: The root **folder** of the project + pub fn write_to(&self, path: impl AsRef<Path>) -> anyhow::Result<()> { + let resource_dir = path.as_ref().join("resources"); + let updated_config = ResourceConfig { + path: resource_dir.clone(), + nodes: collect_nodes(&resource_dir, path.as_ref(), vec!["thumbnails"].as_slice()), + }; + let ron_str = ron::ser::to_string_pretty(&updated_config, PrettyConfig::default()) + .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?; + let config_path = path.as_ref().join("resources").join("resources.eucc"); + fs::create_dir_all(config_path.parent().unwrap())?; + fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?; + Ok(()) + } + + /// Updates the in-memory ResourceConfig by re-scanning the resource directory. + pub fn update_mem(&mut self) -> anyhow::Result<ResourceConfig> { + let resource_dir = self.path.clone(); + let project_path = resource_dir.parent().unwrap_or(&resource_dir).to_path_buf(); + let updated_config = ResourceConfig { + path: resource_dir.clone(), + nodes: collect_nodes(&resource_dir, &project_path, vec!["thumbnails"].as_slice()), + }; + Ok(updated_config) + } + + /// # Parameters + /// - path: The location to the **resources.eucc** file + pub fn read_from(path: impl AsRef<Path>) -> anyhow::Result<Self> { + let config_path = path.as_ref().join("resources").join("resources.eucc"); + let ron_str = fs::read_to_string(&config_path)?; + let config: ResourceConfig = ron::de::from_str(&ron_str) + .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?; + Ok(config) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub struct SourceConfig { + /// The path to the resource folder. + pub path: PathBuf, + /// The files and folders of the assets + pub nodes: Vec<Node>, +} + +impl SourceConfig { + /// Builds a source path from the ProjectConfiguration's project_path (or a string) + #[allow(dead_code)] + pub fn build_path(project_path: String) -> PathBuf { + PathBuf::from(project_path).join("src/source.eucc") + } + + /// # Parameters + /// - path: The root **folder** of the project + pub fn write_to(&self, path: impl AsRef<Path>) -> anyhow::Result<()> { + let resource_dir = path.as_ref().join("src"); + let updated_config = SourceConfig { + path: resource_dir.clone(), + nodes: collect_nodes(&resource_dir, path.as_ref(), vec!["scripts"].as_slice()), + }; + + let ron_str = ron::ser::to_string_pretty(&updated_config, PrettyConfig::default()) + .map_err(|e| anyhow::anyhow!("RON serialisation error: {}", e))?; + let config_path = path.as_ref().join("src").join("source.eucc"); + fs::create_dir_all(config_path.parent().unwrap())?; + fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?; + Ok(()) + } + + /// # Parameters + /// - path: The location to the **source.eucc** file + pub fn read_from(path: impl AsRef<Path>) -> anyhow::Result<Self> { + let config_path = path.as_ref().join("src").join("source.eucc"); + let ron_str = fs::read_to_string(&config_path)?; + let config: SourceConfig = ron::de::from_str(&ron_str) + .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?; + Ok(config) + } +} + +fn collect_nodes( + dir: impl AsRef<Path>, + project_path: impl AsRef<Path>, + exclude_list: &[&str], +) -> Vec<Node> { + let mut nodes = Vec::new(); + if let Ok(entries) = fs::read_dir(dir) { + for entry in entries.flatten() { + let entry_path = entry.path(); + let name = entry_path + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + + if entry_path.is_dir() && exclude_list.iter().any(|ex| ex.to_string() == *name) { + log::debug!("Skipped past folder {:?}", name); + continue; + } + + if entry_path.is_dir() { + let folder_nodes = collect_nodes(&entry_path, project_path.as_ref(), exclude_list); + nodes.push(Node::Folder(Folder { + name, + path: entry_path.clone(), + nodes: folder_nodes, + })); + } else { + let parent_folder = entry_path + .parent() + .and_then(|p| p.file_name()) + .map(|n| n.to_string_lossy().to_lowercase()) + .unwrap_or_default(); + + let resource_type = if parent_folder.contains("model") { + ResourceType::Model + } else if parent_folder.contains("texture") { + ResourceType::Texture + } else if parent_folder.contains("shader") { + ResourceType::Shader + } else if entry_path + .extension() + .map(|e| e.to_string_lossy().to_lowercase()) + == Some("kt".to_string()) + { + ResourceType::Script + } else if entry_path + .extension() + .map(|e| e.to_string_lossy().to_lowercase().contains("eu")) + .unwrap_or_default() + { + ResourceType::Config + } else { + ResourceType::Unknown + }; + + // Store relative path from the project root instead of absolute path + let relative_path = entry_path + .strip_prefix(project_path.as_ref()) + .unwrap_or(&entry_path) + .to_path_buf(); + + nodes.push(Node::File(File::ResourceFile { + name, + path: relative_path, + resource_type, + })); + } + } + } + nodes +} @@ -11,7 +11,8 @@ pub mod states; pub mod utils; pub mod window; pub mod scene; -mod component; +pub mod component; +pub mod config; pub use dropbear_traits as traits; pub use dropbear_macro as macros; @@ -27,7 +27,6 @@ use jni::sys::{ use parking_lot::Mutex; use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use dropbear_engine::lighting::Light; use crate::hierarchy::EntityTransformExt; /// `JNIEXPORT jlong JNICALL Java_com_dropbear_ffi_JNINative_getEntity @@ -6,8 +6,8 @@ use crate::states::{Label, ModelProperties, Value}; use crate::utils::keycode_from_ordinal; use crate::window::{GraphicsCommand, WindowCommand}; use dropbear_engine::camera::Camera; -use dropbear_engine::entity::{MeshRenderer, Transform}; -use glam::{DQuat, DVec3}; +use dropbear_engine::entity::{EntityTransform, MeshRenderer}; +use glam::{DVec3}; use hecs::World; use std::ffi::{CStr, c_char}; @@ -60,9 +60,10 @@ pub unsafe extern "C" fn dropbear_get_world_transform( let world = unsafe { &*world_ptr }; let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; - match world.query_one::<&WorldTransform>(entity) { + match world.query_one::<&EntityTransform>(entity) { Ok(mut q) => { if let Some(transform) = q.get() { + let transform = transform.world(); unsafe { (*out_transform).position_x = transform.position.x; (*out_transform).position_y = transform.position.y; @@ -80,12 +81,12 @@ pub unsafe extern "C" fn dropbear_get_world_transform( eprintln!( "[dropbear_get_transform] [ERROR] Entity has no WorldTransform component" ); - -4 + DropbearNativeError::NoSuchComponent as i32 } } Err(_) => { eprintln!("[dropbear_get_transform] [ERROR] Failed to query entity"); - -2 + DropbearNativeError::QueryFailed as i32 } } } @@ -104,9 +105,10 @@ pub unsafe extern "C" fn dropbear_get_local_transform( let world = unsafe { &*world_ptr }; let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; - match world.query_one::<&LocalTransform>(entity) { + match world.query_one::<&EntityTransform>(entity) { Ok(mut q) => { if let Some(transform) = q.get() { + let transform = transform.local(); unsafe { (*out_transform).position_x = transform.position.x; (*out_transform).position_y = transform.position.y; @@ -124,88 +126,12 @@ pub unsafe extern "C" fn dropbear_get_local_transform( eprintln!( "[dropbear_get_local_transform] [ERROR] Entity has no LocalTransform component" ); - return DropbearNativeError::NoSuchComponent as i32; + DropbearNativeError::NoSuchComponent as i32 } } Err(_) => { eprintln!("[dropbear_get_local_transform] [ERROR] Failed to query entity"); - return DropbearNativeError::QueryFailed as i32; - } - } -} - -#[unsafe(no_mangle)] -pub unsafe extern "C" fn dropbear_commit_world_transform( - world_ptr: *mut World, - entity_id: i64, - transform: NativeTransform, -) -> i32 { - if world_ptr.is_null() { - eprintln!("[dropbear_commit_world_transform] [ERROR] World pointer is null"); - return -1; - } - - let world = unsafe { &mut *world_ptr }; - let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; - - let rust_transform = WorldTransform::new(Transform { - position: DVec3::new( - transform.position_x, - transform.position_y, - transform.position_z, - ), - rotation: DQuat::from_xyzw( - transform.rotation_x, - transform.rotation_y, - transform.rotation_z, - transform.rotation_w, - ), - scale: DVec3::new(transform.scale_x, transform.scale_y, transform.scale_z), - }); - - match world.insert_one(entity, rust_transform) { - Ok(_) => 0, - Err(_) => { - eprintln!("[dropbear_commit_world_transform] [ERROR] Failed to insert transform"); - DropbearNativeError::WorldInsertError as i32; - } - } -} - -#[unsafe(no_mangle)] -pub unsafe extern "C" fn dropbear_commit_local_transform( - world_ptr: *mut World, - entity_id: i64, - transform: NativeTransform, -) -> i32 { - if world_ptr.is_null() { - eprintln!("[dropbear_commit_local_transform] [ERROR] World pointer is null"); - return -1; - } - - let world = unsafe { &mut *world_ptr }; - let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; - - let rust_transform = LocalTransform::new(Transform { - position: DVec3::new( - transform.position_x, - transform.position_y, - transform.position_z, - ), - rotation: DQuat::from_xyzw( - transform.rotation_x, - transform.rotation_y, - transform.rotation_z, - transform.rotation_w, - ), - scale: DVec3::new(transform.scale_x, transform.scale_y, transform.scale_z), - }); - - match world.insert_one(entity, rust_transform) { - Ok(_) => 0, - Err(_) => { - eprintln!("[dropbear_commit_local_transform] [ERROR] Failed to insert transform"); - -6 + DropbearNativeError::QueryFailed as i32 } } } @@ -1,10 +1,8 @@ -use crate::states::ModelProperties; -use dropbear_engine::entity::{EntityTransform}; use dropbear_engine::future::{FutureHandle, FutureQueue}; use dropbear_engine::graphics::SharedGraphicsContext; -use dropbear_engine::utils::ResourceReference; use parking_lot::Mutex; use std::sync::{Arc, LazyLock}; +use crate::scene::SceneEntity; /// All spawns that are waiting to be spawned in. pub static PENDING_SPAWNS: LazyLock<Mutex<Vec<PendingSpawn>>> = @@ -13,14 +11,7 @@ pub static PENDING_SPAWNS: LazyLock<Mutex<Vec<PendingSpawn>>> = /// A spawn that's waiting to be added into the world. #[derive(Clone, Debug)] pub struct PendingSpawn { - /// A [`ResourceReference`] to the asset - pub asset_path: ResourceReference, - /// The name/label of the asset - pub asset_name: String, - /// The [`EntityTransform`] properties (position) - pub transform: EntityTransform, - /// The properties of a model, as specified in [`ModelProperties`] - pub properties: ModelProperties, + pub scene_entity: SceneEntity, /// An optional future handle to an object. /// /// If one is specified, it is assumed that the returned object is a [`MeshRenderer`](dropbear_engine::entity::MeshRenderer). @@ -1,33 +1,21 @@ use crate::traits::SerializableComponent; use crate::camera::{CameraComponent, CameraType}; -use crate::hierarchy::{Parent, SceneHierarchy}; -use crate::utils::{ResolveReference, PROTO_TEXTURE}; -use chrono::Utc; -use dropbear_engine::asset::ASSET_REGISTRY; -use dropbear_engine::camera::{Camera, CameraBuilder, CameraSettings}; +use dropbear_engine::camera::Camera; use dropbear_engine::entity::{MaterialOverride, MeshRenderer, Transform}; -use dropbear_engine::graphics::SharedGraphicsContext; -use dropbear_engine::lighting::{Light, LightComponent}; -use dropbear_engine::model::Model; -use dropbear_engine::procedural::plane::PlaneBuilder; -use dropbear_engine::utils::{ResourceReference, ResourceReferenceType}; +use dropbear_engine::lighting::{LightComponent}; +use dropbear_engine::utils::{ResourceReference}; use egui::Ui; -use egui_dock::DockState; -use glam::{DQuat, DVec3}; use once_cell::sync::Lazy; use parking_lot::RwLock; -use rayon::prelude::*; -use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; use std::collections::HashMap; use std::fmt::{Display, Formatter}; use std::ops::{Deref, DerefMut}; -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use std::{fmt, fs}; -use tokio::sync::mpsc::UnboundedSender; +use std::path::{PathBuf}; +use std::{fmt}; use dropbear_macro::SerializableComponent; +use crate::config::{ProjectConfig, ResourceConfig, SourceConfig}; use crate::scene::SceneConfig; pub static PROJECT: Lazy<RwLock<ProjectConfig>> = @@ -97,229 +85,6 @@ pub fn load_scene_into_memory(scene_name: &str) -> anyhow::Result<()> { Ok(()) } -/// The root config file, responsible for building and other metadata. -/// -/// # Location -/// This file is {project_name}.eucp and is located at {project_dir}/{project_name}.eucp -#[derive(Debug, Deserialize, Serialize, Default, Clone)] -pub struct ProjectConfig { - pub project_name: String, - pub project_path: PathBuf, - pub date_created: String, - pub date_last_accessed: String, - #[serde(default)] - pub dock_layout: Option<DockState<EditorTab>>, - #[serde(default)] - pub editor_settings: EditorSettings, - #[serde(default)] - pub last_opened_scene: Option<String>, -} - -impl ProjectConfig { - /// Creates a new instance of the ProjectConfig. This function is typically used when creating - /// a new project, with it creating new defaults for everything. - pub fn new(project_name: String, project_path: impl AsRef<Path>) -> Self { - let date_created = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S")); - let date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S")); - - let mut result = Self { - project_name, - project_path: project_path.as_ref().to_path_buf(), - date_created, - date_last_accessed, - editor_settings: Default::default(), - dock_layout: None, - last_opened_scene: None, - }; - let _ = result.load_config_to_memory(); - result - } - - /// This function writes the [`ProjectConfig`] struct (and other PathBufs) to a file of the choice - /// under the PathBuf path parameter. - /// - /// # Parameters - /// * path - The root **folder** of the project. - pub fn write_to(&mut self, path: impl AsRef<Path>) -> anyhow::Result<()> { - self.load_config_to_memory()?; - self.date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S")); - // self.assets = Assets::walk(path); - let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default()) - .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?; - let config_path = path - .as_ref() - .join(format!("{}.eucp", self.project_name.clone().to_lowercase())); - self.project_path = path.as_ref().to_path_buf(); - - fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?; - Ok(()) - } - - /// This function reads from the RON and traverses down the different folders to add more information - /// to the ProjectConfig, such as Assets location and other stuff. - /// - /// # Parameters - /// * path - The root config **file** for the project - pub fn read_from(path: impl AsRef<Path>) -> anyhow::Result<Self> { - let ron_str = fs::read_to_string(path.as_ref())?; - let mut config: ProjectConfig = ron::de::from_str(ron_str.as_str())?; - config.project_path = path.as_ref().parent().unwrap().to_path_buf(); - log::info!("Loaded project!"); - log::debug!("Loaded config info"); - log::debug!("Updating with new content"); - config.load_config_to_memory()?; - config.write_to_all()?; - log::debug!("Successfully updated!"); - Ok(config) - } - - /// This function loads a `source.eucc`, `resources.eucc` or a `{scene}.eucs` config file into memory, allowing - /// you to reference and load the nodes located inside them. - pub fn load_config_to_memory(&mut self) -> anyhow::Result<()> { - let project_root = PathBuf::from(&self.project_path); - - // resource config - match ResourceConfig::read_from(&project_root) { - Ok(resources) => { - let mut cfg = RESOURCES.write(); - *cfg = resources; - } - Err(e) => { - if let Some(io_err) = e.downcast_ref::<std::io::Error>() { - if io_err.kind() == std::io::ErrorKind::NotFound { - log::warn!("resources.eucc not found, creating default."); - let default = ResourceConfig { - path: project_root.join("resources"), - nodes: vec![], - }; - default.write_to(&project_root)?; - { - let mut cfg = RESOURCES.write(); - *cfg = default; - } - } else { - log::warn!("Failed to load resources.eucc: {}", e); - } - } else { - log::warn!("Failed to load resources.eucc: {}", e); - } - } - } - - // src config - let mut source_config = SOURCE.write(); - match SourceConfig::read_from(&project_root) { - Ok(source) => *source_config = source, - Err(e) => { - if let Some(io_err) = e.downcast_ref::<std::io::Error>() { - if io_err.kind() == std::io::ErrorKind::NotFound { - log::warn!("source.eucc not found, creating default."); - let default = SourceConfig { - path: project_root.join("src"), - nodes: vec![], - }; - default.write_to(&project_root)?; - *source_config = default; - } else { - log::warn!("Failed to load source.eucc: {}", e); - } - } else { - log::warn!("Failed to load source.eucc: {}", e); - } - } - } - - // scenes - let mut scene_configs = SCENES.write(); - scene_configs.clear(); - - // iterate through each scene file in the folder - let scene_folder = &project_root.join("scenes"); - - if !scene_folder.exists() { - fs::create_dir_all(scene_folder)?; - } - - for scene_entry in fs::read_dir(scene_folder)? { - let scene_entry = scene_entry?; - let path = scene_entry.path(); - - if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("eucs") { - match SceneConfig::read_from(&path) { - Ok(scene) => { - log::debug!("Loaded scene: {}", scene.scene_name); - scene_configs.push(scene); - } - Err(e) => { - if let Some(io_err) = e.downcast_ref::<std::io::Error>() { - if io_err.kind() == std::io::ErrorKind::NotFound { - log::warn!("Scene file {:?} not found", path); - } else { - panic!("Failed to load scene file {:?}: {}", path, e); - } - } else { - panic!("Failed to load scene file {:?}: {}", path, e); - } - } - } - } - } - - if scene_configs.is_empty() { - log::info!("No scenes found, creating default scene"); - let default_scene = - SceneConfig::new("Default".to_string(), scene_folder.join("default.eucs")); - default_scene.write_to(&project_root)?; - self.last_opened_scene = Some(default_scene.scene_name.clone()); - scene_configs.push(default_scene); - } - - if let Some(ref last_scene_name) = self.last_opened_scene { - if let Some(pos) = scene_configs - .iter() - .position(|scene| &scene.scene_name == last_scene_name) - { - if pos != 0 { - let scene = scene_configs.remove(pos); - scene_configs.insert(0, scene); - } - } else if let Some(first) = scene_configs.first() { - self.last_opened_scene = Some(first.scene_name.clone()); - } - } else if let Some(first) = scene_configs.first() { - self.last_opened_scene = Some(first.scene_name.clone()); - } - - Ok(()) - } - - /// # Parameters - /// * path - The root folder of the project - pub fn write_to_all(&mut self) -> anyhow::Result<()> { - let path = self.project_path.clone(); - - { - let resources_config = RESOURCES.read(); - resources_config.write_to(&path)?; - } - - { - let source_config = SOURCE.read(); - source_config.write_to(&path)?; - } - - { - let scene_configs = SCENES.read(); - for scene in scene_configs.iter() { - scene.write_to(&path)?; - } - } - - self.write_to(&path)?; - Ok(()) - } -} - #[derive(Debug, Serialize, Deserialize, Clone)] pub enum Node { File(File), @@ -341,13 +106,6 @@ pub enum File { }, } -// #[derive(Default, Debug, Serialize, Deserialize, Clone)] -// pub struct File { -// pub name: String, -// pub path: PathBuf, -// pub resource_type: Option<ResourceType>, -// } - #[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct Folder { pub name: String, @@ -382,319 +140,11 @@ impl Display for ResourceType { } } -/// The resource config. -#[derive(Default, Debug, Serialize, Deserialize)] -pub struct ResourceConfig { - /// The path to the resource folder. - pub path: PathBuf, - /// The files and folders of the assets - pub nodes: Vec<Node>, -} - -impl ResourceConfig { - /// Builds a resource path from the ProjectConfiguration's project_path (or a string) - #[allow(dead_code)] - pub fn build_path(project_path: String) -> PathBuf { - PathBuf::from(project_path).join("resources/resources.eucc") - } - - /// # Parameters - /// - path: The root **folder** of the project - pub fn write_to(&self, path: impl AsRef<Path>) -> anyhow::Result<()> { - let resource_dir = path.as_ref().join("resources"); - let updated_config = ResourceConfig { - path: resource_dir.clone(), - nodes: collect_nodes(&resource_dir, path.as_ref(), vec!["thumbnails"].as_slice()), - }; - let ron_str = ron::ser::to_string_pretty(&updated_config, PrettyConfig::default()) - .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?; - let config_path = path.as_ref().join("resources").join("resources.eucc"); - fs::create_dir_all(config_path.parent().unwrap())?; - fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?; - Ok(()) - } - - /// Updates the in-memory ResourceConfig by re-scanning the resource directory. - pub fn update_mem(&mut self) -> anyhow::Result<ResourceConfig> { - let resource_dir = self.path.clone(); - let project_path = resource_dir.parent().unwrap_or(&resource_dir).to_path_buf(); - let updated_config = ResourceConfig { - path: resource_dir.clone(), - nodes: collect_nodes(&resource_dir, &project_path, vec!["thumbnails"].as_slice()), - }; - Ok(updated_config) - } - - /// # Parameters - /// - path: The location to the **resources.eucc** file - pub fn read_from(path: impl AsRef<Path>) -> anyhow::Result<Self> { - let config_path = path.as_ref().join("resources").join("resources.eucc"); - let ron_str = fs::read_to_string(&config_path)?; - let config: ResourceConfig = ron::de::from_str(&ron_str) - .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?; - Ok(config) - } -} - -#[derive(Default, Debug, Serialize, Deserialize, Clone)] -pub struct SourceConfig { - /// The path to the resource folder. - pub path: PathBuf, - /// The files and folders of the assets - pub nodes: Vec<Node>, -} - -impl SourceConfig { - /// Builds a source path from the ProjectConfiguration's project_path (or a string) - #[allow(dead_code)] - pub fn build_path(project_path: String) -> PathBuf { - PathBuf::from(project_path).join("src/source.eucc") - } - - /// # Parameters - /// - path: The root **folder** of the project - pub fn write_to(&self, path: impl AsRef<Path>) -> anyhow::Result<()> { - let resource_dir = path.as_ref().join("src"); - let updated_config = SourceConfig { - path: resource_dir.clone(), - nodes: collect_nodes(&resource_dir, path.as_ref(), vec!["scripts"].as_slice()), - }; - - let ron_str = ron::ser::to_string_pretty(&updated_config, PrettyConfig::default()) - .map_err(|e| anyhow::anyhow!("RON serialisation error: {}", e))?; - let config_path = path.as_ref().join("src").join("source.eucc"); - fs::create_dir_all(config_path.parent().unwrap())?; - fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?; - Ok(()) - } - - /// # Parameters - /// - path: The location to the **source.eucc** file - pub fn read_from(path: impl AsRef<Path>) -> anyhow::Result<Self> { - let config_path = path.as_ref().join("src").join("source.eucc"); - let ron_str = fs::read_to_string(&config_path)?; - let config: SourceConfig = ron::de::from_str(&ron_str) - .map_err(|e| anyhow::anyhow!("RON deserialization error: {}", e))?; - Ok(config) - } -} - -fn collect_nodes( - dir: impl AsRef<Path>, - project_path: impl AsRef<Path>, - exclude_list: &[&str], -) -> Vec<Node> { - let mut nodes = Vec::new(); - if let Ok(entries) = fs::read_dir(dir) { - for entry in entries.flatten() { - let entry_path = entry.path(); - let name = entry_path - .file_name() - .unwrap_or_default() - .to_string_lossy() - .to_string(); - - if entry_path.is_dir() && exclude_list.iter().any(|ex| ex.to_string() == *name) { - log::debug!("Skipped past folder {:?}", name); - continue; - } - - if entry_path.is_dir() { - let folder_nodes = collect_nodes(&entry_path, project_path.as_ref(), exclude_list); - nodes.push(Node::Folder(Folder { - name, - path: entry_path.clone(), - nodes: folder_nodes, - })); - } else { - let parent_folder = entry_path - .parent() - .and_then(|p| p.file_name()) - .map(|n| n.to_string_lossy().to_lowercase()) - .unwrap_or_default(); - - let resource_type = if parent_folder.contains("model") { - ResourceType::Model - } else if parent_folder.contains("texture") { - ResourceType::Texture - } else if parent_folder.contains("shader") { - ResourceType::Shader - } else if entry_path - .extension() - .map(|e| e.to_string_lossy().to_lowercase()) - == Some("kt".to_string()) - { - ResourceType::Script - } else if entry_path - .extension() - .map(|e| e.to_string_lossy().to_lowercase().contains("eu")) - .unwrap_or_default() - { - ResourceType::Config - } else { - ResourceType::Unknown - }; - - // Store relative path from the project root instead of absolute path - let relative_path = entry_path - .strip_prefix(project_path.as_ref()) - .unwrap_or(&entry_path) - .to_path_buf(); - - nodes.push(Node::File(File::ResourceFile { - name, - path: relative_path, - resource_type, - })); - } - } - } - nodes -} - -#[derive(Clone, Debug, Serialize, Deserialize, Hash)] -pub enum EntityNode { - Entity { - id: hecs::Entity, - name: String, - }, - Script { - tags: Vec<String>, - }, - Light { - id: hecs::Entity, - name: String, - }, - Camera { - id: hecs::Entity, - name: String, - camera_type: CameraType, - }, - Group { - name: String, - children: Vec<EntityNode>, - collapsed: bool, - }, -} - #[derive(Default, Debug, Serialize, Deserialize, Clone, SerializableComponent)] pub struct ScriptComponent { pub tags: Vec<String>, } -impl EntityNode { - pub fn from_world(world: &hecs::World) -> Vec<Self> { - let mut nodes = Vec::new(); - let mut handled = std::collections::HashSet::new(); - - for (id, (label, script, _transform, _renderer)) in world - .query::<( - &Label, - &ScriptComponent, - &dropbear_engine::entity::Transform, - &dropbear_engine::entity::MeshRenderer, - )>() - .iter() - { - let name = label.to_string(); - let mut children = vec![ - EntityNode::Entity { - id, - name: name.clone(), - }, - EntityNode::Script { - tags: script.tags.clone(), - }, - ]; - - // Check if this entity also has camera components - if let Ok(mut camera_query) = world.query_one::<(&Camera, &CameraComponent)>(id) - && let Some((camera, component)) = camera_query.get() - { - children.push(EntityNode::Camera { - id, - name: camera.label.clone(), - camera_type: component.camera_type, - }); - } - - nodes.push(EntityNode::Group { - name: name.clone(), - children, - collapsed: false, - }); - handled.insert(id); - } - - // Handle single entities (and potentially cameras) - for (id, (label, _renderer)) in world - .query::<(&Label, &dropbear_engine::entity::MeshRenderer)>() - .iter() - { - if handled.contains(&id) { - continue; - } - let name = label.to_string(); - - // Check if this entity has camera components - if let Ok(mut camera_query) = world.query_one::<(&Camera, &CameraComponent)>(id) { - if let Some((camera, component)) = camera_query.get() { - // Create a group with the entity and its camera component - nodes.push(EntityNode::Group { - name: name.clone(), - children: vec![ - EntityNode::Entity { - id, - name: name.clone(), - }, - EntityNode::Camera { - id, - name: camera.label.clone(), - camera_type: component.camera_type, - }, - ], - collapsed: false, - }); - } else { - // Regular entity without camera components - nodes.push(EntityNode::Entity { id, name }); - } - } else { - // Regular entity without camera components - nodes.push(EntityNode::Entity { id, name }); - } - } - - // lights - for (id, (_transform, _light_comp, light)) in world - .query::<(&dropbear_engine::entity::Transform, &LightComponent, &Light)>() - .iter() - { - if handled.contains(&id) { - continue; - } - nodes.push(EntityNode::Light { - id, - name: light.label().to_string(), - }); - handled.insert(id); - } - - // Handle standalone cameras (cameras without MeshRenderer - like viewport cameras) - for (entity, (camera, component)) in world.query::<(&Camera, &CameraComponent)>().iter() { - if world.get::<&MeshRenderer>(entity).is_err() { - nodes.push(EntityNode::Camera { - id: entity, - name: camera.label.clone(), - camera_type: component.camera_type, - }); - } - } - - nodes - } -} - #[derive(Debug, Serialize, Deserialize, Clone, SerializableComponent)] pub struct CameraConfig { pub label: String, @@ -757,12 +207,6 @@ impl CameraConfig { } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, SerializableComponent)] -pub struct ModelProperties { - pub custom_properties: Vec<Property>, - pub next_id: u64, -} - #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct Property { pub id: u64, @@ -798,7 +242,16 @@ impl Display for Value { } } +/// Properties for an entity, typically queries with `entity.getProperty<Float>` and `entity.setProperty(67)` +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, SerializableComponent)] +pub struct ModelProperties { + pub custom_properties: Vec<Property>, + pub next_id: u64, +} + + impl ModelProperties { + /// Creates a new [ModelProperties] pub fn new() -> Self { Self { custom_properties: Vec::new(), @@ -806,6 +259,10 @@ impl ModelProperties { } } + /// Sets the property based on the [Value] (type) and its key. + /// + /// If the value does NOT exist, it will be created. + /// If the value does exist, it will replace the contents of that item. pub fn set_property(&mut self, key: String, value: Value) { if let Some(prop) = self.custom_properties.iter_mut().find(|p| p.key == key) { prop.value = value; @@ -819,6 +276,7 @@ impl ModelProperties { } } + /// Fetches the property by its key. pub fn get_property(&self, key: &str) -> Option<&Value> { self.custom_properties .iter() @@ -826,6 +284,7 @@ impl ModelProperties { .map(|p| &p.value) } + /// Fetches the float property pub fn get_float(&self, key: &str) -> Option<f64> { match self.get_property(key)? { Value::Float(f) => Some(*f), @@ -833,6 +292,7 @@ impl ModelProperties { } } + /// Fetches the integer property pub fn get_int(&self, key: &str) -> Option<i64> { match self.get_property(key)? { Value::Int(i) => Some(*i), @@ -840,6 +300,9 @@ impl ModelProperties { } } + /// Creates a new property based on a key and a value. + /// + /// It will push that value again to the property vector. pub fn add_property(&mut self, key: String, value: Value) { self.custom_properties.push(Property { id: self.next_id, @@ -849,6 +312,7 @@ impl ModelProperties { self.next_id += 1; } + /// Shows a template of the different values when inspected as a component in the editor. pub fn show_value_editor(ui: &mut Ui, value: &mut Value) -> bool { match value { Value::String(s) => ui.text_edit_singleline(s).changed(), @@ -896,6 +360,7 @@ impl Default for ModelProperties { } } +// A serializable configuration struct for the [Light] type #[derive(Debug, Serialize, Deserialize, Clone, SerializableComponent)] pub struct LightConfig { pub label: String, @@ -919,6 +384,7 @@ impl Default for LightConfig { } } +/// Describes the settings of the editor, not the project or the scene. #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct EditorSettings { pub is_debug_menu_shown: bool, @@ -1050,8 +516,20 @@ impl DerefMut for Label { } } +/// A [MeshRenderer] that is serialized into a file to be stored as a value for config. #[derive(Default, Debug, Clone, Serialize, Deserialize, SerializableComponent)] pub struct SerializedMeshRenderer { pub handle: ResourceReference, pub material_override: Vec<MaterialOverride>, +} + +impl SerializedMeshRenderer { + /// Creates a new [SerializedMeshRenderer] from an existing [MeshRenderer] by cloning data. + pub fn from_renderer(renderer: &MeshRenderer) -> Self { + let handle = renderer.handle(); + Self { + handle: handle.path.clone(), + material_override: renderer.material_overrides.clone(), + } + } } @@ -9,6 +9,7 @@ readme = "README.md" [dependencies] eucalyptus-core = { path = "../eucalyptus-core", features = ["editor"] } + anyhow.workspace = true app_dirs2.workspace = true bincode.workspace = true @@ -44,6 +45,7 @@ memory-stats.workspace = true env_logger.workspace = true colored.workspace = true chrono.workspace = true +egui_ltreeview.workspace = true [target.'cfg(not(target_os = "android"))'.dependencies] rfd.workspace = true @@ -53,6 +55,6 @@ default = ["editor"] editor = ["eucalyptus-core/editor"] [build-dependencies] -anyhow = "1.0" -app_dirs2 = "2.5" -zip = "4.3" +anyhow.workspace = true +app_dirs2.workspace = true +zip.workspace = true @@ -2,8 +2,8 @@ use clap::ArgMatches; use std::path::Path; use std::{collections::HashMap, fs, path::PathBuf, process::Command}; use zip::write::SimpleFileOptions; - -use eucalyptus_core::states::{ProjectConfig, RuntimeData, SCENES, SOURCE}; +use eucalyptus_core::config::ProjectConfig; +use eucalyptus_core::states::{RuntimeData, SCENES, SOURCE}; pub fn package(project_path: PathBuf, _sub_matches: &ArgMatches) -> anyhow::Result<()> { if !project_path.exists() { @@ -1,5 +1,5 @@ use crate::editor::component::InspectableComponent; -use crate::editor::{EntityType, Signal, StaticallyKept, UndoableAction}; +use crate::editor::{Signal, StaticallyKept, UndoableAction}; use dropbear_engine::camera::Camera; use egui::{CollapsingHeader, Ui}; use eucalyptus_core::camera::{CameraComponent, CameraType}; @@ -38,7 +38,7 @@ impl InspectableComponent for Camera { if let Some(orig) = cfg.label_original.take() { UndoableAction::push_to_undo( undo_stack, - UndoableAction::Label(ent, orig, EntityType::Entity), + UndoableAction::Label(ent, orig), ); log::debug!("Pushed camera label change to undo stack"); } @@ -1,9 +1,9 @@ //! This module should describe the different components that are editable in the resource inspector. -use crate::editor::{EntityType, Signal, StaticallyKept, UndoableAction}; +use crate::editor::{Signal, StaticallyKept, UndoableAction}; use dropbear_engine::asset::{ASSET_REGISTRY, AssetHandle}; use dropbear_engine::attenuation::ATTENUATION_PRESETS; -use dropbear_engine::entity::{MeshRenderer, Transform}; +use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform}; use dropbear_engine::graphics::NO_TEXTURE; use dropbear_engine::lighting::{Light, LightComponent, LightType}; use dropbear_engine::utils::ResourceReference; @@ -206,6 +206,21 @@ impl InspectableComponent for ModelProperties { } } +impl InspectableComponent for EntityTransform { + fn inspect( + &mut self, + entity: &mut Entity, + cfg: &mut StaticallyKept, + ui: &mut Ui, + undo_stack: &mut Vec<UndoableAction>, + signal: &mut Signal, + _label: &mut String + ) { + self.local_mut().inspect(entity, cfg, ui, undo_stack, signal, &mut "Local Transform".to_string()); + self.world_mut().inspect(entity, cfg, ui, undo_stack, signal, &mut "World Transform".to_string()); + } +} + impl InspectableComponent for Transform { fn inspect( &mut self, @@ -214,380 +229,388 @@ impl InspectableComponent for Transform { ui: &mut Ui, undo_stack: &mut Vec<UndoableAction>, _signal: &mut Signal, - _label: &mut String, + label: &mut String, ) { ui.vertical(|ui| { - CollapsingHeader::new("Transform") + CollapsingHeader::new(label.clone()) .default_open(true) .show(ui, |ui| { ui.horizontal(|ui| { ui.label("Position:"); }); - ui.horizontal(|ui| { - ui.label("X:"); - let response = ui.add( - egui::DragValue::new(&mut self.position.x) - .speed(0.1) - .fixed_decimals(3), - ); + ui.horizontal_wrapped(|ui| { + ui.horizontal(|ui| { + ui.label("X:"); + let response = ui.add( + egui::DragValue::new(&mut self.position.x) + .speed(0.1) + .fixed_decimals(3), + ); - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*self); - cfg.transform_in_progress = true; - } + if response.drag_started() { + cfg.transform_old_entity = Some(*entity); + cfg.transform_original_transform = Some(*self); + cfg.transform_in_progress = true; + } - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed X transform change to undo stack"); + if response.drag_stopped() && cfg.transform_in_progress { + if let Some(ent) = cfg.transform_old_entity.take() + && let Some(orig) = cfg.transform_original_transform.take() + { + UndoableAction::push_to_undo( + undo_stack, + UndoableAction::Transform(ent, orig), + ); + log::debug!("Pushed X transform change to undo stack"); + } + cfg.transform_in_progress = false; } - cfg.transform_in_progress = false; - } - }); - ui.horizontal(|ui| { - ui.label("Y:"); - let response = ui.add( - egui::DragValue::new(&mut self.position.y) - .speed(0.1) - .fixed_decimals(3), - ); + }); + ui.horizontal(|ui| { + ui.label("Y:"); + let response = ui.add( + egui::DragValue::new(&mut self.position.y) + .speed(0.1) + .fixed_decimals(3), + ); - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*self); - cfg.transform_in_progress = true; - } + if response.drag_started() { + cfg.transform_old_entity = Some(*entity); + cfg.transform_original_transform = Some(*self); + cfg.transform_in_progress = true; + } - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Y transform change to undo stack"); + if response.drag_stopped() && cfg.transform_in_progress { + if let Some(ent) = cfg.transform_old_entity.take() + && let Some(orig) = cfg.transform_original_transform.take() + { + UndoableAction::push_to_undo( + undo_stack, + UndoableAction::Transform(ent, orig), + ); + log::debug!("Pushed Y transform change to undo stack"); + } + cfg.transform_in_progress = false; } - cfg.transform_in_progress = false; - } - }); + }); - ui.horizontal(|ui| { - ui.label("Z:"); - let response = ui.add( - egui::DragValue::new(&mut self.position.z) - .speed(0.1) - .fixed_decimals(3), - ); + ui.horizontal(|ui| { + ui.label("Z:"); + let response = ui.add( + egui::DragValue::new(&mut self.position.z) + .speed(0.1) + .fixed_decimals(3), + ); - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*self); - cfg.transform_in_progress = true; - } + if response.drag_started() { + cfg.transform_old_entity = Some(*entity); + cfg.transform_original_transform = Some(*self); + cfg.transform_in_progress = true; + } - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Z transform change to undo stack"); + if response.drag_stopped() && cfg.transform_in_progress { + if let Some(ent) = cfg.transform_old_entity.take() + && let Some(orig) = cfg.transform_original_transform.take() + { + UndoableAction::push_to_undo( + undo_stack, + UndoableAction::Transform(ent, orig), + ); + log::debug!("Pushed Z transform change to undo stack"); + } + cfg.transform_in_progress = false; } - cfg.transform_in_progress = false; - } + }); }); + if ui.button("Reset Position").clicked() { - self.position = glam::DVec3::ZERO; + self.position = DVec3::ZERO; } ui.add_space(5.0); - ui.horizontal(|ui| { - ui.label("Rotation:"); - }); + ui.horizontal_wrapped(|ui| { + ui.horizontal(|ui| { + ui.label("Rotation:"); + }); - let cached_rotation = cfg.transform_rotation_cache.get(entity).copied(); + let cached_rotation = cfg.transform_rotation_cache.get(entity).copied(); - let mut rotation_deg: DVec3 = if cfg.transform_in_progress { - cached_rotation.unwrap_or_else(|| { + let mut rotation_deg: DVec3 = if cfg.transform_in_progress { + cached_rotation.unwrap_or_else(|| { + let (x, y, z) = self.rotation.to_euler(glam::EulerRot::YXZ); + DVec3::new(x.to_degrees(), y.to_degrees(), z.to_degrees()) + }) + } else { let (x, y, z) = self.rotation.to_euler(glam::EulerRot::YXZ); - DVec3::new(x.to_degrees(), y.to_degrees(), z.to_degrees()) - }) - } else { - let (x, y, z) = self.rotation.to_euler(glam::EulerRot::YXZ); - let mut degrees = - DVec3::new(x.to_degrees(), y.to_degrees(), z.to_degrees()); - - if let Some(prev) = cached_rotation { - degrees.x = reconcile_angle(degrees.x, prev.x); - degrees.y = reconcile_angle(degrees.y, prev.y); - degrees.z = reconcile_angle(degrees.z, prev.z); - } + let mut degrees = + DVec3::new(x.to_degrees(), y.to_degrees(), z.to_degrees()); - degrees.x = wrap_angle_degrees(degrees.x); - degrees.y = wrap_angle_degrees(degrees.y); - degrees.z = wrap_angle_degrees(degrees.z); + if let Some(prev) = cached_rotation { + degrees.x = reconcile_angle(degrees.x, prev.x); + degrees.y = reconcile_angle(degrees.y, prev.y); + degrees.z = reconcile_angle(degrees.z, prev.z); + } - cfg.transform_rotation_cache.insert(*entity, degrees); - degrees - }; + degrees.x = wrap_angle_degrees(degrees.x); + degrees.y = wrap_angle_degrees(degrees.y); + degrees.z = wrap_angle_degrees(degrees.z); - let mut rotation_changed = false; + cfg.transform_rotation_cache.insert(*entity, degrees); + degrees + }; - ui.horizontal(|ui| { - ui.label("X:"); - let response = ui.add( - egui::DragValue::new(&mut rotation_deg.x) - .speed(0.5) - .suffix("°") - .range(-180.0..=180.0) - .fixed_decimals(2), - ); + let mut rotation_changed = false; - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*self); - cfg.transform_in_progress = true; - } + ui.horizontal(|ui| { + ui.label("Pitch (X):"); + let response = ui.add( + egui::DragValue::new(&mut rotation_deg.x) + .speed(0.5) + .suffix("°") + .range(-180.0..=180.0) + .fixed_decimals(2), + ); - if response.changed() { - rotation_changed = true; - } + if response.drag_started() { + cfg.transform_old_entity = Some(*entity); + cfg.transform_original_transform = Some(*self); + cfg.transform_in_progress = true; + } - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed X rotation change to undo stack"); + if response.changed() { + rotation_changed = true; } - cfg.transform_in_progress = false; - } - }); - ui.horizontal(|ui| { - ui.label("Y:"); - let response = ui.add( - egui::DragValue::new(&mut rotation_deg.y) - .speed(0.5) - .suffix("°") - .range(-180.0..=180.0) - .fixed_decimals(2), - ); + if response.drag_stopped() && cfg.transform_in_progress { + if let Some(ent) = cfg.transform_old_entity.take() + && let Some(orig) = cfg.transform_original_transform.take() + { + UndoableAction::push_to_undo( + undo_stack, + UndoableAction::Transform(ent, orig), + ); + log::debug!("Pushed X rotation change to undo stack"); + } + cfg.transform_in_progress = false; + } + }); - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*self); - cfg.transform_in_progress = true; - } + ui.horizontal(|ui| { + ui.label("Yaw (Y):"); + let response = ui.add( + egui::DragValue::new(&mut rotation_deg.y) + .speed(0.5) + .suffix("°") + .range(-180.0..=180.0) + .fixed_decimals(2), + ); - if response.changed() { - rotation_changed = true; - } + if response.drag_started() { + cfg.transform_old_entity = Some(*entity); + cfg.transform_original_transform = Some(*self); + cfg.transform_in_progress = true; + } - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Y rotation change to undo stack"); + if response.changed() { + rotation_changed = true; } - cfg.transform_in_progress = false; - } - }); - ui.horizontal(|ui| { - ui.label("Z:"); - let response = ui.add( - egui::DragValue::new(&mut rotation_deg.z) - .speed(0.5) - .suffix("°") - .range(-180.0..=180.0) - .fixed_decimals(2), - ); + if response.drag_stopped() && cfg.transform_in_progress { + if let Some(ent) = cfg.transform_old_entity.take() + && let Some(orig) = cfg.transform_original_transform.take() + { + UndoableAction::push_to_undo( + undo_stack, + UndoableAction::Transform(ent, orig), + ); + log::debug!("Pushed Y rotation change to undo stack"); + } + cfg.transform_in_progress = false; + } + }); - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*self); - cfg.transform_in_progress = true; - } + ui.horizontal(|ui| { + ui.label("Roll (Z):"); + let response = ui.add( + egui::DragValue::new(&mut rotation_deg.z) + .speed(0.5) + .suffix("°") + .range(-180.0..=180.0) + .fixed_decimals(2), + ); - if response.changed() { - rotation_changed = true; - } + if response.drag_started() { + cfg.transform_old_entity = Some(*entity); + cfg.transform_original_transform = Some(*self); + cfg.transform_in_progress = true; + } - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Z rotation change to undo stack"); + if response.changed() { + rotation_changed = true; + } + + if response.drag_stopped() && cfg.transform_in_progress { + if let Some(ent) = cfg.transform_old_entity.take() + && let Some(orig) = cfg.transform_original_transform.take() + { + UndoableAction::push_to_undo( + undo_stack, + UndoableAction::Transform(ent, orig), + ); + log::debug!("Pushed Z rotation change to undo stack"); + } + cfg.transform_in_progress = false; } - cfg.transform_in_progress = false; + }); + + if rotation_changed { + rotation_deg.x = wrap_angle_degrees(rotation_deg.x); + rotation_deg.y = wrap_angle_degrees(rotation_deg.y); + rotation_deg.z = wrap_angle_degrees(rotation_deg.z); + + cfg.transform_rotation_cache.insert(*entity, rotation_deg); + self.rotation = glam::DQuat::from_euler( + glam::EulerRot::YXZ, + rotation_deg.x.to_radians(), + rotation_deg.y.to_radians(), + rotation_deg.z.to_radians(), + ); } }); - if rotation_changed { - rotation_deg.x = wrap_angle_degrees(rotation_deg.x); - rotation_deg.y = wrap_angle_degrees(rotation_deg.y); - rotation_deg.z = wrap_angle_degrees(rotation_deg.z); - - cfg.transform_rotation_cache.insert(*entity, rotation_deg); - self.rotation = glam::DQuat::from_euler( - glam::EulerRot::YXZ, - rotation_deg.x.to_radians(), - rotation_deg.y.to_radians(), - rotation_deg.z.to_radians(), - ); - } if ui.button("Reset Rotation").clicked() { self.rotation = glam::DQuat::IDENTITY; cfg.transform_rotation_cache.insert(*entity, DVec3::ZERO); } ui.add_space(5.0); - ui.horizontal(|ui| { - ui.label("Scale:"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let lock_icon = if cfg.scale_locked { "🔒" } else { "🔓" }; - if ui - .button(lock_icon) - .on_hover_text("Lock uniform scaling") - .clicked() - { - cfg.scale_locked = !cfg.scale_locked; - } + ui.horizontal_wrapped(|ui| { + ui.horizontal(|ui| { + ui.label("Scale:"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let lock_icon = if cfg.scale_locked { "🔒" } else { "🔓" }; + if ui + .button(lock_icon) + .on_hover_text("Lock uniform scaling") + .clicked() + { + cfg.scale_locked = !cfg.scale_locked; + } + }); }); - }); - let mut scale_changed = false; - let mut new_scale = self.scale; + let mut scale_changed = false; + let mut new_scale = self.scale; - ui.horizontal(|ui| { - ui.label("X:"); - let response = ui.add( - egui::DragValue::new(&mut new_scale.x) - .speed(0.01) - .fixed_decimals(3), - ); + ui.horizontal(|ui| { + ui.label("X:"); + let response = ui.add( + DragValue::new(&mut new_scale.x) + .speed(0.01) + .fixed_decimals(3), + ); - if response.drag_started() { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*self); - cfg.transform_in_progress = true; - } + if response.drag_started() { + cfg.transform_old_entity = Some(*entity); + cfg.transform_original_transform = Some(*self); + cfg.transform_in_progress = true; + } - if response.changed() { - scale_changed = true; - if cfg.scale_locked { - let scale_factor = new_scale.x / self.scale.x; - new_scale.y = self.scale.y * scale_factor; - new_scale.z = self.scale.z * scale_factor; + if response.changed() { + scale_changed = true; + if cfg.scale_locked { + let scale_factor = new_scale.x / self.scale.x; + new_scale.y = self.scale.y * scale_factor; + new_scale.z = self.scale.z * scale_factor; + } } - } - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed X scale change to undo stack"); + if response.drag_stopped() && cfg.transform_in_progress { + if let Some(ent) = cfg.transform_old_entity.take() + && let Some(orig) = cfg.transform_original_transform.take() + { + UndoableAction::push_to_undo( + undo_stack, + UndoableAction::Transform(ent, orig), + ); + log::debug!("Pushed X scale change to undo stack"); + } + cfg.transform_in_progress = false; } - cfg.transform_in_progress = false; - } - }); + }); - ui.horizontal(|ui| { - ui.label("Y:"); - let y_slider = egui::DragValue::new(&mut new_scale.y) - .speed(0.01) - .fixed_decimals(3); + ui.horizontal(|ui| { + ui.label("Y:"); + let y_slider = egui::DragValue::new(&mut new_scale.y) + .speed(0.01) + .fixed_decimals(3); - let response = ui.add_enabled(!cfg.scale_locked, y_slider); + let response = ui.add_enabled(!cfg.scale_locked, y_slider); - if response.drag_started() && !cfg.scale_locked { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*self); - cfg.transform_in_progress = true; - } + if response.drag_started() && !cfg.scale_locked { + cfg.transform_old_entity = Some(*entity); + cfg.transform_original_transform = Some(*self); + cfg.transform_in_progress = true; + } - if response.changed() { - scale_changed = true; - } + if response.changed() { + scale_changed = true; + } - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Y scale change to undo stack"); + if response.drag_stopped() && cfg.transform_in_progress { + if let Some(ent) = cfg.transform_old_entity.take() + && let Some(orig) = cfg.transform_original_transform.take() + { + UndoableAction::push_to_undo( + undo_stack, + UndoableAction::Transform(ent, orig), + ); + log::debug!("Pushed Y scale change to undo stack"); + } + cfg.transform_in_progress = false; } - cfg.transform_in_progress = false; - } - }); + }); - ui.horizontal(|ui| { - ui.label("Z:"); - let z_slider = egui::DragValue::new(&mut new_scale.z) - .speed(0.01) - .fixed_decimals(3); + ui.horizontal(|ui| { + ui.label("Z:"); + let z_slider = egui::DragValue::new(&mut new_scale.z) + .speed(0.01) + .fixed_decimals(3); - let response = ui.add_enabled(!cfg.scale_locked, z_slider); + let response = ui.add_enabled(!cfg.scale_locked, z_slider); - if response.drag_started() && !cfg.scale_locked { - cfg.transform_old_entity = Some(*entity); - cfg.transform_original_transform = Some(*self); - cfg.transform_in_progress = true; - } + if response.drag_started() && !cfg.scale_locked { + cfg.transform_old_entity = Some(*entity); + cfg.transform_original_transform = Some(*self); + cfg.transform_in_progress = true; + } - if response.changed() { - scale_changed = true; - } + if response.changed() { + scale_changed = true; + } - if response.drag_stopped() && cfg.transform_in_progress { - if let Some(ent) = cfg.transform_old_entity.take() - && let Some(orig) = cfg.transform_original_transform.take() - { - UndoableAction::push_to_undo( - undo_stack, - UndoableAction::Transform(ent, orig), - ); - log::debug!("Pushed Z scale change to undo stack"); + if response.drag_stopped() && cfg.transform_in_progress { + if let Some(ent) = cfg.transform_old_entity.take() + && let Some(orig) = cfg.transform_original_transform.take() + { + UndoableAction::push_to_undo( + undo_stack, + UndoableAction::Transform(ent, orig), + ); + log::debug!("Pushed Z scale change to undo stack"); + } + cfg.transform_in_progress = false; } - cfg.transform_in_progress = false; + }); + + if scale_changed { + self.scale = new_scale; } }); - - if scale_changed { - self.scale = new_scale; - } if ui.button("Reset Scale").clicked() { - self.scale = glam::DVec3::ONE; + self.scale = DVec3::ONE; } ui.add_space(5.0); }); @@ -669,7 +692,7 @@ impl InspectableComponent for MeshRenderer { if let Some(orig) = cfg.label_original.take() { UndoableAction::push_to_undo( undo_stack, - UndoableAction::Label(ent, orig, EntityType::Entity), + UndoableAction::Label(ent, orig), ); log::debug!("Pushed label change to undo stack (immediate)"); } @@ -997,7 +1020,7 @@ impl InspectableComponent for Light { if let Some(orig) = cfg.label_original.take() { UndoableAction::push_to_undo( undo_stack, - UndoableAction::Label(ent, orig, EntityType::Light), + UndoableAction::Label(ent, orig), ); log::debug!("Pushed label change to undo stack (immediate)"); } @@ -4,7 +4,7 @@ use crate::editor::{ console_error::{ConsoleItem, ErrorLevel}, }; use std::{ - collections::{HashMap, HashSet}, + collections::{HashMap}, path::PathBuf, sync::LazyLock, }; @@ -18,17 +18,14 @@ use dropbear_engine::{ }; use egui::{self, CollapsingHeader, Margin, RichText}; use egui_dock::TabViewer; -use egui_extras; -use eucalyptus_core::spawn::{PendingSpawn, push_pending_spawn}; -use eucalyptus_core::states::{File, Label, Node, RESOURCES, ResourceType}; -use eucalyptus_core::{APP_INFO, utils::ResolveReference}; +use indexmap::Equivalent; +use eucalyptus_core::states::{Label}; use log; use parking_lot::Mutex; -use transform_gizmo_egui::{EnumSet, Gizmo, GizmoConfig, GizmoExt, GizmoMode, math::DVec3}; +use transform_gizmo_egui::{EnumSet, Gizmo, GizmoConfig, GizmoExt, GizmoMode}; pub struct EditorTabViewer<'a> { pub view: egui::TextureId, - pub nodes: Vec<EntityNode>, pub tex_size: Extent3d, pub gizmo: &'a mut Gizmo, pub world: &'a mut World, @@ -45,40 +42,6 @@ pub struct EditorTabViewer<'a> { pub editor: *mut Editor, } -impl<'a> EditorTabViewer<'a> { - fn spawn_entity_at_pos( - &mut self, - asset: &DraggedAsset, - position: DVec3, - properties: Option<ModelProperties>, - ) -> anyhow::Result<()> { - let transform = Transform { - position, - ..Default::default() - }; - { - if let Some(props) = properties { - push_pending_spawn(PendingSpawn { - asset_path: asset.path.clone(), - asset_name: asset.name.clone(), - transform, - properties: props, - handle: None, - }); - } else { - push_pending_spawn(PendingSpawn { - asset_path: asset.path.clone(), - asset_name: asset.name.clone(), - transform, - properties: ModelProperties::default(), - handle: None, - }); - } - Ok(()) - } - } -} - #[derive(Clone, Debug)] pub struct DraggedAsset { pub name: String, @@ -202,181 +165,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> { .fit_to_exact_size([display_width, display_height].into()), ) }); - - let image_response = ui.interact( - image_rect, - ui.id().with("viewport_image"), - egui::Sense::click_and_drag(), - ); - - if image_response.clicked() { - let active_cam = self.active_camera.lock(); - if let Some(active_camera) = *active_cam { - { - if let Ok(mut q) = self - .world - .query_one::<(&Camera, &CameraComponent)>(active_camera) - { - if let Some((camera, _)) = q.get() { - if let Some(click_pos) = - ui.ctx().input(|i| i.pointer.interact_pos()) - { - let viewport_rect = image_response.rect; - let local_pos = click_pos - viewport_rect.min; - - let ndc_x = - (2.0 * local_pos.x / viewport_rect.width()) - 1.0; - let ndc_y = - 1.0 - (2.0 * local_pos.y / viewport_rect.height()); - - let view_matrix = glam::DMat4::look_at_lh( - camera.eye, - camera.target, - camera.up, - ); - - let proj_matrix = glam::DMat4::perspective_lh( - camera.settings.fov_y.to_radians(), - camera.aspect, - camera.znear, - camera.zfar, - ); - - if !view_matrix.is_finite() { - log::error!("Invalid view matrix"); - return; - } - if !proj_matrix.is_finite() { - log::error!("Invalid projection matrix"); - return; - } - - let view_proj = proj_matrix * view_matrix; - let inv_view_proj = view_proj.inverse(); - - if !inv_view_proj.is_finite() { - log::error!("Cannot invert view-projection matrix"); - return; - } - - let ray_start_ndc = - glam::DVec4::new(ndc_x as f64, ndc_y as f64, 0.0, 1.0); - let ray_end_ndc = - glam::DVec4::new(ndc_x as f64, ndc_y as f64, 1.0, 1.0); - - let ray_start_world = inv_view_proj * ray_start_ndc; - let ray_end_world = inv_view_proj * ray_end_ndc; - - if ray_start_world.w == 0.0 || ray_end_world.w == 0.0 { - log::error!("Invalid homogeneous coordinates"); - return; - } - - let ray_start = - ray_start_world.truncate() / ray_start_world.w; - let ray_end = ray_end_world.truncate() / ray_end_world.w; - - if !ray_start.is_finite() || !ray_end.is_finite() { - log::error!( - "Invalid ray points - start: {:?}, end: {:?}", - ray_start, - ray_end - ); - return; - } - - let ray_direction = (ray_end - ray_start).normalize(); - - if !ray_direction.is_finite() { - log::error!( - "Invalid ray direction: {:?}", - ray_direction - ); - return; - } - - let (selected_entity_id, entity_count) = { - let mut closest_distance = f64::INFINITY; - let mut selected_entity_id: Option<hecs::Entity> = None; - let mut entity_count = 0; - - { - for (entity_id, (transform, _)) in self - .world - .query::<(&Transform, &MeshRenderer)>() - .iter() - { - entity_count += 1; - let entity_pos = transform.position; - let sphere_radius = - transform.scale.max_element() * 1.5; - let to_sphere = entity_pos - ray_start; - let projection = to_sphere.dot(ray_direction); - if projection > 0.0 { - let closest_point = - ray_start + ray_direction * projection; - let distance_to_sphere = - (closest_point - entity_pos).length(); - if distance_to_sphere <= sphere_radius { - let discriminant = sphere_radius - * sphere_radius - - distance_to_sphere - * distance_to_sphere; - if discriminant >= 0.0 { - let intersection_distance = - projection - - discriminant.sqrt(); - if intersection_distance - < closest_distance - { - closest_distance = - intersection_distance; - selected_entity_id = - Some(entity_id); - } - } - } - } - } - } - - (selected_entity_id, entity_count) - }; - - log::debug!("Total entities checked: {}", entity_count); - - if !matches!(self.editor_mode, EditorState::Playing) { - if let Some(entity_id) = selected_entity_id { - *self.selected_entity = Some(entity_id); - log::debug!("Selected entity: {:?}", entity_id); - } else if entity_count == 0 { - log::debug!("No entities in world to select"); - } else { - log::debug!( - "No entity hit by ray (checked {} entities)", - entity_count - ); - } - } - } - } else { - log_once::warn_once!( - "Unable to fetch the query result of camera: {:?}", - active_camera - ) - } - } else { - log_once::warn_once!( - "Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", - active_camera - ); - } - } - } else { - log_once::warn_once!("No active camera found"); - } - } - + let snapping = ui.input(|input| input.modifiers.shift); // Note to self: fuck you >:( @@ -464,312 +253,49 @@ impl<'a> TabViewer for EditorTabViewer<'a> { } } EditorTab::ModelEntityList => { - ui.label("Model/Entity List"); - show_entity_tree( - ui, - &mut self.nodes, - self.selected_entity, - "Model Entity Asset List", - ); + // todo: use egui_ltreeview to create a component graph + // egui_ltreeview::TreeView::new(egui::Id::new("model_entity_list")).show(ui, |builder| { + // builder.dir() + // // entity is entity.id() + // // component is entity.id() * -100 * + // }); } EditorTab::AssetViewer => { - egui_extras::install_image_loaders(ui.ctx()); - - let mut assets: Vec<(String, String, PathBuf, ResourceType)> = Vec::new(); - { - let res = RESOURCES.read(); - egui_extras::install_image_loaders(ui.ctx()); - - fn recursive_search_nodes_and_attach_thumbnail( - res: &Vec<Node>, - assets: &mut Vec<(String, String, PathBuf, ResourceType)>, - logged: &mut HashSet<String>, - ) { - for node in res { - match node { - Node::File(file) => { - match file { - File::Unknown => {} - File::ResourceFile { - name, - path, - resource_type, - } => { - if !logged.contains(name) { - logged.insert(name.clone()); - log::debug!( - "Adding image for {} of type {}", - name, - resource_type - ); - } - match resource_type { - ResourceType::Model => { - let ad_dir = app_dirs2::get_app_root( - app_dirs2::AppDataType::UserData, - &APP_INFO, - ) - .unwrap(); - - let model_thumbnail = - ad_dir.join(format!("{}.png", name)); - - if !model_thumbnail.exists() { - // gen image - log::debug!( - "Model thumbnail [{}] does not exist, generating one now", - name - ); - let path = ResourceReference::from_path( - path, - ) // cuts off everything to the /resources folder - .unwrap() - .resolve() // resovles path to reference of resource - .unwrap(); - let mut model = match model_to_image::ModelToImageBuilder::new(&path) - .with_size((600, 600)) - .build() { - Ok(v) => v, - Err(e) => panic!("Error occurred while loading file from path: {}", e), - }; - if let Err(e) = - model.render().unwrap().write_to(Some( - &ad_dir - .join(format!("{}.png", name)), - )) - { - log::error!( - "Failed to write model thumbnail for {}: {}", - name, - e - ); - } - } - - let image_uri = model_thumbnail - .to_string_lossy() - .to_string(); - - assets.push(( - format!("file://{}", image_uri), - name.clone(), - path.clone(), - resource_type.clone(), - )) - } - ResourceType::Texture => assets.push(( - format!("file://{}", path.to_string_lossy()), - name.clone(), - path.clone(), - resource_type.clone(), - )), - _ => { - if path - .clone() - .extension() - .unwrap() - .to_str() - .unwrap() - .contains("euc") - { - continue; - } - assets.push(( - "NO_TEXTURE".into(), - name.clone(), - path.clone(), - resource_type.clone(), - )) - } - } - } - File::SourceFile { .. } => {} - } - } - Node::Folder(folder) => { - recursive_search_nodes_and_attach_thumbnail( - &folder.nodes, - assets, - logged, - ) - } - } - } - } - - let mut logged = LOGGED.lock(); - recursive_search_nodes_and_attach_thumbnail( - &res.nodes, - &mut assets, - &mut logged, - ); - } - - egui::ScrollArea::vertical().show(ui, |ui| { - let max_columns = 6; - let available_width = ui.clip_rect().width() - ui.spacing().indent; - let margin = 16.0; - let usable_width = available_width - margin; - let label_height = 20.0; - let padding = 8.0; - let min_card_width = 60.0; - - let mut columns = max_columns; - for test_columns in (1..=max_columns).rev() { - let card_width = usable_width / test_columns as f32; - if card_width >= min_card_width { - columns = test_columns; - break; - } - } - - let card_width = usable_width / columns as f32; - let image_size = (card_width - label_height - padding).max(32.0); - let card_height = image_size + label_height + padding; - - for row_start in (0..assets.len()).step_by(columns) { - let row_end = (row_start + columns).min(assets.len()); - let row_items = &mut assets[row_start..row_end]; - - ui.horizontal(|ui| { - ui.set_max_width(usable_width); - - egui_dnd::dnd(ui, format!("asset_row_{}", row_start / columns)) - .show_vec( - row_items, - |ui, (image, asset_name, asset_path, asset_type), handle, state| { - let card_size = egui::vec2(card_width, card_height); - handle.ui(ui, |ui| { - let (rect, card_response) = ui.allocate_exact_size( - card_size, - egui::Sense::click(), - ); - - let mut card_ui = ui.new_child( - egui::UiBuilder::new().max_rect(rect).layout( - egui::Layout::top_down(egui::Align::Center), - ), - ); - - let image_response = card_ui.add_sized( - [image_size, image_size], - egui::Button::image(image.clone()).frame(false), - ); - - let is_hovered = card_response.hovered() || image_response.hovered() || state.dragged; - let is_d_clicked = card_response.double_clicked() || image_response.double_clicked(); - - if is_d_clicked - && matches!(asset_type, ResourceType::Model) { - let mut spawn_position = DVec3::default(); - { - let active_cam = self.active_camera.lock(); - if let Some(active_camera) = *active_cam { - if let Ok(mut q) = self.world.query_one::<(&Camera, &CameraComponent)>(active_camera) { - if let Some((camera, _)) = q.get() { - spawn_position = camera.eye; - } else { - log_once::warn_once!("Unable to fetch the query result of camera: {:?}", active_camera) - } - } else { - log_once::warn_once!("Unable to query camera, component and option<camerafollowtarget> for active camera: {:?}", active_camera); - } - } else { - log_once::warn_once!("No active camera found"); - } - } - - let asset = DraggedAsset { - name: asset_name.clone(), - path: ResourceReference::from_path(asset_path.clone()).unwrap_or_else(|_e| { - log::warn!("Unable to create ResourceReference from path: {:?}", asset_path); - Default::default() - }), - }; - - match self.spawn_entity_at_pos(&asset, spawn_position, None) { - Ok(()) => { - log::debug!("double click spawned {} at camera pos {:?}", - asset.name, spawn_position - ); - - success!("Pushing spawn \"{}\" at camera", asset.name); - } - Err(e) => { - log::error!( - "Failed to spawn {} at camera: {}", - asset.name, - e); - - fatal!("Failed to spawn {}: {}", - asset.name, e); - } - } - } - - if is_hovered || state.dragged { - ui.painter().rect_filled( - rect, - 6.0, - if state.dragged { - egui::Color32::from_rgb(80, 80, 100) - } else { - egui::Color32::from_rgb(60, 60, 80) - }, - ); - } - - card_ui.vertical_centered(|ui| { - ui.label( - egui::RichText::new(asset_name.clone()) - .strong() - .color(egui::Color32::WHITE), - ); - }); - }); - }, - ); - }); - ui.add_space(8.0); - } - }); + // // todo: use egui_ltreeview to create a asset viewer + // egui_ltreeview::TreeView::new(egui::Id::new("asset_viewer")).show(ui, |builder| { + // + // }); } EditorTab::ResourceInspector => { if let Some(entity) = self.selected_entity { let mut local_set_initial_camera = false; if let Ok(mut q) = self.world.query_one::<( &mut Label, - &mut MeshRenderer, - Option<&mut Transform>, - Option<&mut ModelProperties>, - Option<&mut ScriptComponent>, - Option<&mut Camera>, - Option<&mut CameraComponent>, - // Option<&mut CameraFollowTarget>, )>(*entity) { if let Some(( label, - e, - transform, - _props, - script, - camera, - camera_component, - // follow_target, )) = q.get() { - // entity - e.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - label.as_mut_string(), - ); - // transform - if let Some(t) = transform { + + if let Ok(mut q) = self.world.query_one::<&mut MeshRenderer>(*entity) + && let Some(e) = q.get() + { + // entity + e.inspect( + entity, + &mut cfg, + ui, + self.undo_stack, + self.signal, + label.as_mut_string(), + ); + } + + if let Ok(mut q) = self.world.query_one::<&mut EntityTransform>(*entity) + && let Some(t) = q.get() + { + // transform t.inspect( entity, &mut cfg, @@ -780,8 +306,10 @@ impl<'a> TabViewer for EditorTabViewer<'a> { ); } - // properties - if let Some(props) = _props { + if let Ok(mut q) = self.world.query_one::<&mut ModelProperties>(*entity) + && let Some(props) = q.get() + { + // properties props.inspect( entity, &mut cfg, @@ -792,97 +320,68 @@ impl<'a> TabViewer for EditorTabViewer<'a> { ); } - // camera - if let (Some(camera), Some(camera_component)) = - (camera, camera_component) + + if let Ok(mut q) = self.world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity) + && let Some((camera, camera_component)) = q.get() { - ui.separator(); CollapsingHeader::new("Camera Components") - .default_open(true) - .show(ui, |ui| { - ui.horizontal(|ui| { - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.button("Remove Component ❌").clicked() { - *self.signal = Signal::RemoveComponent( - *entity, - Box::new(ComponentType::Camera( - Box::new(camera.clone()), - camera_component.clone(), - // None, - )), - ); - // } - } - }); - }); - - camera.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut String::new(), - ); - camera_component.camera_type = CameraType::Player; // it will always be a player if attached to an entity, never normal - camera_component.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut camera.label.clone(), - ); + .default_open(true) + .show(ui, |ui| { + camera.inspect( + entity, + &mut cfg, + ui, + self.undo_stack, + self.signal, + &mut String::new(), + ); - ui.separator(); - ui.label("Camera Controls:"); - let mut active_camera = self.active_camera.lock(); + camera_component.inspect( + entity, + &mut cfg, + ui, + self.undo_stack, + self.signal, + &mut camera.label.clone(), + ); - if *active_camera == Some(*entity) { - ui.label("Status: Currently viewing through camera"); - } else { - ui.label("Status: Not viewing through this camera"); - } + ui.separator(); - if ui.button("Set as active camera").clicked() { - *active_camera = Some(*entity); - log::info!("Currently viewing from camera angle '{}'", camera.label); - } + // camera controller + ui.label("Camera Controls:"); + let mut active_camera = self.active_camera.lock(); - if camera_component.starting_camera { - if ui.button("Stop making camera initial").clicked() { - log::debug!("'Stop making camera initial' button clicked"); - camera_component.starting_camera = false; - success!("Removed {} from starting camera", camera.label); - } - } else if ui.button("Set as initial camera").clicked() { - log::debug!("'Set as initial camera' button clicked"); - if matches!(camera_component.camera_type, CameraType::Debug) { - warn!("Cannot set any cameras of type 'Debug' to initial camera"); + if active_camera.equivalent(&Some(*entity)) { + ui.label("Status: Currently viewing through camera"); } else { - local_set_initial_camera = true + ui.label("Status: Not viewing through this camera"); + } + + if ui.button("Set as active camera").clicked() { + *active_camera = Some(*entity); + log::info!("Currently viewing from camera angle '{}'", camera.label); } - } - }); - } - // scripting - if let Some(script) = script { - ui.separator(); - ui.horizontal(|ui| { - ui.with_layout( - egui::Layout::right_to_left(egui::Align::Center), - |ui| { - if ui.button("Remove Component ❌").clicked() { - *self.signal = Signal::RemoveComponent( - *entity, - Box::new(ComponentType::Script(script.clone())), - ) + if camera_component.starting_camera { + if ui.button("Stop making camera initial").clicked() { + log::debug!("'Stop making camera initial' button clicked"); + camera_component.starting_camera = false; + success!("Removed {} from starting camera", camera.label); } - }, - ); - }); + } else if ui.button("Set as initial camera").clicked() { + log::debug!("'Set as initial camera' button clicked"); + if matches!(camera_component.camera_type, CameraType::Debug) { + warn!("Cannot set any cameras of type 'Debug' to initial camera"); + } else { + local_set_initial_camera = true + } + } + }); + } + if let Ok(mut q) = self.world.query_one::<&mut ScriptComponent>(*entity) + && let Some(script) = q.get() + { script.inspect( entity, &mut cfg, @@ -893,6 +392,28 @@ impl<'a> TabViewer for EditorTabViewer<'a> { ); } + if let Ok(mut q) = self.world.query_one::<(&mut Light, &mut LightComponent)>(*entity) + && let Some((light, comp)) = q.get() + { + light.inspect( + entity, + &mut cfg, + ui, + self.undo_stack, + self.signal, + &mut String::new(), + ); + + comp.inspect( + entity, + &mut cfg, + ui, + self.undo_stack, + self.signal, + &mut light.label, + ); + } + if let Some(t) = cfg.label_last_edit && t.elapsed() >= Duration::from_millis(500) { @@ -901,7 +422,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> { { UndoableAction::push_to_undo( self.undo_stack, - UndoableAction::Label(ent, orig, EntityType::Entity), + UndoableAction::Label(ent, orig), ); log::debug!( "Pushed label change to undo stack after 500ms debounce period" @@ -927,123 +448,6 @@ impl<'a> TabViewer for EditorTabViewer<'a> { comp.starting_camera = true; } } - - // lighting system - if let Ok(mut q) = self - .world - .query_one::<(&mut Light, &mut Transform, &mut LightComponent)>(*entity) - { - if let Some((light, transform, props)) = q.get() { - light.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut String::new(), - ); - transform.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut light.label, - ); - props.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut light.label, - ); - if let Some(t) = cfg.label_last_edit - && t.elapsed() >= Duration::from_millis(500) - { - if let Some(ent) = cfg.old_label_entity.take() - && let Some(orig) = cfg.label_original.take() - { - UndoableAction::push_to_undo( - self.undo_stack, - UndoableAction::Label(ent, orig, EntityType::Light), - ); - log::debug!( - "Pushed label change to undo stack after 500ms debounce period" - ); - } - cfg.label_last_edit = None; - } - } - } else { - log_once::debug_once!("Unable to query light inside resource inspector"); - } - - // camera - if let Ok(mut q) = self.world.query_one::<( - &mut Camera, - &mut CameraComponent, - // Option<&mut CameraFollowTarget>, - )>(*entity) - && let Some((camera, camera_component)) = q.get() - && self.world.get::<&MeshRenderer>(*entity).is_err() - { - ui.vertical(|ui| { - camera.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut String::new(), - ); - camera_component.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut camera.label.clone(), - ); - - ui.separator(); - - let mut active_camera = self.active_camera.lock(); - - ui.label("Camera Controls:"); - if *active_camera == Some(*entity) { - ui.label("Status: Currently viewing through camera"); - } else { - ui.label("Status: Not viewing through this camera"); - } - - if ui.button("Set as active camera").clicked() { - *active_camera = Some(*entity); - log::info!("Currently viewing from camera angle '{}'", camera.label); - } - - if camera_component.starting_camera { - if ui.button("Stop making camera initial").clicked() { - log::debug!("'Stop making camera initial' button clicked"); - success!("Removed {} from starting camera", camera.label); - camera_component.starting_camera = false; - } - } else { - #[allow(clippy::collapsible_else_if)] - if ui.button("Set as initial camera").clicked() { - log::debug!("'Set as initial camera' button clicked"); - if matches!(camera_component.camera_type, CameraType::Debug) { - info!("Cannot set any cameras of type 'Debug' to initial camera"); - } else { - success!("Set {} at the starting camera. When you start your game, \ - expect to see through this camera!", camera.label); - camera_component.starting_camera = true; - } - } - } - }); - } - ui.separator(); } else { ui.label("No entity selected, therefore no info to provide. Go on, what are you waiting for? Click an entity!"); } @@ -1185,210 +589,6 @@ impl<'a> TabViewer for EditorTabViewer<'a> { }); } } - - let mut menu_action: Option<EditorTabMenuAction> = None; - let area = egui::Area::new("context_menu".into()) - .fixed_pos(cfg.context_menu_pos) - .order(egui::Order::Foreground); - - if cfg.show_context_menu { - let menu_tab = cfg - .context_menu_tab - .clone() - .unwrap_or(EditorTab::ModelEntityList); - - let mut popup_rect = None; - - area.show(ui.ctx(), |ui| { - egui::Frame::popup(ui.style()).show(ui, |ui| { - popup_rect.replace(ui.max_rect()); - - match menu_tab { - EditorTab::AssetViewer => { - ui.set_min_width(150.0); - if ui.selectable_label(false, "Import resource").clicked() { - menu_action = Some(EditorTabMenuAction::ImportResource); - } - if ui.selectable_label(false, "Refresh assets").clicked() { - menu_action = Some(EditorTabMenuAction::RefreshAssets); - } - } - EditorTab::ModelEntityList => { - ui.set_min_width(150.0); - if ui.selectable_label(false, "Add Entity").clicked() { - menu_action = Some(EditorTabMenuAction::AddEntity); - } - if ui.selectable_label(false, "Delete Entity").clicked() { - menu_action = Some(EditorTabMenuAction::DeleteEntity); - } - } - EditorTab::ResourceInspector => { - ui.set_min_width(150.0); - if ui.selectable_label(false, "Add Component").clicked() { - menu_action = Some(EditorTabMenuAction::AddComponent); - } - } - EditorTab::Viewport => { - ui.set_min_width(150.0); - if ui.selectable_label(false, "Viewport Option").clicked() { - 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"); - } - - let editor = unsafe { &mut *self.editor }; - if let Some((_, plugin)) = - self.plugin_registry.plugins.get_index_mut(dock_info) - { - plugin.context_menu(ui, editor); - } else { - ui.colored_label( - egui::Color32::RED, - format!("Plugin at index '{}' not found", dock_info), - ); - } - } - } - }) - }); - - if let Some(action) = menu_action - && Some(tab.clone()) == cfg.context_menu_tab - { - match action { - EditorTabMenuAction::ImportResource => { - log::debug!("Import Resource clicked"); - - match import_object() { - Ok(_) => { - success!("Resource(s) imported successfully!"); - } - Err(e) => { - warn!("Failed to import resource(s): {e}"); - } - } - cfg.show_context_menu = false; - cfg.context_menu_tab = None; - return; - } - EditorTabMenuAction::RefreshAssets => { - log::debug!("Refresh assets clicked"); - { - let mut res = RESOURCES.write(); - match res.update_mem() { - Ok(res_cfg) => { - *res = res_cfg; - success!("Assets refreshed successfully!"); - } - Err(e) => { - fatal!("Failed to refresh assets: {}", e); - } - } - } - cfg.show_context_menu = false; - cfg.context_menu_tab = None; - return; - } - EditorTabMenuAction::AddEntity => { - log::debug!("Add Entity clicked"); - *self.signal = Signal::CreateEntity; - cfg.show_context_menu = false; - cfg.context_menu_tab = None; - return; - } - EditorTabMenuAction::DeleteEntity => { - log::debug!("Delete Entity clicked"); - *self.signal = Signal::Delete; - cfg.show_context_menu = false; - cfg.context_menu_tab = None; - return; - } - EditorTabMenuAction::AddComponent => { - log::debug!("Add Component clicked"); - if let Some(entity) = self.selected_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"); - *self.signal = - Signal::AddComponent(*entity, EntityType::Entity); - } - } - - { - if let Ok(mut q) = self.world.query_one::<&Light>(*entity) - && q.get().is_some() - { - log::debug!("Queried selected entity, it is a light"); - *self.signal = Signal::AddComponent(*entity, EntityType::Light); - } - } - } else { - warn!( - "What are you adding a component to? Theres no entity selected..." - ); - } - - cfg.show_context_menu = false; - cfg.context_menu_tab = None; - return; - } - EditorTabMenuAction::ViewportOption => { - log::debug!("Viewport Option clicked"); - cfg.show_context_menu = false; - cfg.context_menu_tab = None; - return; - } - EditorTabMenuAction::RemoveComponent => { - log::debug!("Remove Component clicked"); - if let Some(entity) = self.selected_entity { - if let Ok(mut q) = self.world.query_one::<&ScriptComponent>(*entity) { - if let Some(script) = q.get() { - log::debug!( - "Queried selected entity, it has a script component" - ); - *self.signal = Signal::RemoveComponent( - *entity, - Box::new(ComponentType::Script(script.clone())), - ); - } - } else { - warn!("Selected entity does not have a script component to remove"); - } - } else { - panic!( - "Paradoxical error: Cannot remove a component when its not selected..." - ); - } - - cfg.show_context_menu = false; - cfg.context_menu_tab = None; - return; - } - } - } - - if let Some(rect) = popup_rect - && cfg.show_context_menu - && Some(tab.clone()) == cfg.context_menu_tab - && ui - .ctx() - .input(|i| i.pointer.button_clicked(egui::PointerButton::Primary)) - && let Some(pos) = ui.ctx().input(|i| i.pointer.interact_pos()) - && !rect.contains(pos) - { - cfg.show_context_menu = false; - cfg.context_menu_tab = None; - } - } } } @@ -1,6 +1,6 @@ use super::*; use dropbear_engine::{ - entity::{MeshRenderer, Transform}, + entity::{MeshRenderer}, input::{Controller, Keyboard, Mouse}, }; use eucalyptus_core::states::Label; @@ -133,20 +133,19 @@ impl Keyboard for Editor { let query = self.world.query_one::<( &Label, &MeshRenderer, - &Transform, + &EntityTransform, &ModelProperties, )>(*entity); if let Ok(mut q) = query { if let Some((label, renderer, t, props)) = q.get() { let s_entity = SceneEntity { - model_path: renderer.handle().path.clone(), label: label.clone(), - transform: *t, - properties: props.clone(), - script: None, - camera: None, - children: None, - material_overrides: renderer.material_overrides().to_vec(), + components: vec![ + Box::new(props.clone()), + Box::new(SerializedMeshRenderer::from_renderer(&renderer)), + Box::new(t.clone()), + + ], entity_id: None, }; self.signal = Signal::Copy(s_entity); @@ -7,7 +7,6 @@ pub mod scene; pub(crate) use crate::editor::dock::*; use crate::build::build; -use crate::camera::UndoableCameraAction; use crate::debug; use crate::graphics::OutlineShader; use crate::plugin::PluginRegistry; @@ -37,7 +36,7 @@ use eucalyptus_core::{ scripting::{BuildStatus, ScriptManager, ScriptTarget}, states, states::{ - CameraConfig, EditorTab, EntityNode, LightConfig, ModelProperties, ScriptComponent, WorldLoadingStatus, + CameraConfig, EditorTab, LightConfig, ModelProperties, ScriptComponent, WorldLoadingStatus, PROJECT, SCENES, }, success, success_without_console, @@ -50,10 +49,10 @@ use parking_lot::Mutex; use rfd::FileDialog; use std::path::Path; use std::{ - collections::{HashMap, HashSet}, + collections::{HashMap}, fs, path::PathBuf, - sync::{Arc, LazyLock}, + sync::{Arc}, time::{Duration, Instant}, }; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; @@ -1019,7 +1018,6 @@ impl Editor { ui, &mut EditorTabViewer { view: self.texture_id.unwrap(), - nodes: EntityNode::from_world(&self.world), gizmo: &mut self.gizmo, tex_size: self.size, world: &mut self.world, @@ -1435,95 +1433,6 @@ impl Editor { } } -pub static LOGGED: LazyLock<Mutex<HashSet<String>>> = LazyLock::new(|| Mutex::new(HashSet::new())); - -fn show_entity_tree( - ui: &mut egui::Ui, - nodes: &mut [EntityNode], - selected: &mut Option<hecs::Entity>, - id_source: &str, -) { - egui_dnd::Dnd::new(ui, id_source).show_vec(nodes, |ui, item, handle, _dragging| { - match item.clone() { - EntityNode::Entity { id, name } => { - ui.horizontal(|ui| { - handle.ui(ui, |ui| { - ui.label("⏹️"); - }); - let resp = ui.selectable_label(selected.as_ref().eq(&Some(&id)), name); - if resp.clicked() { - *selected = Some(id); - } - }); - } - EntityNode::Script { .. } => { - ui.horizontal(|ui| { - handle.ui(ui, |ui| { - ui.label("📜"); - }); - ui.label("Script"); - }); - } - EntityNode::Group { - ref name, - ref mut children, - ref mut collapsed, - } => { - ui.horizontal(|ui| { - handle.ui(ui, |ui| { - let header = egui::CollapsingHeader::new(name) - .default_open(!*collapsed) - .show(ui, |ui| { - show_entity_tree(ui, children, selected, name); - }); - *collapsed = header.body_returned.is_none(); - }); - }); - } - EntityNode::Light { id, name } => { - ui.horizontal(|ui| { - handle.ui(ui, |ui| { - ui.label("💡"); - }); - let resp = ui.selectable_label(selected.as_ref().eq(&Some(&id)), name); - if resp.clicked() { - *selected = Some(id); - } - }); - } - EntityNode::Camera { - id, - name, - camera_type, - } => { - ui.horizontal(|ui| { - handle.ui(ui, |ui| { - let icon = match camera_type { - CameraType::Debug => "🎥", // Debug camera - CameraType::Player => "📹", // Player camera - CameraType::Normal => "📷", // Normal camera - }; - ui.label(icon); - }); - let display_name = format!( - "{} ({})", - name, - match camera_type { - CameraType::Debug => "Debug", - CameraType::Player => "Player", - CameraType::Normal => "Normal", - } - ); - let resp = ui.selectable_label(selected.as_ref().eq(&Some(&id)), display_name); - if resp.clicked() { - *selected = Some(id); - } - }); - } - } - }); -} - /// Describes an action that is undoable #[derive(Debug)] pub enum UndoableAction { @@ -1533,21 +1442,8 @@ pub enum UndoableAction { /// A spawn of the entity. Undoing will delete the entity Spawn(hecs::Entity), /// A change of label of the entity. Undoing will revert its label - Label(hecs::Entity, String, EntityType), - /// Removing a component. Undoing will add back the component. - RemoveComponent(hecs::Entity, Box<ComponentType>), - #[allow(dead_code)] - CameraAction(UndoableCameraAction), - RemoveStartingCamera(hecs::Entity), -} - -#[derive(Debug)] -#[allow(dead_code)] -// todo: deal with why there is no Camera -pub enum EntityType { - Entity, - Light, - Camera, + Label(hecs::Entity, String), + RemoveStartingCamera(Entity), } impl UndoableAction { @@ -1579,150 +1475,14 @@ impl UndoableAction { Err(anyhow::anyhow!("Failed to despawn entity {:?}", entity)) } } - UndoableAction::Label(entity, original_label, entity_type) => match entity_type { - EntityType::Entity => { - let mut updated = false; - - if let Ok(mut label_query) = world.query_one::<&mut Label>(*entity) { - if let Some(label_component) = label_query.get() { - label_component.set(original_label.clone()); - updated = true; - } - } - - if let Ok(mut renderer_query) = world.query_one::<&mut MeshRenderer>(*entity) { - if let Some(renderer) = renderer_query.get() { - renderer.make_model_mut().label = original_label.clone(); - updated = true; - } - } - - if updated { - log::debug!( - "Reverted label for entity {:?} to '{}'", - entity, - original_label - ); - Ok(()) - } else { - Err(anyhow::anyhow!( - "Unable to query the entity for label revert" - )) - } - } - EntityType::Light => { - let mut updated = false; - - if let Ok(mut label_query) = world.query_one::<&mut Label>(*entity) { - if let Some(label_component) = label_query.get() { - label_component.set(original_label.clone()); - updated = true; - } - } - - if let Ok(mut q) = world.query_one::<&mut Light>(*entity) { - if let Some(adopted) = q.get() { - adopted.label = original_label.clone(); - updated = true; - } - } - - if updated { - log::debug!( - "Reverted label for light {:?} to '{}'", - entity, - original_label - ); - Ok(()) - } else { - Err(anyhow::anyhow!( - "Could not find a light to query for label revert" - )) - } - } - EntityType::Camera => { - let mut updated = false; - - if let Ok(mut label_query) = world.query_one::<&mut Label>(*entity) { - if let Some(label_component) = label_query.get() { - label_component.set(original_label.clone()); - updated = true; - } - } - - if let Ok(mut q) = world.query_one::<&mut Camera>(*entity) { - if let Some(adopted) = q.get() { - adopted.label = original_label.clone(); - updated = true; - } - } - - if updated { - log::debug!( - "Reverted label for camera {:?} to '{}'", - entity, - original_label - ); - Ok(()) - } else { - Err(anyhow::anyhow!( - "Could not find a camera to query for label revert" - )) - } + UndoableAction::Label(entity, original_label) => { + if let Ok(label) = world.query_one_mut::<&mut Label>(*entity) { + label.set(original_label.clone()); + Ok(()) + } else { + anyhow::bail!("No entity found (with or without the Label property)"); } }, - UndoableAction::RemoveComponent(entity, c_type) => { - match &**c_type { - ComponentType::Script(component) => { - world.insert_one(*entity, component.clone())?; - } - ComponentType::Camera(camera, component) => { - world.insert(*entity, (camera.clone(), component.clone()))?; - } - } - Ok(()) - } - UndoableAction::CameraAction(action) => { - match action { - UndoableCameraAction::Speed(entity, speed) => { - if let Ok(mut q) = - world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity) - && let Some((cam, comp)) = q.get() - { - comp.settings.speed = *speed; - comp.update(cam); - } - } - UndoableCameraAction::Sensitivity(entity, sensitivity) => { - if let Ok(mut q) = - world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity) - && let Some((cam, comp)) = q.get() - { - comp.settings.sensitivity = *sensitivity; - comp.update(cam); - } - } - UndoableCameraAction::Fov(entity, fov) => { - if let Ok(mut q) = - world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity) - && let Some((cam, comp)) = q.get() - { - comp.settings.fov_y = *fov; - comp.update(cam); - } - } - UndoableCameraAction::Type(entity, camera_type) => { - if let Ok(mut q) = - world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity) - && let Some((cam, comp)) = q.get() - { - comp.camera_type = *camera_type; - comp.update(cam); - } - } - }; - Ok(()) - } UndoableAction::RemoveStartingCamera(old) => { for (_i, comp) in &mut world.query::<&mut CameraComponent>() { comp.starting_camera = false; @@ -1747,36 +1507,23 @@ pub enum Signal { Paste(SceneEntity), Delete, Undo, - // ScriptAction(ScriptAction), - // not actions required because follow target is set through scripting. - // CameraAction(CameraAction), Play, StopPlaying, - AddComponent(hecs::Entity, EntityType), - RemoveComponent(hecs::Entity, Box<ComponentType>), CreateEntity, LogEntities, Spawn(PendingSpawnType), } -#[derive(Debug)] -#[allow(dead_code)] -// todo: deal with the Camera and create an implementation -pub enum ComponentType { - Script(ScriptComponent), - Camera(Box<Camera>, CameraComponent), -} - #[derive(Clone)] pub struct PlayModeBackup { entities: Vec<( - hecs::Entity, + Entity, MeshRenderer, Transform, ModelProperties, Option<ScriptComponent>, )>, - camera_data: Vec<(hecs::Entity, Camera, CameraComponent)>, + camera_data: Vec<(Entity, Camera, CameraComponent)>, } #[derive(Debug)] @@ -1819,13 +1566,6 @@ impl IsWorldLoadedYet { self.project_loaded && self.scene_loaded } - // I don't know whether this should be kept or removed, but - // im adding dead code just in case. - #[allow(dead_code)] - pub fn is_project_ready(&self) -> bool { - self.project_loaded - } - pub fn mark_project_loaded(&mut self) { self.project_loaded = true; } @@ -1,4 +1,5 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +// #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +// note to self: when it becomes release, remember to readd this back use clap::{Arg, Command}; use dropbear_engine::future::FutureQueue; @@ -22,7 +23,7 @@ async fn main() -> anyhow::Result<()> { app_dirs2::app_root(app_dirs2::AppDataType::UserData, &eucalyptus_core::APP_INFO) .expect("Failed to get app data directory") .join("logs"); - std::fs::create_dir_all(&log_dir).expect("Failed to create log dir"); + fs::create_dir_all(&log_dir).expect("Failed to create log dir"); let datetime_str = chrono::offset::Local::now().format("%Y-%m-%d_%H-%M-%S"); let log_filename = format!("{}.{}.log", "eucalyptus-editor", datetime_str); @@ -1,4 +1,4 @@ -use anyhow::{Context, anyhow}; +use anyhow::{anyhow, Context}; use dropbear_engine::{ future::{FutureHandle, FutureQueue}, graphics::RenderContext, @@ -7,7 +7,7 @@ use dropbear_engine::{ }; use egui::{self, FontId, Frame, RichText}; use egui_toast::{ToastOptions, Toasts}; -use eucalyptus_core::states::{PROJECT, ProjectConfig}; +use eucalyptus_core::states::PROJECT; use git2::Repository; use log::{self, debug}; use rfd::FileDialog; @@ -17,6 +17,7 @@ use tokio::sync::watch; use winit::{ dpi::PhysicalPosition, event::MouseButton, event_loop::ActiveEventLoop, keyboard::KeyCode, }; +use eucalyptus_core::config::ProjectConfig; #[derive(Debug, Clone)] pub enum ProjectProgress { @@ -1,21 +1,23 @@ use crate::editor::{ - ComponentType, Editor, EditorState, EntityType, PendingSpawnType, Signal, UndoableAction, + Editor, EditorState, PendingSpawnType, Signal, }; use dropbear_engine::camera::Camera; -use dropbear_engine::entity::{MeshRenderer, Transform}; +use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform}; use dropbear_engine::graphics::SharedGraphicsContext; use dropbear_engine::lighting::{Light, LightComponent}; use dropbear_engine::utils::{ResourceReference, ResourceReferenceType}; -use egui::{Align2, Image}; +use egui::{Align2}; 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::{EditorTab, Label, ModelProperties, PROJECT, ScriptComponent, Value}; +use eucalyptus_core::states::{EditorTab, Label, ModelProperties, PROJECT, ScriptComponent, SerializedMeshRenderer}; use eucalyptus_core::{fatal, info, success, success_without_console, warn, warn_without_console}; use std::any::TypeId; use std::path::PathBuf; use std::sync::Arc; use winit::keyboard::KeyCode; +use eucalyptus_core::scene::SceneEntity; +use eucalyptus_core::traits::SerializableComponent; pub trait SignalController { fn run_signal(&mut self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<()>; @@ -23,10 +25,8 @@ pub trait SignalController { impl SignalController for Editor { fn run_signal(&mut self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<()> { - let mut local_insert_script = false; - let mut local_insert_camera = (false, String::new()); - let mut local_signal: Option<Signal> = None; - let mut show = true; + let local_signal: Option<Signal> = None; + let show = true; match &self.signal { Signal::None => { @@ -36,10 +36,7 @@ impl SignalController for Editor { Signal::Copy(_) => Ok(()), Signal::Paste(scene_entity) => { let spawn = PendingSpawn { - asset_path: scene_entity.model_path.clone(), - asset_name: scene_entity.label.to_string(), - transform: scene_entity.transform, - properties: scene_entity.properties.clone(), + scene_entity: scene_entity.clone(), handle: None, }; push_pending_spawn(spawn); @@ -442,213 +439,6 @@ impl SignalController for Editor { self.signal = Signal::None; Ok(()) } - Signal::AddComponent(entity, e_type) => { - match e_type { - EntityType::Entity => { - if let Ok(mut q) = self.world.query_one::<(&MeshRenderer, &Label)>(*entity) - { - if let Some((_renderer, label)) = q.get() { - let label_text = label.to_string(); - egui::Window::new(format!("Add component for {}", label_text)) - .title_bar(true) - .open(&mut show) - .scroll([false, true]) - .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) - .enabled(true) - .show(&graphics.get_egui_context(), |ui| { - if ui - .add_sized( - [ui.available_width(), 30.0], - egui::Button::new("Scripting"), - ) - .clicked() - { - log::debug!( - "Adding scripting component to entity [{}]", - label_text - ); - local_insert_script = true; - local_signal = Some(Signal::None); - } - if ui - .add_sized( - [ui.available_width(), 30.0], - egui::Button::new("Camera"), - ) - .clicked() - { - log::debug!( - "Adding camera component to entity [{}]", - label_text - ); - - local_insert_camera = (true, label_text.clone()); - local_signal = Some(Signal::None); - } - }); - } - } else { - log_once::warn_once!( - "Failed to add component to entity: no entity component found" - ); - } - if local_insert_script { - if let Err(e) = - self.world.insert_one(*entity, ScriptComponent::default()) - { - warn!("Failed to add scripting component to entity: {}", e); - } else { - success!("Added the scripting component"); - } - } - - if local_insert_camera.0 { - let camera = Camera::predetermined( - graphics.clone(), - Some(&format!("{} Camera", local_insert_camera.1)), - ); - let component = CameraComponent::new(); - if let Err(e) = self.world.insert(*entity, (camera, component)) { - warn!("Failed to add camera component to entity: {}", e); - } else { - success!("Added the camera component"); - } - } - Ok(()) - } - EntityType::Light => { - if let Ok(mut q) = self.world.query_one::<&Light>(*entity) { - if let Some(light) = q.get() { - let mut show = true; - egui::Window::new(format!("Add component for {}", light.label)) - .scroll([false, true]) - .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) - .enabled(true) - .open(&mut show) - .title_bar(true) - .show(&graphics.get_egui_context(), |ui| { - if ui - .add_sized( - [ui.available_width(), 30.0], - egui::Button::new("Scripting"), - ) - .clicked() - { - log::debug!( - "Adding scripting component to light [{}]", - light.label - ); - - log::warn!("Its not really added, it's just a dummy button. To be implemented..."); - - success!( - "Added the scripting component to light [{}]", - light.label - ); - self.signal = Signal::None; - } - }); - if !show { - self.signal = Signal::None; - } - } - Ok(()) - } else { - log_once::warn_once!( - "Failed to add component to light: no light component found" - ); - Ok(()) - } - } - EntityType::Camera => { - { - if let Ok(mut q) = - self.world.query_one::<(&Camera, &CameraComponent)>(*entity) - { - if let Some((cam, _comp)) = q.get() { - let mut show = true; - egui::Window::new(format!("Add component for {}", cam.label)) - .scroll([false, true]) - .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) - .enabled(true) - .open(&mut show) - .title_bar(true) - .show(&graphics.get_egui_context(), |ui| { - egui_extras::install_image_loaders(ui.ctx()); - ui.add(Image::from_bytes( - "bytes://theres_nothing.jpg", - include_bytes!( - "../../resources/textures/theres_nothing.jpg" - ), - )); - ui.label("Theres nothing..."); - // scripting could be planned??? - // if ui.add_sized([ui.available_width(), 30.0], egui::Button::new("Scripting")).clicked() { - // log::debug!("Adding scripting component to camera [{}]", cam.label); - - // success!("Added the scripting component to camera [{}]", cam.label); - // self.signal = Signal::None; - // } - }); - if !show { - self.signal = Signal::None; - } - } - Ok(()) - } else { - log_once::warn_once!( - "Failed to add component to light: no light component found" - ); - Ok(()) - } - } - } - } - } - Signal::RemoveComponent(entity, c_type) => match &**c_type { - ComponentType::Script(_) => { - match self.world.remove_one::<ScriptComponent>(*entity) { - Ok(component) => { - success!("Removed script component from entity {:?}", entity); - UndoableAction::push_to_undo( - &mut self.undo_stack, - UndoableAction::RemoveComponent( - *entity, - Box::new(ComponentType::Script(component)), - ), - ); - } - Err(e) => { - warn!("Failed to remove script component from entity: {}", e); - } - }; - self.signal = Signal::None; - Ok(()) - } - ComponentType::Camera(_, _) => { - match self.world.remove::<(Camera, CameraComponent)>(*entity) { - Ok(component) => { - success!("Removed camera component from entity {:?}", entity); - UndoableAction::push_to_undo( - &mut self.undo_stack, - UndoableAction::RemoveComponent( - *entity, - Box::new(ComponentType::Camera( - Box::new(component.0), - component.1, - )), - ), - ); - } - Err(e) => { - warn!("Failed to remove script component from entity: {}", e); - self.signal = Signal::None; - } - }; - self.signal = Signal::None; - Ok(()) - } - }, Signal::CreateEntity => { let mut show = true; egui::Window::new("Add Entity") @@ -768,7 +558,7 @@ impl SignalController for Editor { } Signal::Spawn(entity_type) => { match entity_type { - crate::editor::PendingSpawnType::Light => { + PendingSpawnType::Light => { let light = Light::new( graphics.clone(), LightComponent::default(), @@ -779,33 +569,42 @@ impl SignalController for Editor { self.light_spawn_queue.push(handle); success!("Pushed light to queue"); } - crate::editor::PendingSpawnType::Plane => { - let transform = Transform::new(); - let mut props = ModelProperties::new(); - props.add_property("width".to_string(), Value::Float(500.0)); - props.add_property("height".to_string(), Value::Float(200.0)); - props.add_property("tiles_x".to_string(), Value::Int(500)); - props.add_property("tiles_z".to_string(), Value::Int(200)); - - push_pending_spawn(PendingSpawn { - asset_path: ResourceReference::from_reference( - ResourceReferenceType::Plane, - ), - asset_name: "DefaultPlane".to_string(), - transform, - properties: props, - handle: None, - }); - success!("Pushed plane to queue"); + PendingSpawnType::Plane => { + fatal!("Plane spawning is not yet supported, sorry! (system being remade)"); + // let transform = Transform::new(); + // let mut props = ModelProperties::new(); + // props.add_property("width".to_string(), Value::Float(500.0)); + // props.add_property("height".to_string(), Value::Float(200.0)); + // props.add_property("tiles_x".to_string(), Value::Int(500)); + // props.add_property("tiles_z".to_string(), Value::Int(200)); + // + // push_pending_spawn(PendingSpawn { + // asset_path: ResourceReference::from_reference( + // ResourceReferenceType::Plane, + // ), + // asset_name: "DefaultPlane".to_string(), + // transform, + // properties: props, + // handle: None, + // }); + // success!("Pushed plane to queue"); } PendingSpawnType::Cube => { + let mut components: Vec<Box<dyn SerializableComponent>> = + Vec::new(); + components.push(Box::new(EntityTransform::default())); + components.push(Box::new(SerializedMeshRenderer { + handle: ResourceReference::from_reference(ResourceReferenceType::Cube), + material_override: Vec::new(), + })); + components.push(Box::new(ModelProperties::new())); + let pending = PendingSpawn { - asset_path: ResourceReference::from_bytes(include_bytes!( - "../../resources/models/cube.glb" - )), - asset_name: "Default Cube".to_string(), - transform: Default::default(), - properties: Default::default(), + scene_entity: SceneEntity { + label: Label::from("Cube"), + components, + entity_id: None, + }, handle: None, }; push_pending_spawn(pending); @@ -1,16 +1,29 @@ use crate::editor::Editor; -use dropbear_engine::entity::MeshRenderer; +use dropbear_engine::asset::ASSET_REGISTRY; +use dropbear_engine::entity::{EntityTransform, MeshRenderer}; use dropbear_engine::future::FutureQueue; use dropbear_engine::graphics::SharedGraphicsContext; use dropbear_engine::model::Model; -use dropbear_engine::procedural::plane::PlaneBuilder; -use dropbear_engine::utils::{ResourceReference, ResourceReferenceType}; +use dropbear_engine::utils::ResourceReferenceType; pub(crate) use eucalyptus_core::spawn::{PENDING_SPAWNS, PendingSpawnController}; -use eucalyptus_core::states::{Label, PROJECT, Value}; -use eucalyptus_core::utils::PROTO_TEXTURE; +use eucalyptus_core::scene::SceneEntity; +use eucalyptus_core::states::{ModelProperties, SerializedMeshRenderer, ScriptComponent}; +use eucalyptus_core::utils::ResolveReference; use eucalyptus_core::{fatal, success}; +use hecs::EntityBuilder; use std::sync::Arc; +fn component_ref<'a, T: 'static>(entity: &'a SceneEntity) -> Option<&'a T> { + entity + .components + .iter() + .find_map(|component| component.as_any().downcast_ref::<T>()) +} + +fn component_cloned<T: Clone + 'static>(entity: &SceneEntity) -> Option<T> { + component_ref::<T>(entity).cloned() +} + impl PendingSpawnController for Editor { fn check_up( &mut self, @@ -19,144 +32,190 @@ impl PendingSpawnController for Editor { ) -> anyhow::Result<()> { queue.poll(); let mut spawn_list = PENDING_SPAWNS.lock(); - let mut completed = Vec::new(); - for (i, spawn) in spawn_list.iter_mut().enumerate() { + for (index, spawn) in spawn_list.iter_mut().enumerate() { log_once::debug_once!( - "Caught pending spawn! Info: {} of type {}", - spawn.asset_name, - spawn.asset_path + "Processing pending spawn for '{}'", + spawn.scene_entity.label ); - if spawn.handle.is_none() { - log_once::debug_once!("Pending spawn does NOT have a handle, creating new one now"); - let graphics_clone = graphics.clone(); - let asset_name = spawn.asset_name.clone(); - let asset_path = spawn.asset_path.ref_type.clone(); - - let func = async move { - match asset_path { - ResourceReferenceType::None => { - Err(anyhow::anyhow!("No asset path available")) - } - ResourceReferenceType::File(file) => { - let path = { - let _guard = PROJECT.read(); - _guard.project_path.clone() - }; - let relative = ResourceReference::relative_path_from_uri(&file)?; - let resource = path.join("resources").join(relative); - MeshRenderer::from_path(graphics_clone, resource, Some(&asset_name)) - .await - } - ResourceReferenceType::Bytes(bytes) => { - let model = Model::load_from_memory( - graphics_clone.clone(), - &bytes, - Some(&asset_name), - ) - .await?; - Ok(MeshRenderer::from_handle(model)) - } - ResourceReferenceType::Plane => { - anyhow::bail!("Unable to load plane: Not supported anymore, rebuilding it"); - // let get_float = |key: &str| -> anyhow::Result<f32> { - // let val = properties - // .custom_properties - // .iter() - // .find(|p| p.key == key) - // .ok_or_else(|| { - // anyhow::anyhow!("Entity has no {} property", key) - // })?; - // match val.value { - // Value::Float(f) => Ok(f as f32), - // _ => Err(anyhow::anyhow!("{} is not a float", key)), - // } - // }; - // - // let get_int = |key: &str| -> anyhow::Result<u32> { - // let val = properties - // .custom_properties - // .iter() - // .find(|p| p.key == key) - // .ok_or_else(|| { - // anyhow::anyhow!("Entity has no {} property", key) - // })?; - // match val.value { - // Value::Int(i) => Ok(i as u32), - // _ => Err(anyhow::anyhow!("{} is not an int", key)), - // } - // }; - // - // let width = get_float("width")?; - // let height = get_float("height")?; - // let tiles_x = get_int("tiles_x")?; - // let tiles_z = get_int("tiles_z")?; - // - // PlaneBuilder::new() - // .with_size(width, height) - // .with_tiles(tiles_x, tiles_z) - // .build(graphics_clone, PROTO_TEXTURE, Some(&asset_name)) - // .await - } - ResourceReferenceType::Cube => { - Model::load_from_memory(graphics_clone, include_bytes!("../../resources/models/cube.glb"), Some(&asset_name)).await.map(MeshRenderer::from_handle) - } - } - }; - let handle = queue.push(Box::pin(func)); - spawn.handle = Some(handle); - } else { - log_once::debug_once!("Spawn does have handle, using that one"); + let serialized_renderer = component_cloned::<SerializedMeshRenderer>(&spawn.scene_entity); + + if serialized_renderer.is_none() && spawn.handle.is_none() { + log::debug!( + "No renderer component found for '{}', spawning immediately", + spawn.scene_entity.label + ); + self.spawn_scene_entity(&spawn.scene_entity, None); + completed.push(index); + continue; + } + + if spawn.handle.is_none() { + if let Some(renderer) = serialized_renderer.clone() { + let graphics_clone = graphics.clone(); + let label = spawn.scene_entity.label.to_string(); + let future = async move { + load_renderer_from_serialized(renderer, graphics_clone, label).await + }; + let handle = queue.push(Box::pin(future)); + spawn.handle = Some(handle); + } } if let Some(handle) = &spawn.handle { - 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<MeshRenderer>>() { - log_once::debug_once!("Result has been successfully downcasted"); match Arc::try_unwrap(r) { - Ok(entity) => match entity { - Ok(entity) => { - log::debug!("Entity loaded"); - self.world.spawn(( - Label::from(spawn.asset_name.clone()), - entity, - spawn.transform, - spawn.properties.clone(), - )); - success!("Spawned entity successfully"); - completed.push(i); + Ok(outcome) => match outcome { + Ok(renderer) => { + self.spawn_scene_entity(&spawn.scene_entity, Some(renderer)); + success!( + "Spawned '{}' from pending queue", + spawn.scene_entity.label + ); + completed.push(index); } - Err(e) => { - fatal!("Unable to load model: {}", e); - completed.push(i); + Err(err) => { + fatal!("Unable to load mesh renderer: {}", err); + completed.push(index); } }, Err(_) => { - return { - log_once::warn_once!("Cannot unwrap Arc result"); - completed.push(i); - Ok(()) - }; + log_once::warn_once!( + "Renderer future for '{}' still shared, deferring", + spawn.scene_entity.label + ); } } + } else { + fatal!( + "Future result for '{}' could not be downcasted", + spawn.scene_entity.label + ); + completed.push(index); } - } else { - log_once::debug_once!("Handle exchanging failed, probably not ready yet"); } - } else { - log_once::debug_once!("Spawn has no handle"); } } for &i in completed.iter().rev() { - log_once::debug_once!("Removing item {} from pending spawn list", i); spawn_list.remove(i); } Ok(()) } } + +impl Editor { + fn spawn_scene_entity( + &mut self, + scene_entity: &SceneEntity, + mesh_renderer: Option<MeshRenderer>, + ) { + let mut builder = EntityBuilder::new(); + builder.add(scene_entity.label.clone()); + + if let Some(transform) = component_ref::<EntityTransform>(scene_entity).copied() { + builder.add(transform); + } else { + builder.add(EntityTransform::default()); + } + + if let Some(renderer) = mesh_renderer { + builder.add(renderer); + } + + if let Some(props) = component_cloned::<ModelProperties>(scene_entity) { + builder.add(props); + } + + if let Some(script) = component_cloned::<ScriptComponent>(scene_entity) { + builder.add(script); + } + + self.world.spawn(builder.build()); + } +} + +async fn load_renderer_from_serialized( + renderer: SerializedMeshRenderer, + graphics: Arc<SharedGraphicsContext>, + label: String, +) -> anyhow::Result<MeshRenderer> { + let mut mesh_renderer = match &renderer.handle.ref_type { + ResourceReferenceType::None => anyhow::bail!( + "Renderer for '{}' does not specify an asset reference", + label + ), + ResourceReferenceType::File(_) => { + let path = renderer.handle.resolve()?; + MeshRenderer::from_path(graphics.clone(), &path, Some(&label)).await? + } + ResourceReferenceType::Bytes(bytes) => { + let model = Model::load_from_memory(graphics.clone(), bytes.clone(), Some(&label)).await?; + MeshRenderer::from_handle(model) + } + ResourceReferenceType::Plane => { + anyhow::bail!("Procedural planes are not supported in pending spawns yet"); + } + ResourceReferenceType::Cube => { + let model = Model::load_from_memory( + graphics.clone(), + include_bytes!("../../resources/models/cube.glb"), + Some(&label), + ) + .await?; + MeshRenderer::from_handle(model) + } + }; + + for override_entry in renderer.material_override { + if ASSET_REGISTRY + .model_handle_from_reference(&override_entry.source_model) + .is_none() + { + if matches!( + override_entry.source_model.ref_type, + ResourceReferenceType::File(_) + ) { + let source_path = override_entry.source_model.resolve()?; + let label_hint = override_entry.source_model.as_uri(); + if let Err(err) = + Model::load(graphics.clone(), &source_path, label_hint).await + { + log::warn!( + "Failed to preload source model {:?} for override '{}': {}", + override_entry.source_model, + override_entry.target_material, + err + ); + continue; + } + } else { + log::warn!( + "Unsupported override source {:?} for '{}'", + override_entry.source_model, + label + ); + continue; + } + } + + if let Err(err) = mesh_renderer.apply_material_override( + &override_entry.target_material, + override_entry.source_model.clone(), + &override_entry.source_material, + ) { + log::warn!( + "Failed to apply material override '{}' on '{}': {}", + override_entry.target_material, + label, + err + ); + } + } + + Ok(mesh_renderer) +} @@ -3,13 +3,14 @@ use dropbear_engine::camera::Camera; use dropbear_engine::scene::SceneCommand; use egui::Context; use egui_toast::{Toast, ToastOptions, Toasts}; -use eucalyptus_core::states::{PROJECT, ProjectConfig}; +use eucalyptus_core::states::PROJECT; use eucalyptus_core::utils::ProjectProgress; use git2::Repository; use std::fs; use std::path::PathBuf; use std::sync::mpsc; use std::sync::mpsc::Receiver; +use eucalyptus_core::config::ProjectConfig; pub fn show_new_project_window<F>( ctx: &Context, @@ -26,6 +26,11 @@ typedef struct { } Vector3D; typedef struct { + NativeTransform local; + NativeTransform world; +} NativeEntityTransform; + +typedef struct { double position_x; double position_y; double position_z; @@ -61,28 +66,16 @@ typedef struct { int dropbear_get_entity(const char* label, const World* world_ptr, int64_t* out_entity); -int dropbear_get_world_transform( - const World* world_ptr, - int64_t entity_id, - NativeTransform* out_transform -); - -int dropbear_commit_world_transform( - const World* world_ptr, - int64_t entity_id, - const NativeTransform transform -); - -int dropbear_get_local_transform( +int dropbear_get_transform( const World* world_ptr, int64_t entity_id, - NativeTransform* out_transform + NativeEntityTransform* out_transform ); -int dropbear_commit_local_transform( +int dropbear_set_transform( const World* world_ptr, int64_t entity_id, - const NativeTransform transform + const NativeEntityTransform transform ); // property management @@ -30,7 +30,9 @@ class EntityRef(val id: EntityId = EntityId(0L)) { } /** - * Walks up the hierarchy to find the transform of the parent, then multiply to create a propagated [Transform]. + * Walks up the world hierarchy to find the transform of the parent, then multiply + * to create a propagated [Transform]. + * * This will update the entity's world transform based on its parent's world transform. */ fun propagate() {