tirbofish/dropbear · diff
wip: made a way more neater component registry that actually doesnt suck ass (so far). i cbb to do the rest tho.
Signature present but could not be verified.
Unverified
@@ -86,6 +86,7 @@ puffin = "0.19" bitflags = "2.10" features = "0.10" puffin_http = "0.16" +dyn-clone = "1.0" [workspace.dependencies.image] version = "0.24" @@ -10,7 +10,6 @@ readme.workspace = true [dependencies] dropbear_future-queue = { path = "../dropbear_future-queue" } -dropbear-traits = { path = "../dropbear-traits" } dropbear-macro = { path = "../dropbear-macro" } slank = { path = "../slank", features = ["download-slang", "use-wgpu"] } @@ -1,14 +1,9 @@ -use dropbear_traits::{Component, ComponentDescriptor, ComponentInitContext, ComponentInitFuture, ComponentUpdateContext, InsertBundle, SerializableComponent}; use std::collections::HashMap; use std::sync::Arc; -use egui::{CollapsingHeader, Ui}; use glam::Mat4; use wgpu::util::DeviceExt; -use crate::asset::ASSET_REGISTRY; -use crate::entity::MeshRenderer; use crate::graphics::SharedGraphicsContext; use crate::model::{AnimationInterpolation, ChannelValues, Model, NodeTransform}; -use std::any::Any; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct AnimationComponent { @@ -34,72 +29,6 @@ pub struct AnimationComponent { pub bind_group: Option<wgpu::BindGroup>, } -impl Component for AnimationComponent { - type Serialized = AnimationComponent; - - fn static_descriptor() -> ComponentDescriptor { - ComponentDescriptor { - fqtn: "dropbear_engine::animation::AnimationComponent".to_string(), - type_name: "AnimationComponent".to_string(), - category: Some("Animation".to_string()), - description: Some("A component that allows playback of a component".to_string()), - } - } - - fn deserialize(serialized: &Self::Serialized) -> Self { - serialized.clone() - } - - fn serialize(&self) -> Self::Serialized { - self.clone() - } - - fn inspect(&mut self, ui: &mut Ui) { - CollapsingHeader::new("Animation").default_open(true).show(ui, |ui| { - ui.label("Active animation:"); - let mut enabled = self.active_animation_index.is_some(); - let mut value = self.active_animation_index.unwrap_or(0); - - ui.horizontal(|ui| { - if ui.checkbox(&mut enabled, "Enable").changed() { - self.active_animation_index = if enabled { Some(value) } else { None }; - } - - ui.add_enabled(enabled, egui::DragValue::new(&mut value)); - - if enabled && self.active_animation_index != Some(value) { - self.active_animation_index = Some(value); - } - }); - - // todo: complete some more - }); - } - - fn update(&mut self, ctx: &mut ComponentUpdateContext) { - let world = ctx.world(); - let Ok(renderer) = world.get::<&MeshRenderer>(ctx.entity) else { - return; - }; - - let handle = renderer.model(); - if handle.is_null() { - return; - } - - let registry = ASSET_REGISTRY.read(); - let Some(model) = registry.get_model(handle) else { - return; - }; - - self.update(ctx.dt, model); - - if let Some(graphics) = ctx.resources.get::<Arc<SharedGraphicsContext>>() { - self.prepare_gpu_resources(graphics.clone()); - } - } -} - impl Default for AnimationComponent { fn default() -> Self { Self { @@ -116,26 +45,6 @@ impl Default for AnimationComponent { } } -#[typetag::serde] -impl SerializableComponent for AnimationComponent { - fn as_any(&self) -> &dyn Any { - self - } - - fn clone_box(&self) -> Box<dyn SerializableComponent> { - Box::new(self.clone()) - } - - fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((value,))); - Ok(insert) - }) - } -} - impl AnimationComponent { pub fn new() -> Self { Self::default() @@ -40,6 +40,9 @@ impl<T> Clone for Handle<T> { impl<T> Handle<T> { /// Creates a null handle, for when there is no way to uniquely identify a hash (such as a viewport texture). /// + /// A NULL model would be a model with no vertices or any properties, just a husk. + /// A NULL texture would throw an error, as it is not possible. + /// /// # Safety /// You will want to watch out, as adding this onto the asset registry with a type /// where there already is a null handle item, it will be overwritten and data @@ -77,12 +80,24 @@ pub enum AssetKind { /// Common impl AssetRegistry { pub fn new() -> Self { - Self { + let mut result = Self { textures: Default::default(), texture_labels: Default::default(), models: Default::default(), model_labels: Default::default(), - } + }; + + result.add_model(Model { + hash: 0, + label: "null".to_string(), + path: Default::default(), + meshes: vec![], + materials: vec![], + skins: vec![], + animations: vec![], + nodes: vec![], + }); + result } /// A convenient helper function for hashing a byte slice of data. @@ -16,9 +16,9 @@ use crate::{ }; use anyhow::anyhow; use egui::{CollapsingHeader, Ui}; -use dropbear_traits::{Component, ComponentDescriptor, ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent}; use std::any::Any; use crate::asset::Handle; +use crate::model::Material; /// A type of transform that is attached to all entities. It contains the local and world transforms. #[derive(Default, Debug, Deserialize, Serialize, Copy, PartialEq, Clone)] @@ -78,71 +78,6 @@ impl EntityTransform { } } -impl Component for EntityTransform { - type Serialized = EntityTransform; - - fn static_descriptor() -> ComponentDescriptor { - ComponentDescriptor { - fqtn: "dropbear_engine::entity::EntityTransform".to_string(), - type_name: "EntityTransform".to_string(), - category: Some("Transform".to_string()), - description: Some("A component that allows the entity to be transformed both locally and globally".to_string()), - } - } - - fn deserialize(serialized: &Self::Serialized) -> Self { - serialized.clone() - } - - fn serialize(&self) -> Self::Serialized { - self.clone() - } - - fn inspect(&mut self, ui: &mut Ui) { - CollapsingHeader::new("Entity Transform") - .default_open(true) - .show(ui, |ui| { - ui.set_min_width(300.0); - - ui.group(|ui| { - ui.strong("Local Transform"); - ui.add_space(4.0); - - self.local.inspect(ui); - }); - - ui.add_space(8.0); - - ui.group(|ui| { - ui.strong("World Transform"); - ui.add_space(4.0); - - self.world.inspect(ui); - }); - }); - } -} - -#[typetag::serde] -impl SerializableComponent for EntityTransform { - fn as_any(&self) -> &dyn Any { - self - } - - fn clone_box(&self) -> Box<dyn SerializableComponent> { - Box::new(*self) - } - - fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture { - let value = *self; - Box::pin(async move { - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((value,))); - Ok(insert) - }) - } -} - /// A type that represents a position, rotation and scale of an entity /// /// This type is the most primitive model, as it implements most traits. @@ -226,7 +161,7 @@ impl Transform { self.scale *= scale; } - fn inspect(&mut self, ui: &mut Ui) { + pub fn inspect(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { ui.label("Position:"); }); @@ -351,18 +286,29 @@ pub struct MeshRenderer { handle: Handle<Model>, pub instance: Instance, previous_matrix: DMat4, - texture_override: Option<Handle<Texture>>, + pub material_snapshot: HashMap<String, Material> } impl MeshRenderer { pub fn from_handle(model: Handle<Model>) -> Self { + let mut hm = HashMap::new(); + let material_snapshot = ASSET_REGISTRY + .read() + .get_model(model) + .map(|m| { + m.materials.clone() + }) + .unwrap_or_default(); + for m in material_snapshot { + hm.insert(m.name.clone(), m); + } Self { handle: model, instance: Instance::default(), previous_matrix: DMat4::IDENTITY, import_scale: 1.0, - texture_override: None, is_selected: false, + material_snapshot: hm, } } @@ -383,8 +329,8 @@ impl MeshRenderer { instance: Instance::default(), import_scale: 1.0, previous_matrix: DMat4::IDENTITY, - texture_override: None, is_selected: false, + material_snapshot: Default::default(), }) } @@ -417,15 +363,11 @@ impl MeshRenderer { pub fn model(&self) -> Handle<Model> { self.handle } - - pub fn set_texture_override(&mut self, texture: Handle<Texture>) { - self.texture_override = Some(texture); - } - - pub fn texture_override(&self) -> Option<Handle<Texture>> { - self.texture_override + + pub fn mutate_material(&mut self, material_name: &str, f: impl FnOnce(&mut Material)) { + self.material_snapshot.entry(material_name.to_string()).and_modify(f); } - + pub fn is_texture_attached(&self, texture: Handle<Texture>) -> bool { let registry = ASSET_REGISTRY.read(); @@ -459,82 +401,18 @@ impl MeshRenderer { } pub fn reset_texture_override(&mut self) { - self.texture_override = None; - } -} - -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct SerializedMeshRenderer { - pub handle: ResourceReference, - pub import_scale: Option<f32>, - pub texture_override: Option<ResourceReference>, -} - -impl Component for MeshRenderer { - type Serialized = SerializedMeshRenderer; - - fn static_descriptor() -> ComponentDescriptor { - ComponentDescriptor { - fqtn: "dropbear_engine::entity::MeshRenderer".to_string(), - type_name: "MeshRenderer".to_string(), - category: Some("Meshes".to_string()), - description: Some("Renders a 3D model".to_string()), - } - } - - fn deserialize(serialized: &Self::Serialized) -> Self { - let handle = match serialized.handle.ref_type { - ResourceReferenceType::Unassigned { id } => Handle::new(id), - _ => Handle::NULL, - }; - - let mut renderer = MeshRenderer::from_handle(handle); - if let Some(scale) = serialized.import_scale { - renderer.set_import_scale(scale); - } - - renderer - } - - fn serialize(&self) -> Self::Serialized { - let handle = self.model(); - let handle_ref = if handle.is_null() { - ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id }) - } else { - let registry = ASSET_REGISTRY.read(); - registry - .get_model(handle) - .map(|model| model.path.clone()) - .unwrap_or_else(|| { - ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id }) - }) - }; - - let texture_override = self.texture_override().map(|handle| { - let registry = ASSET_REGISTRY.read(); - let label = registry.get_label_from_texture_handle(handle); - let reference = label.and_then(|value| { - if value.starts_with(EUCA_SCHEME) { - Some(ResourceReference::from_reference(ResourceReferenceType::File(value))) - } else { - None - } - }); - - reference.unwrap_or_else(|| { - ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id }) + let mut hm = HashMap::new(); + let material_snapshot = ASSET_REGISTRY + .read() + .get_model(self.handle) + .map(|m| { + m.materials.clone() }) - }); - - SerializedMeshRenderer { - handle: handle_ref, - import_scale: Some(self.import_scale()), - texture_override, + .unwrap_or_default(); + for m in material_snapshot { + hm.insert(m.name.clone(), m); } - } - - fn inspect(&mut self, ui: &mut Ui) { - let _ = ui; + self.material_snapshot = hm; } } @@ -21,7 +21,6 @@ pub mod mipmap; pub mod sky; pub mod features; pub mod animation; -pub use dropbear_traits as component; features! { pub mod build { @@ -1,4 +1,4 @@ -use crate::attenuation::{Attenuation, RANGE_50, ATTENUATION_PRESETS}; +use crate::attenuation::{Attenuation, RANGE_50}; use crate::buffer::{ResizableBuffer, UniformBuffer}; use crate::graphics::SharedGraphicsContext; use crate::pipelines::light_cube::InstanceInput; @@ -9,12 +9,9 @@ use crate::{ use glam::{DMat4, DVec3}; use std::fmt::{Display, Formatter}; use std::sync::Arc; -use egui::{CollapsingHeader, Ui}; use wgpu::{BindGroup}; -use dropbear_traits::{Component, ComponentDescriptor}; use crate::asset::{Handle, ASSET_REGISTRY}; -use crate::model::Material; -use crate::procedural::{ProcObj, ProcedurallyGeneratedObject}; +use crate::procedural::{ProcedurallyGeneratedObject}; pub const MAX_LIGHTS: usize = 10; @@ -178,112 +175,6 @@ impl Default for LightComponent { } } -impl Component for LightComponent { - type Serialized = LightComponent; - - fn static_descriptor() -> ComponentDescriptor { - ComponentDescriptor { - fqtn: "dropbear_engine::lighting::LightComponent".to_string(), - type_name: "LightComponent".to_string(), - category: Some("Lighting".to_string()), - description: Some("Light parameters used by the renderer".to_string()), - } - } - - fn deserialize(serialized: &Self::Serialized) -> Self { - serialized.clone() - } - - fn serialize(&self) -> Self::Serialized { - self.clone() - } - - fn inspect(&mut self, ui: &mut Ui) { - CollapsingHeader::new("Light") - .default_open(true) - .show(ui, |ui| { - egui::ComboBox::from_id_salt("light_type") - .selected_text(self.light_type.to_string()) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.light_type, - LightType::Directional, - "Directional", - ); - ui.selectable_value(&mut self.light_type, LightType::Point, "Point"); - ui.selectable_value(&mut self.light_type, LightType::Spot, "Spot"); - }); - - ui.separator(); - - let mut colour = self.colour.as_vec3().to_array(); - ui.horizontal(|ui| { - ui.label("Colour"); - egui::color_picker::color_edit_button_rgb(ui, &mut colour); - }); - self.colour = glam::Vec3::from_array(colour).as_dvec3(); - - ui.horizontal(|ui| { - ui.label("Intensity"); - ui.add(egui::Slider::new(&mut self.intensity, 0.0..=10.0)); - }); - - ui.horizontal(|ui| { - ui.checkbox(&mut self.enabled, "Enabled"); - ui.checkbox(&mut self.visible, "Visible"); - }); - - let is_point = matches!(self.light_type, LightType::Point); - let is_spot = matches!(self.light_type, LightType::Spot); - - if is_point || is_spot { - ui.separator(); - egui::ComboBox::from_id_salt("attenuation_range") - .selected_text(format!("Range {}", self.attenuation.range)) - .show_ui(ui, |ui| { - for (preset, label) in ATTENUATION_PRESETS { - ui.selectable_value(&mut self.attenuation, *preset, *label); - } - }); - } - - if is_spot { - ui.separator(); - ui.add( - egui::Slider::new(&mut self.cutoff_angle, 1.0..=89.0) - .text("Inner") - .suffix("\u{00B0}") - .step_by(0.1), - ); - ui.add( - egui::Slider::new(&mut self.outer_cutoff_angle, 1.0..=90.0) - .text("Outer") - .suffix("\u{00B0}") - .step_by(0.1), - ); - - if self.outer_cutoff_angle <= self.cutoff_angle { - self.outer_cutoff_angle = self.cutoff_angle + 1.0; - } - } - - ui.separator(); - ui.label("Shadows"); - ui.checkbox(&mut self.cast_shadows, "Cast Shadows"); - ui.horizontal(|ui| { - ui.label("Depth"); - ui.add(egui::DragValue::new(&mut self.depth.start).speed(0.1)); - ui.label(".."); - ui.add(egui::DragValue::new(&mut self.depth.end).speed(0.1)); - }); - - if self.depth.end < self.depth.start { - self.depth.end = self.depth.start; - } - }); - } -} - impl LightComponent { pub fn default_direction() -> DVec3 { let dir = DVec3::new(-0.35, -1.0, -0.25); @@ -402,7 +293,7 @@ impl Light { .build_model( graphics.clone(), None, - None, + Some("light cube"), ASSET_REGISTRY.clone() ); @@ -52,7 +52,7 @@ pub struct Material { pub emissive_factor: [f32; 3], pub metallic_factor: f32, pub roughness_factor: f32, - pub alpha_mode: gltf::material::AlphaMode, + pub alpha_mode: AlphaMode, pub alpha_cutoff: Option<f32>, pub double_sided: bool, pub occlusion_strength: f32, @@ -66,6 +66,24 @@ pub struct Material { pub occlusion_texture: Option<Texture>, } +#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize, Default)] +pub enum AlphaMode { + #[default] + Opaque = 1, + Mask, + Blend, +} + +impl Into<AlphaMode> for gltf::material::AlphaMode { + fn into(self) -> AlphaMode { + match self { + gltf::material::AlphaMode::Opaque => AlphaMode::Opaque, + gltf::material::AlphaMode::Mask => AlphaMode::Mask, + gltf::material::AlphaMode::Blend => AlphaMode::Blend, + } + } +} + /// Represents a node in the scene graph (can be a joint/bone or a mesh) #[derive(Clone, Debug)] pub struct Node { @@ -177,7 +195,7 @@ impl Material { emissive_factor: [0.0, 0.0, 0.0], metallic_factor: 1.0, roughness_factor: 1.0, - alpha_mode: gltf::material::AlphaMode::Opaque, + alpha_mode: AlphaMode::Opaque, alpha_cutoff: None, double_sided: false, occlusion_strength: 1.0, @@ -1014,7 +1032,7 @@ impl Model { material.emissive_factor = processed.emissive_factor; material.metallic_factor = processed.metallic_factor; material.roughness_factor = processed.roughness_factor; - material.alpha_mode = processed.alpha_mode; + material.alpha_mode = processed.alpha_mode.into(); material.alpha_cutoff = processed.alpha_cutoff; material.double_sided = processed.double_sided; material.occlusion_strength = processed.occlusion_strength; @@ -4,7 +4,7 @@ use image::GenericImageView; use serde::{Deserialize, Serialize}; use crate::asset::AssetRegistry; use crate::graphics::SharedGraphicsContext; -use crate::utils::ToPotentialString; +use crate::utils::{ResourceReference, ToPotentialString}; /// As defined in `shaders.wgsl` as /// ``` @@ -69,6 +69,7 @@ pub struct Texture { pub size: wgpu::Extent3d, pub view: wgpu::TextureView, pub hash: Option<u64>, + pub reference: Option<ResourceReference> } impl Texture { @@ -141,6 +142,7 @@ impl Texture { sampler, size, hash: None, + reference: None, } } @@ -190,6 +192,7 @@ impl Texture { view, label: label.to_potential_string(), hash: None, + reference: None, } } @@ -230,6 +233,7 @@ impl Texture { size, view, hash: None, + reference: None, } } @@ -241,7 +245,9 @@ impl Texture { ) -> anyhow::Result<Self> { puffin::profile_function!(label.unwrap_or("")); let data = fs::read(path)?; - Ok(Self::from_bytes(graphics.clone(), &data, label)) + let mut result = Self::from_bytes(graphics.clone(), &data, label); + result.reference = Some(ResourceReference::from_path(path)?); + Ok(result) } /// Loads the texture from bytes. @@ -436,6 +442,7 @@ impl Texture { size, view, hash: Some(hash), + reference: Some(ResourceReference::from_bytes(bytes)), } } @@ -94,12 +94,6 @@ pub enum ResourceReferenceType { /// Typically creates errors, so watch out! None, - /// A stable placeholder reference that represents an intentional "no model selected" state. - /// - /// This is distinct from [`ResourceReferenceType::None`] so it can be serialized and - /// round-tripped without being treated as an error, while still being unique per instance. - Unassigned { id: u64 }, - /// A file type. The [`String`] is the reference from the project or the runtime executable. File(String), @@ -1,14 +0,0 @@ -[package] -name = "dropbear-traits" -version.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true -readme = "README.md" - -[dependencies] -serde.workspace = true -egui.workspace = true -anyhow.workspace = true -hecs.workspace = true -typetag.workspace = true @@ -1,3 +0,0 @@ -# dropbear-traits - -Contains all the traits that other crates would use. @@ -1,156 +0,0 @@ -use std::any::{Any, TypeId}; -use std::collections::HashMap; -use std::future::Future; -use std::pin::Pin; -use hecs::{Entity, World, DynamicBundle}; -use std::sync::Arc; -use serde::{Deserialize, Serialize}; - -pub struct ComponentDescriptor { - pub fqtn: String, - pub type_name: String, - pub category: Option<String>, - pub description: Option<String>, -} - -pub struct ComponentRepository { - components: HashMap<TypeId, ComponentDescriptor>, -} - -impl ComponentRepository { - pub fn new() -> Self { - Self { - components: Default::default(), - } - } - - pub fn register<T: Component + 'static>(&mut self) { - let type_id = TypeId::of::<T>(); - - let descriptor = T::static_descriptor(); - - self.components.insert(type_id, descriptor); - } - - pub fn get_descriptor<T: Component + 'static>(&self) -> Option<&ComponentDescriptor> { - self.components.get(&TypeId::of::<T>()) - } - - pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &ComponentDescriptor)> { - self.components.iter() - } - - pub fn descriptors(&self) -> impl Iterator<Item = &ComponentDescriptor> { - self.components.values() - } -} - -pub trait Component { - type Serialized: Serialize + for<'de> Deserialize<'de>; - - fn static_descriptor() -> ComponentDescriptor; - - fn deserialize(serialized: &Self::Serialized) -> Self; - fn serialize(&self) -> Self::Serialized; - fn inspect(&mut self, ui: &mut egui::Ui); - fn update(&mut self, _ctx: &mut ComponentUpdateContext) {} -} - -pub mod registry; - -pub struct ComponentResources { - map: HashMap<TypeId, Box<dyn Any + Send + Sync>>, -} - -pub struct ComponentUpdateContext { - pub entity: Entity, - pub dt: f32, - pub resources: Arc<ComponentResources>, - world_ptr: *const World, -} - -impl ComponentUpdateContext { - pub fn world(&self) -> &World { - unsafe { &*self.world_ptr } - } - - pub fn new(entity: Entity, dt: f32, resources: Arc<ComponentResources>, world: &World) -> Self { - Self { - entity, - dt, - resources, - world_ptr: world as *const World, - } - } -} - -impl ComponentResources { - pub fn new() -> Self { - Self { - map: HashMap::new(), - } - } - - pub fn insert<T: Any + Send + Sync>(&mut self, value: T) { - self.map.insert(TypeId::of::<T>(), Box::new(value)); - } - - pub fn get<T: Any + Send + Sync>(&self) -> Option<&T> { - self.map - .get(&TypeId::of::<T>()) - .and_then(|boxed| boxed.downcast_ref::<T>()) - } - - pub fn get_mut<T: Any + Send + Sync>(&mut self) -> Option<&mut T> { - self.map - .get_mut(&TypeId::of::<T>()) - .and_then(|boxed| boxed.downcast_mut::<T>()) - } -} - -#[derive(Clone)] -pub struct ComponentInitContext { - pub entity: Entity, - pub resources: Arc<ComponentResources>, -} - -pub type ComponentInitFuture = Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn ComponentInsert>>> + Send + 'static>>; - -pub trait ComponentInsert: Sync + Send { - fn insert(self: Box<Self>, world: &mut World, entity: Entity) -> anyhow::Result<()>; -} - -pub struct InsertBundle<T: DynamicBundle + Send + 'static>(pub T); - -impl<T: DynamicBundle + Send + 'static + Sync> ComponentInsert for InsertBundle<T> { - fn insert(self: Box<Self>, world: &mut World, entity: Entity) -> anyhow::Result<()> { - world.insert(entity, self.0).map_err(|e| anyhow::anyhow!(e.to_string()))?; - Ok(()) - } -} - -#[typetag::serde(tag = "type")] -pub trait SerializableComponent: Send + Sync { - fn as_any(&self) -> &dyn Any; - fn clone_box(&self) -> Box<dyn SerializableComponent>; - - fn type_name(&self) -> &'static str { - std::any::type_name::<Self>() - } - - fn display_name(&self) -> String { - self.type_name() - .split("::") - .last() - .unwrap_or(self.type_name()) - .to_string() - } - - fn init(&self, ctx: ComponentInitContext) -> ComponentInitFuture; -} - -impl Clone for Box<dyn SerializableComponent> { - fn clone(&self) -> Self { - self.clone_box() - } -} @@ -1,244 +0,0 @@ -use std::any::TypeId; -use std::collections::HashMap; -use std::sync::Arc; - -use egui::Ui; -use hecs::{Entity, World}; - -use crate::{Component, ComponentResources, ComponentUpdateContext, SerializableComponent}; - -pub struct ComponentRegistry { - next_id: u32, - entries: HashMap<u32, ComponentEntry>, - type_to_id: HashMap<TypeId, u32>, - converters: Vec<ConverterEntry>, -} - -struct ComponentEntry { - #[allow(dead_code)] - type_id: TypeId, - fqtn: &'static str, - display_name: String, - create_default: Option<fn() -> Box<dyn SerializableComponent>>, - extract_fn: Option<fn(&World, Entity) -> Option<Box<dyn SerializableComponent>>>, - remove_fn: Option<fn(&mut World, Entity)>, - inspect_fn: Option<fn(&mut World, Entity, &mut Ui) -> bool>, - update_fn: Option<fn(&mut World, f32, &Arc<ComponentResources>)>, -} - -struct ConverterEntry { - from_type: TypeId, - convert_fn: Box<dyn Fn(&World, Entity) -> Option<Box<dyn SerializableComponent>> + Send + Sync>, -} - -impl ComponentRegistry { - pub fn new() -> Self { - Self { - next_id: 1, - entries: HashMap::new(), - type_to_id: HashMap::new(), - converters: Vec::new(), - } - } - - pub fn register_with_default<T>(&mut self) - where - T: SerializableComponent + Default + Clone + 'static, - { - self.register_entry::<T>(None, None); - } - - pub fn register_with_default_component<T>(&mut self) - where - T: Component + SerializableComponent + Default + Clone + 'static, - { - let inspect_fn: fn(&mut World, Entity, &mut Ui) -> bool = |world, entity, ui| { - if let Ok(mut component) = world.get::<&mut T>(entity) { - component.inspect(ui); - true - } else { - false - } - }; - - let update_fn: fn(&mut World, f32, &Arc<ComponentResources>) = - |world, dt, resources| { - let world_ptr = world as *const World; - let mut query = world.query::<(Entity, &mut T)>(); - for (entity, component) in query.iter() { - let mut ctx = ComponentUpdateContext::new( - entity, - dt, - resources.clone(), - unsafe { &*world_ptr }, - ); - component.update(&mut ctx); - } - }; - - self.register_entry::<T>(Some(inspect_fn), Some(update_fn)); - } - - fn register_entry<T>( - &mut self, - inspect_fn: Option<fn(&mut World, Entity, &mut Ui) -> bool>, - update_fn: Option<fn(&mut World, f32, &Arc<ComponentResources>)>, - ) - where - T: SerializableComponent + Default + Clone + 'static, - { - let type_id = TypeId::of::<T>(); - if self.type_to_id.contains_key(&type_id) { - return; - } - - let id = self.next_id; - self.next_id = self.next_id.saturating_add(1); - - let entry = ComponentEntry { - type_id, - fqtn: std::any::type_name::<T>(), - display_name: short_name(std::any::type_name::<T>()), - create_default: Some(|| Box::new(T::default())), - extract_fn: Some(|world, entity| { - world - .get::<&T>(entity) - .ok() - .map(|component| component.clone_box()) - }), - remove_fn: Some(|world, entity| { - let _ = world.remove_one::<T>(entity); - }), - inspect_fn, - update_fn, - }; - - self.entries.insert(id, entry); - self.type_to_id.insert(type_id, id); - } - - pub fn register_converter<From, To, F>(&mut self, converter: F) - where - From: Sync + Send + 'static, - To: SerializableComponent + 'static, - F: Fn(&World, Entity, &From) -> Option<To> + Sync + Send + 'static, - { - let from_id = TypeId::of::<From>(); - let convert_fn = move |world: &World, entity: Entity| -> Option<Box<dyn SerializableComponent>> { - world - .get::<&From>(entity) - .ok() - .and_then(|component| converter(world, entity, &component)) - .map(|converted| Box::new(converted) as Box<dyn SerializableComponent>) - }; - - self.converters.push(ConverterEntry { - from_type: from_id, - convert_fn: Box::new(convert_fn), - }); - } - - pub fn iter_available_components(&self) -> impl Iterator<Item = (u32, &str)> { - self.entries - .iter() - .map(|(id, entry)| (*id, entry.fqtn)) - } - - pub fn create_default_component(&self, id: u32) -> Option<Box<dyn SerializableComponent>> { - self.entries - .get(&id) - .and_then(|entry| entry.create_default.map(|ctor| ctor())) - } - - pub fn id_for_component(&self, component: &dyn SerializableComponent) -> Option<u32> { - self.type_to_id.get(&component.as_any().type_id()).copied() - } - - pub fn remove_component_by_id(&self, world: &mut World, entity: Entity, id: u32) { - if let Some(entry) = self.entries.get(&id) { - if let Some(remove_fn) = entry.remove_fn { - remove_fn(world, entity); - } - } - } - - pub fn extract_all_components( - &self, - world: &World, - entity: Entity, - ) -> Vec<Box<dyn SerializableComponent>> { - let mut components = Vec::new(); - - for entry in self.entries.values() { - if let Some(extract_fn) = entry.extract_fn { - if let Some(component) = extract_fn(world, entity) { - components.push(component); - } - } - } - - for converter in &self.converters { - if let Some(component) = (converter.convert_fn)(world, entity) { - components.push(component); - } - } - - components - } - - pub fn inspect_components(&self, world: &mut World, entity: Entity, ui: &mut Ui) { - let mut ids: Vec<u32> = self.entries.keys().copied().collect(); - ids.sort_unstable(); - - for id in ids { - let Some(entry) = self.entries.get(&id) else { - continue; - }; - - if let Some(inspect_fn) = entry.inspect_fn { - if inspect_fn(world, entity, ui) { - ui.add_space(6.0); - } - } - } - } - - pub fn update_components( - &self, - world: &mut World, - dt: f32, - resources: &Arc<ComponentResources>, - ) { - let mut ids: Vec<u32> = self.entries.keys().copied().collect(); - ids.sort_unstable(); - - for id in ids { - let Some(entry) = self.entries.get(&id) else { - continue; - }; - - if let Some(update_fn) = entry.update_fn { - update_fn(world, dt, resources); - } - } - } - - pub fn find_components_by_numeric_id( - &self, - id: u64 - ) -> Vec<(u32, &str)> { - self.entries - .iter() - .filter(|(_, entry)| entry.display_name.starts_with(&id.to_string())) - .map(|(id, entry)| (*id, entry.fqtn)) - .collect() - } - - pub fn iter(&self) -> impl Iterator<Item = (u32, &str)> { - self.iter_available_components() - } -} - -fn short_name(fqtn: &str) -> String { - fqtn.split("::").last().unwrap_or(fqtn).to_string() -} @@ -10,7 +10,6 @@ readme = "README.md" crate-type = ["rlib", "cdylib"] [dependencies] -dropbear-traits = { path = "../dropbear-traits" } dropbear-macro = { path = "../dropbear-macro" } magna-carta = { path = "../magna-carta" } @@ -46,6 +45,7 @@ bytemuck.workspace = true #yakui.workspace = true thiserror.workspace = true combine.workspace = true +dyn-clone.workspace = true [features] default = [] @@ -0,0 +1,82 @@ +use std::sync::Arc; +use egui::{CollapsingHeader, Ui}; +use hecs::{Entity, World}; +use dropbear_engine::animation::AnimationComponent; +use dropbear_engine::asset::ASSET_REGISTRY; +use dropbear_engine::entity::MeshRenderer; +use dropbear_engine::graphics::SharedGraphicsContext; +use crate::component::{Component, ComponentDescriptor, SerializedComponent}; + +#[typetag::serde] +impl SerializedComponent for AnimationComponent {} + +impl Component for AnimationComponent { + type SerializedForm = Self; + + fn descriptor() -> ComponentDescriptor { + ComponentDescriptor { + fqtn: "dropbear_engine::animation::AnimationComponent".to_string(), + type_name: "AnimationComponent".to_string(), + category: Some("Animation".to_string()), + description: Some("Animates a 3D MeshRenderer".to_string()), + } + } + + async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> + where + Self: Sized + { + Ok(Self::default()) + } + + async fn init(ser: Self::SerializedForm, _graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + Ok(ser) + } + + fn update_component(&mut self, world: &World, entity: Entity, dt: f32, graphics: Arc<SharedGraphicsContext>) { + let Ok(renderer) = world.get::<&MeshRenderer>(entity) else { + return; + }; + + let handle = renderer.model(); + if handle.is_null() { + return; + } + + let registry = ASSET_REGISTRY.read(); + let Some(model) = registry.get_model(handle) else { + return; + }; + + self.update(dt, model); + + self.prepare_gpu_resources(graphics.clone()); + } + + fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> { + Box::new(self.clone()) + } + + fn inspect(&mut self, ui: &mut Ui) { + CollapsingHeader::new("Animation").default_open(true).show(ui, |ui| { + ui.label("Active animation:"); + let mut enabled = self.active_animation_index.is_some(); + let mut value = self.active_animation_index.unwrap_or(0); + + ui.horizontal(|ui| { + if ui.checkbox(&mut enabled, "Enable").changed() { + self.active_animation_index = if enabled { Some(value) } else { None }; + } + + ui.add_enabled(enabled, egui::DragValue::new(&mut value)); + + if enabled && self.active_animation_index != Some(value) { + self.active_animation_index = Some(value); + } + }); + + ui.label("Not implemented yet!"); + // todo: complete some more + }); + } +} @@ -1,10 +1,14 @@ //! Additional information and context for cameras from the [`dropbear_engine::camera`] use crate::states::SerializableCamera; use dropbear_engine::camera::{Camera, CameraBuilder, CameraSettings}; -use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent}; use glam::DVec3; use serde::{Deserialize, Serialize}; use std::any::Any; +use std::sync::Arc; +use egui::{CollapsingHeader, Ui}; +use hecs::{Entity, World}; +use dropbear_engine::graphics::SharedGraphicsContext; +use crate::component::{Component, ComponentDescriptor, SerializedComponent}; use crate::ptr::WorldPtr; use crate::scripting::result::DropbearNativeResult; use crate::types::NVector3; @@ -16,6 +20,53 @@ pub struct CameraComponent { pub starting_camera: bool, } +#[typetag::serde] +impl SerializedComponent for SerializableCamera {} + +impl Component for Camera { + type SerializedForm = SerializableCamera; + + fn descriptor() -> ComponentDescriptor { + ComponentDescriptor { + fqtn: "dropbear_engine::camera::Camera".to_string(), + type_name: "Camera3D".to_string(), + category: Some("Camera".to_string()), + description: Some("Allows you to view the scene through the eyes of the component".to_string()), + } + } + + async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> + where + Self: Sized + { + Ok(Camera::predetermined(graphics.clone(), None)) + } + + async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + let label = ser.label.clone(); + let builder = CameraBuilder::from(ser); + Ok(Camera::new(graphics.clone(), builder, Some(label.as_str()))) + } + + fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, graphics: Arc<SharedGraphicsContext>) { + self.update(graphics.clone()) + } + + fn save(&self, world: &World, entity: Entity) -> Box<dyn SerializedComponent> { + if let Ok((cam, comp)) = world.query_one::<(&Camera, &CameraComponent)>(entity).get() { + Box::new(SerializableCamera::from_ecs_camera(cam, comp)) + } else { + Box::new(SerializableCamera::default()) + } + } + + fn inspect(&mut self, ui: &mut Ui) { + CollapsingHeader::new("Camera3D").show(ui, |ui| { + ui.label("Not implemented yet!"); + }); + } +} + impl Default for CameraComponent { fn default() -> Self { Self::new() @@ -36,26 +87,6 @@ impl CameraComponent { } } -#[typetag::serde] -impl SerializableComponent for CameraComponent { - fn as_any(&self) -> &dyn Any { - self - } - - fn clone_box(&self) -> Box<dyn SerializableComponent> { - Box::new(self.clone()) - } - - fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((value,))); - Ok(insert) - }) - } -} - impl From<SerializableCamera> for CameraBuilder { fn from(value: SerializableCamera) -> Self { let forward = value.transform.rotation * DVec3::Z; @@ -0,0 +1,459 @@ +use std::any::TypeId; +use std::collections::HashMap; +use std::sync::Arc; +use egui::{CollapsingHeader, Ui}; +use hecs::{Entity, World}; +use serde::{Deserialize, Serialize}; +use dropbear_engine::asset::{Handle, ASSET_REGISTRY}; +use dropbear_engine::entity::{EntityTransform, MeshRenderer}; +use dropbear_engine::graphics::SharedGraphicsContext; +use dropbear_engine::model::{Model}; +use dropbear_engine::procedural::ProcedurallyGeneratedObject; +use dropbear_engine::texture::Texture; +use dropbear_engine::utils::{ResourceReference, ResourceReferenceType}; +use crate::hierarchy::EntityTransformExt; +use crate::states::{SerializedMaterialCustomisation, SerializedMeshRenderer}; +use crate::utils::ResolveReference; + +pub use typetag::*; + +pub struct ComponentRegistry { + /// Maps TypeId to ComponentDescriptor for quick lookups + descriptors: HashMap<TypeId, ComponentDescriptor>, + /// Maps fully qualified type name to TypeId for lookups by string + fqtn_to_type: HashMap<String, TypeId>, + /// Maps category name to list of TypeIds in that category + categories: HashMap<String, Vec<TypeId>>, + /// Functions that extract and serialize components from entities + extractors: HashMap<TypeId, ExtractorFn>, +} + +type ExtractorFn = Box<dyn Fn(&hecs::World, hecs::Entity) -> Option<Box<dyn SerializedComponent>> + Send + Sync>; + +impl ComponentRegistry { + pub fn new() -> Self { + Self { + descriptors: HashMap::new(), + fqtn_to_type: HashMap::new(), + categories: HashMap::new(), + extractors: Default::default(), + } + } + + /// Register a component type with the registry + pub fn register<T: Component + 'static + Sync + Send>(&mut self) { + let type_id = TypeId::of::<T>(); + let descriptor = T::descriptor(); + + self.fqtn_to_type.insert(descriptor.fqtn.clone(), type_id); + + if let Some(ref category) = descriptor.category { + self.categories + .entry(category.clone()) + .or_insert_with(Vec::new) + .push(type_id); + } + + self.extractors.insert( + type_id, + Box::new(|world: &hecs::World, entity: hecs::Entity| { + world.get::<&T>(entity).ok().map(|component| { + component.save(world, entity) + }) + }) + ); + + self.descriptors.insert(type_id, descriptor); + } + + /// Get descriptor for a specific component type + pub fn get_descriptor<T: Component + 'static>(&self) -> Option<&ComponentDescriptor> { + self.descriptors.get(&TypeId::of::<T>()) + } + + /// Get descriptor by fully qualified type name + pub fn get_descriptor_by_fqtn(&self, fqtn: &str) -> Option<&ComponentDescriptor> { + self.fqtn_to_type + .get(fqtn) + .and_then(|type_id| self.descriptors.get(type_id)) + } + + /// Get all registered component descriptors + pub fn all_descriptors(&self) -> impl Iterator<Item = &ComponentDescriptor> { + self.descriptors.values() + } + + /// Get all component descriptors in a specific category + pub fn descriptors_in_category(&self, category: &str) -> Vec<&ComponentDescriptor> { + self.categories + .get(category) + .map(|type_ids| { + type_ids + .iter() + .filter_map(|type_id| self.descriptors.get(type_id)) + .collect() + }) + .unwrap_or_default() + } + + /// Get all category names + pub fn categories(&self) -> impl Iterator<Item = &String> { + self.categories.keys() + } + + /// Check if a component type is registered + pub fn is_registered<T: Component + 'static>(&self) -> bool { + self.descriptors.contains_key(&TypeId::of::<T>()) + } + + /// Get the TypeId for a component by its fully qualified type name + pub fn get_type_id(&self, fqtn: &str) -> Option<TypeId> { + self.fqtn_to_type.get(fqtn).copied() + } + + /// Get count of registered components + pub fn count(&self) -> usize { + self.descriptors.len() + } + + /// Extract all registered components from an entity + pub fn extract_all_components( + &self, + world: &hecs::World, + entity: hecs::Entity, + ) -> Vec<Box<dyn SerializedComponent>> { + self.extractors + .values() + .filter_map(|extractor| extractor(world, entity)) + .collect() + } + + /// Extract a specific component by type + pub fn extract_component<T: Component + 'static>( + &self, + world: &hecs::World, + entity: hecs::Entity, + ) -> Option<Box<dyn SerializedComponent>> { + let type_id = TypeId::of::<T>(); + self.extractors + .get(&type_id) + .and_then(|extractor| extractor(world, entity)) + } + + /// Extract components by category + pub fn extract_components_in_category( + &self, + world: &hecs::World, + entity: hecs::Entity, + category: &str, + ) -> Vec<Box<dyn SerializedComponent>> { + self.categories + .get(category) + .map(|type_ids| { + type_ids + .iter() + .filter_map(|type_id| { + self.extractors + .get(type_id) + .and_then(|extractor| extractor(world, entity)) + }) + .collect() + }) + .unwrap_or_default() + } +} + +impl Default for ComponentRegistry { + fn default() -> Self { + Self::new() + } +} + +/// A blanket trait for types that can be serialized as a component. +#[typetag::serde(tag = "type")] +pub trait SerializedComponent: dyn_clone::DynClone + Send + Sync {} + +dyn_clone::clone_trait_object!(SerializedComponent); + +#[derive(Clone, Debug)] +pub struct ComponentDescriptor { + /// Fully qualified type name of the component, such as `eucalyptus_core::components::MeshRenderer`. + pub fqtn: String, + /// Short name of the component, such as `MeshRenderer`. + pub type_name: String, + /// Category of the component, such as `Rendering`. + pub category: Option<String>, + /// Description of the component, such as `Renders a 3D model`. + pub description: Option<String>, +} + +/// Defines a type that can be considered a component of an entity. +pub trait Component: Sized { + /// A custom format of the component for saving the state of the component to disk. + /// + /// To have your type available, you must include this blanket trait: + /// ```rust no-run + /// #[typetag::serde] + /// impl SerializedComponent for T {} + /// ``` + type SerializedForm: Serialize + for<'de> Deserialize<'de> + SerializedComponent; + + fn descriptor() -> ComponentDescriptor; + + /// Creates a new instance of the component for times when there is no existing component to + /// initialise from. + async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>; + + /// Converts [`Self::SerializedForm`] into a [`Component`] instance that can be added to + /// `hecs::EntityBuilder` during scene initialisation. + async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self>; + + /// Called every frame to update the component's state. + fn update_component(&mut self, world: &hecs::World, entity: hecs::Entity, dt: f32, graphics: Arc<SharedGraphicsContext>); + + /// Called when saving the scene to disk. Returns the [`Self::SerializedForm`] of the component that can be + /// saved to disk. + fn save(&self, world: &hecs::World, entity: hecs::Entity) -> Box<dyn SerializedComponent>; + + /// In the editor, how the component will be represented in the `Resource Viewer` dock. + fn inspect(&mut self, ui: &mut egui::Ui); +} + +#[typetag::serde] +impl SerializedComponent for SerializedMeshRenderer {} + +// sample for MeshRenderer +impl Component for MeshRenderer { + type SerializedForm = SerializedMeshRenderer; + + fn descriptor() -> ComponentDescriptor { + ComponentDescriptor { + fqtn: "dropbear_engine::entity::MeshRenderer".to_string(), + type_name: "MeshRenderer".to_string(), + category: Some("Rendering".to_string()), + description: Some("Renders a mesh".to_string()), + } + } + + async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> + where + Self: Sized + { + Ok(MeshRenderer::from_handle(Handle::NULL)) + } + + async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + let import_scale = ser.import_scale.unwrap_or(1.0); + + let handle = match &ser.handle.ref_type { + ResourceReferenceType::None => { + log::debug!("ResourceReferenceType is None, setting to `Handle::NULL`"); + Handle::NULL + } + ResourceReferenceType::File(_) => { + log::debug!("Loading model from file: {:?}", ser.handle); + let path = ser.handle.resolve()?; + let buffer = std::fs::read(&path)?; + Model::load_from_memory_raw(graphics.clone(), buffer, Some(ser.label.as_str()), ASSET_REGISTRY.clone()).await? + } + ResourceReferenceType::Bytes(bytes) => { + log::debug!("Loading model from bytes [Len: {}]", bytes.len()); + Model::load_from_memory_raw(graphics.clone(), bytes, Some(ser.label.as_str()), ASSET_REGISTRY.clone()).await? + } + ResourceReferenceType::ProcObj(obj) => match obj { + dropbear_engine::procedural::ProcObj::Cuboid { size_bits } => { + let size = [ + f32::from_bits(size_bits[0]), + f32::from_bits(size_bits[1]), + f32::from_bits(size_bits[2]), + ]; + log::debug!("Loading model from cuboid: {:?}", size); + + let size_vec = glam::DVec3::new(size[0] as f64, size[1] as f64, size[2] as f64); + ProcedurallyGeneratedObject::cuboid(size_vec).build_model( + graphics.clone(), + None, + Some(ser.label.as_str()), + ASSET_REGISTRY.clone(), + ) + } + }, + }; + + let mut renderer = MeshRenderer::from_handle(handle); + renderer.set_import_scale(import_scale); + + for (label, m) in ser.texture_override { + if let Some(mat) = renderer.material_snapshot.get_mut(&label) { + mat.tint = m.tint; + mat.emissive_factor = m.emissive_factor; + mat.metallic_factor = m.metallic_factor; + mat.roughness_factor = m.roughness_factor; + mat.alpha_mode = m.alpha_mode; + mat.alpha_cutoff = m.alpha_cutoff; + mat.double_sided = m.double_sided; + mat.occlusion_strength = m.occlusion_strength; + mat.normal_scale = m.normal_scale; + mat.uv_tiling = m.uv_tiling; + mat.texture_tag = m.texture_tag; + mat.wrap_mode = m.wrap_mode; + + let get_tex = async |resource: Option<ResourceReference>| -> Option<anyhow::Result<Texture>> { + if let Some(dif) = resource { + match dif.ref_type { + ResourceReferenceType::None => { + None + } + ResourceReferenceType::File(_) => { + let path = dif.resolve().ok()?; + let tex = Texture::from_file(graphics.clone(), &path, Some(&label)).await; + Some(tex) + } + ResourceReferenceType::Bytes(bytes) => { + let tex = Texture::from_bytes(graphics.clone(), &bytes, Some(&label)); + Some(Ok(tex)) + } + ResourceReferenceType::ProcObj(_) => { + Some(Err(anyhow::anyhow!("Using a ProcObj as a texture is not valid, for texture with label {}", label))) + } + } + } else { + None + } + }; + + if let Some(tex) = get_tex(m.diffuse_texture).await { + mat.diffuse_texture = tex?; + } + + if let Some(tex) = get_tex(m.emissive_texture).await { + mat.emissive_texture = Some(tex?); + } + + if let Some(tex) = get_tex(m.normal_texture).await { + mat.normal_texture = tex?; + } + + if let Some(tex) = get_tex(m.occlusion_texture).await { + mat.occlusion_texture = Some(tex?); + } + + if let Some(tex) = get_tex(m.metallic_roughness_texture).await { + mat.metallic_roughness_texture = Some(tex?); + } + } + } + + Ok(renderer) + } + + fn update_component(&mut self, world: &World, entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) { + if let Ok((mesh, transform)) = world.query_one::<(&mut MeshRenderer, &EntityTransform)>(entity).get() { + mesh.update(&transform.propagate(&world, entity)) + } + } + + fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> { + let asset = ASSET_REGISTRY.read(); + let model = asset.get_model(self.model()); + + let mut texture_override: HashMap<String, SerializedMaterialCustomisation> = HashMap::new(); + for (label, mat) in &self.material_snapshot { + let diffuse_texture = model + .and_then(|m| m.materials.iter() + .find(|material| + material.diffuse_texture.label.as_ref() == Some(&label) && + material.diffuse_texture.reference == mat.diffuse_texture.reference + ) + .and_then(|material| material.diffuse_texture.reference.clone()) + ); + + let normal_texture = model + .and_then(|m| m.materials.iter() + .find(|material| + material.normal_texture.label.as_ref() == Some(&label) && + material.normal_texture.reference == mat.normal_texture.reference + ) + .and_then(|material| material.normal_texture.reference.clone()) + ); + + let emissive_texture = model + .and_then(|m| { + let mat_ref = mat.emissive_texture.as_ref()?.reference.as_ref()?; + m.materials.iter() + .find_map(|material| { + let nt = material.emissive_texture.as_ref()?; + if nt.label.as_ref() == Some(&label) && nt.reference.as_ref() == Some(mat_ref) { + nt.reference.clone() + } else { + None + } + }) + }); + + let occlusion_texture = model + .and_then(|m| { + let mat_ref = mat.occlusion_texture.as_ref()?.reference.as_ref()?; + m.materials.iter() + .find_map(|material| { + let nt = material.occlusion_texture.as_ref()?; + if nt.label.as_ref() == Some(&label) && nt.reference.as_ref() == Some(mat_ref) { + nt.reference.clone() + } else { + None + } + }) + }); + + let metallic_roughness_texture = model + .and_then(|m| { + let mat_ref = mat.metallic_roughness_texture.as_ref()?.reference.as_ref()?; + m.materials.iter() + .find_map(|material| { + let nt = material.metallic_roughness_texture.as_ref()?; + if nt.label.as_ref() == Some(&label) && nt.reference.as_ref() == Some(mat_ref) { + nt.reference.clone() + } else { + None + } + }) + }); + + + texture_override.insert(label.to_string(), SerializedMaterialCustomisation { + label: label.clone(), + diffuse_texture, + tint: mat.tint, + emissive_factor: mat.emissive_factor, + metallic_factor: mat.metallic_factor, + roughness_factor: mat.roughness_factor, + alpha_mode: mat.alpha_mode, + alpha_cutoff: mat.alpha_cutoff, + double_sided: mat.double_sided, + occlusion_strength: mat.occlusion_strength, + normal_scale: mat.normal_scale, + uv_tiling: mat.uv_tiling, + texture_tag: mat.texture_tag.clone(), + wrap_mode: mat.wrap_mode, + emissive_texture, + normal_texture, + occlusion_texture, + metallic_roughness_texture, + }); + } + + Box::new(SerializedMeshRenderer { + label: model.unwrap().label.clone(), + handle: Default::default(), + import_scale: None, + texture_override, + }) + } + + fn inspect(&mut self, ui: &mut Ui) { + CollapsingHeader::new("Mesh Renderer").show(ui, |ui| { + ui.label("Not implemented yet (MeshRenderer)"); + }); + } +} + @@ -8,7 +8,6 @@ pub mod ptr; pub mod runtime; pub mod scene; pub mod scripting; -pub mod spawn; pub mod states; pub mod utils; pub mod command; @@ -20,22 +19,24 @@ pub mod entity; pub mod engine; pub mod transform; pub mod asset; +pub mod component; +pub mod animation; pub use dropbear_macro as macros; -pub use dropbear_traits as traits; pub use egui; pub use rapier3d; use dropbear_engine::animation::AnimationComponent; use dropbear_engine::camera::Camera; use dropbear_engine::entity::{EntityTransform, MeshRenderer}; -use dropbear_traits::registry::ComponentRegistry; +use dropbear_engine::lighting::Light; use properties::CustomProperties; use crate::camera::CameraComponent; +use crate::component::ComponentRegistry; use crate::physics::collider::ColliderGroup; use crate::physics::kcc::KCC; use crate::physics::rigidbody::RigidBody; -use crate::states::{SerializableCamera, Light, Script, SerializedMeshRenderer}; +use crate::states::{SerializableCamera, SerializedLight, Script, SerializedMeshRenderer}; /// The appdata directory for storing any information. /// @@ -49,56 +50,14 @@ pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo { pub fn register_components( component_registry: &mut ComponentRegistry, ) { - component_registry.register_with_default_component::<EntityTransform>(); - component_registry.register_with_default::<CustomProperties>(); - component_registry.register_with_default::<Light>(); - component_registry.register_with_default::<Script>(); - component_registry.register_with_default::<SerializedMeshRenderer>(); - component_registry.register_with_default::<SerializableCamera>(); - component_registry.register_with_default::<RigidBody>(); - component_registry.register_with_default::<ColliderGroup>(); - component_registry.register_with_default::<KCC>(); - component_registry.register_with_default_component::<AnimationComponent>(); - - component_registry.register_converter::<MeshRenderer, SerializedMeshRenderer, _>( - |_, _, renderer| { - Some(SerializedMeshRenderer::from_renderer(renderer)) - }, - ); - - component_registry.register_converter::<CameraComponent, SerializableCamera, _>( - |world, entity, component| { - let Ok(camera) = world.get::<&Camera>(entity) else { - log::debug!( - "Camera component without matching Camera found on entity {:?}", - entity - ); - return None; - }; - - Some(SerializableCamera::from_ecs_camera(&camera, component)) - }, - ); - - // // register plugin defined structs - // if let Err(e) = plugin_registry.load_plugins() { - // fatal!("Failed to load plugins: {}", e); - // return; - // } - // - // for p in plugin_registry.list_plugins() { - // log::info!("Plugin {} has been loaded", p.display_name); - // } - // - // log::info!("Total plugins added: {}", plugin_registry.plugins.len()); - // - // for plugin in plugin_registry.list_plugins() { - // if let Some(p) = plugin_registry.get_mut(&plugin.display_name) { - // p.register_component(component_registry); - // log::info!( - // "Components for plugin [{}] has been registered to component registry", - // plugin.display_name - // ); - // } - // } + component_registry.register::<EntityTransform>(); + component_registry.register::<CustomProperties>(); + component_registry.register::<Light>(); + component_registry.register::<Script>(); + component_registry.register::<MeshRenderer>(); + component_registry.register::<Camera>(); + component_registry.register::<RigidBody>(); + component_registry.register::<ColliderGroup>(); + component_registry.register::<KCC>(); + component_registry.register::<AnimationComponent>(); } @@ -1,14 +1,77 @@ +use std::sync::Arc; +use egui::Ui; use crate::ptr::WorldPtr; use crate::scripting::jni::utils::{FromJObject, ToJObject}; use crate::scripting::native::DropbearNativeError; use crate::scripting::result::DropbearNativeResult; use crate::types::NVector3; use dropbear_engine::entity::{EntityTransform, Transform}; -use dropbear_engine::lighting::{LightComponent, LightType}; +use dropbear_engine::lighting::{Light, LightComponent, LightType}; use glam::{DQuat, DVec3}; use hecs::{Entity, World}; use jni::objects::{JObject, JValue}; use jni::JNIEnv; +use dropbear_engine::graphics::SharedGraphicsContext; +use crate::component::{Component, ComponentDescriptor, SerializedComponent}; +use crate::states::SerializedLight; + +#[typetag::serde] +impl SerializedComponent for SerializedLight {} + +impl Component for Light { + type SerializedForm = SerializedLight; + + fn descriptor() -> ComponentDescriptor { + ComponentDescriptor { + fqtn: "dropbear_engine::lighting::Light".to_string(), + type_name: "Light".to_string(), + category: Some("Lighting".to_string()), + description: Some("An object that emits light".to_string()), + } + } + + async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> + where + Self: Sized + { + Ok(Light::new(graphics.clone(), LightComponent::default(), Transform::default(), None).await) + } + + async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + let light = Light::new( + graphics.clone(), + ser.light_component, + ser.transform, + Some(ser.label.as_str()) + ).await; + + Ok(light) + } + + fn update_component(&mut self, world: &World, entity: Entity, _dt: f32, graphics: Arc<SharedGraphicsContext>) { + if let Ok((light, comp, trans)) = world.query_one::<(&mut Light, &mut LightComponent, &mut Transform)>(entity).get() { + light.update(&graphics, comp, trans); + } + } + + fn save(&self, world: &World, entity: Entity) -> Box<dyn SerializedComponent> { + if let Ok((_, comp, transform)) = world.query_one::<(&Light, &LightComponent, &Transform)>(entity).get() { + Box::new(SerializedLight { + label: self.label.clone(), + transform: *transform, + light_component: comp.clone(), + enabled: comp.enabled, + entity_id: Some(entity), + }) + } else { + Box::new(SerializedLight::default()) + } + } + + fn inspect(&mut self, ui: &mut Ui) { + todo!() + } +} #[repr(C)] #[derive(Clone, Copy, Debug)] @@ -205,17 +205,21 @@ fn set_texture_override( let _ = shared::resolve_target_material_name(model, &material_name) .ok_or(DropbearNativeError::InvalidArgument)?; - let handle = Handle::<dropbear_engine::texture::Texture>::new(texture_handle); - if reader.get_texture(handle).is_none() { + let handle = Handle::<Texture>::new(texture_handle); + let Some(diff_tex) = reader.get_texture(handle) else { return Err(DropbearNativeError::InvalidHandle); - } - - drop(reader); + }; let mut renderer = world .get::<&mut MeshRenderer>(entity) .map_err(|_| DropbearNativeError::NoSuchComponent)?; - renderer.set_texture_override(handle); + + if let Some(v) = renderer.material_snapshot.get_mut(&material_name) { + v.diffuse_texture = diff_tex.clone(); + } else { + return Err(DropbearNativeError::InvalidArgument); + } + Ok(()) } @@ -21,19 +21,22 @@ use crate::states::Label; use dropbear_engine::graphics::SharedGraphicsContext; use dropbear_engine::wgpu::util::{BufferInitDescriptor, DeviceExt}; use dropbear_engine::wgpu::{Buffer, BufferUsages}; -use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent}; use std::any::Any; use ::jni::objects::{JObject, JValue}; use ::jni::JNIEnv; use rapier3d::prelude::ColliderBuilder; use serde::{Deserialize, Serialize}; use std::sync::Arc; +use egui::{CollapsingHeader, Ui}; use crate::physics::collider::shared::{get_collider, get_collider_mut}; use crate::scripting::native::DropbearNativeError; use crate::scripting::result::DropbearNativeResult; use crate::types::{NCollider, NVector3}; use glam::DQuat; +use hecs::{Entity, World}; use rapier3d::prelude::{Rotation, SharedShape, TypedShape, Vector}; +use dropbear_engine::animation::AnimationComponent; +use crate::component::{Component, ComponentDescriptor, SerializedComponent}; use crate::physics::PhysicsState; use crate::ptr::PhysicsStatePtr; @@ -54,22 +57,41 @@ impl ColliderGroup { } #[typetag::serde] -impl SerializableComponent for ColliderGroup { - fn as_any(&self) -> &dyn Any { - self +impl SerializedComponent for ColliderGroup {} + +impl Component for ColliderGroup { + type SerializedForm = Self; + + fn descriptor() -> ComponentDescriptor { + ComponentDescriptor { + fqtn: "eucalyptus_core::physics::collider::ColliderGroup".to_string(), + type_name: "ColliderGroup".to_string(), + category: Some("Physics".to_string()), + description: Some("A group of colliders".to_string()), + } } - fn clone_box(&self) -> Box<dyn SerializableComponent> { + async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> + where + Self: Sized + { + Ok(Self::new()) + } + + async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + Ok(ser) + } + + fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {} + + fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> { Box::new(self.clone()) } - fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((value,))); - Ok(insert) - }) + fn inspect(&mut self, ui: &mut Ui) { + CollapsingHeader::new("Colliders").default_open(true).show(ui, |ui| { + ui.label("Not implemented yet!"); + }); } } @@ -5,9 +5,11 @@ pub mod character_collision; use rapier3d::control::{CharacterCollision, KinematicCharacterController}; use serde::{Deserialize, Serialize}; -use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent}; use crate::states::Label; use std::any::Any; +use std::sync::Arc; +use egui::Ui; +use hecs::{Entity, World}; use crate::physics::PhysicsState; use crate::scripting::jni::utils::ToJObject; use crate::scripting::native::DropbearNativeError; @@ -18,6 +20,9 @@ use jni::JNIEnv; use rapier3d::dynamics::RigidBodyType; use rapier3d::math::Rotation; use rapier3d::prelude::QueryFilter; +use dropbear_engine::animation::AnimationComponent; +use dropbear_engine::graphics::SharedGraphicsContext; +use crate::component::{Component, ComponentDescriptor, SerializedComponent}; use crate::ptr::WorldPtr; /// The kinematic character controller (kcc) component. @@ -30,22 +35,41 @@ pub struct KCC { } #[typetag::serde] -impl SerializableComponent for KCC { - fn as_any(&self) -> &dyn Any { - self +impl SerializedComponent for KCC {} + +impl Component for KCC { + type SerializedForm = Self; + + fn descriptor() -> ComponentDescriptor { + ComponentDescriptor { + fqtn: "eucalyptus_core::physics::kcc::KCC".to_string(), + type_name: "KinematicCharacterController".to_string(), + category: Some("Physics".to_string()), + description: Some("A kinematic character controller".to_string()), + } + } + + async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> + where + Self: Sized + { + Ok(Self::default()) + } + + async fn init(ser: Self::SerializedForm, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + Ok(ser) } - fn clone_box(&self) -> Box<dyn SerializableComponent> { + fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {} + + fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> { Box::new(self.clone()) } - fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((value,))); - Ok(insert) - }) + fn inspect(&mut self, ui: &mut Ui) { + egui::CollapsingHeader::new("Kinematic Character Controller").default_open(true).show(ui, |ui| { + ui.label("Not implemented yet!") + }); } } @@ -4,12 +4,16 @@ use crate::scripting::jni::utils::{FromJObject, ToJObject}; use crate::scripting::native::DropbearNativeError; use crate::scripting::result::DropbearNativeResult; use crate::states::Label; -use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent}; use std::any::Any; +use std::sync::Arc; use ::jni::objects::{JObject, JValue}; use ::jni::JNIEnv; +use egui::{CollapsingHeader, Ui}; +use hecs::{Entity, World}; use rapier3d::prelude::RigidBodyType; use serde::{Deserialize, Serialize}; +use dropbear_engine::graphics::SharedGraphicsContext; +use crate::component::{Component, ComponentDescriptor, SerializedComponent}; use crate::types::{IndexNative, NCollider, RigidBodyContext, NVector3}; use crate::physics::PhysicsState; use crate::ptr::{PhysicsStatePtr, WorldPtr}; @@ -183,22 +187,41 @@ impl Default for RigidBody { } #[typetag::serde] -impl SerializableComponent for RigidBody { - fn as_any(&self) -> &dyn Any { - self +impl SerializedComponent for RigidBody {} + +impl Component for RigidBody { + type SerializedForm = Self; + + fn descriptor() -> ComponentDescriptor { + ComponentDescriptor { + fqtn: "eucalyptus_core::physics::rigidbody::RigidBody".to_string(), + type_name: "RigidBody".to_string(), + category: Some("Physics".to_string()), + description: Some("An object that can move, rotate or collide with another object".to_string()), + } } - fn clone_box(&self) -> Box<dyn SerializableComponent> { + async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> + where + Self: Sized + { + Ok(Self::default()) + } + + async fn init(ser: Self::SerializedForm, _graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + Ok(ser) + } + + fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {} + + fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> { Box::new(self.clone()) } - fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((value,))); - Ok(insert) - }) + fn inspect(&mut self, ui: &mut Ui) { + CollapsingHeader::new("RigidBody").default_open(true).show(ui, |ui| { + ui.label("Not implemented yet!"); + }); } } @@ -1,15 +1,18 @@ use std::fmt; use std::fmt::{Display, Formatter}; use serde::{Deserialize, Serialize}; -use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent}; use std::any::Any; -use egui::Ui; +use std::sync::Arc; +use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, TextEdit, Ui}; use hecs::{Entity, World}; +use dropbear_engine::graphics::SharedGraphicsContext; +use crate::component::{Component, ComponentDescriptor, SerializedComponent}; use crate::ptr::WorldPtr; use crate::scripting::native::DropbearNativeError; use crate::scripting::result::DropbearNativeResult; use crate::states::Property; use crate::types::NVector3; +use crate::warn; /// Properties for an entity, typically queries with `entity.getProperty<Float>` and `entity.setProperty(21)` #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] @@ -18,6 +21,166 @@ pub struct CustomProperties { pub next_id: u64, } +#[typetag::serde] +impl SerializedComponent for CustomProperties {} + +impl Component for CustomProperties { + type SerializedForm = Self; + + fn descriptor() -> ComponentDescriptor { + ComponentDescriptor { + fqtn: "eucalyptus_core::properties::CustomProperties".to_string(), + type_name: "CustomProperties".to_string(), + category: None, + description: Some("Custom properties for an entity".to_string()), + } + } + + async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> + where + Self: Sized + { + Ok(Self::new()) + } + + async fn init(ser: Self::SerializedForm, _graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + Ok(ser) + } + + fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {} + + fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> { + Box::new(self.clone()) + } + + fn inspect(&mut self, ui: &mut Ui) { + CollapsingHeader::new("Custom Properties").default_open(true).show(ui, |ui| { + ui.vertical(|ui| { + Grid::new("properties").striped(true).show(ui, |ui| { + ui.label(RichText::new("Key")); + ui.label(RichText::new("Type")); + ui.label(RichText::new("Value")); + ui.label(RichText::new("Action")); + ui.end_row(); + + let mut to_delete: Option<u64> = None; + let mut to_rename: Option<(u64, String)> = None; + + for (_i, property) in self.custom_properties.iter_mut().enumerate() { + let mut edited_key = property.key.clone(); + ui.add_sized([100.0, 20.0], TextEdit::singleline(&mut edited_key)); + + if edited_key != property.key { + to_rename = Some((property.id, edited_key)); + } + + let current_type = ValueType::from(&mut property.value); + let mut selected_type = current_type; + + ComboBox::from_id_salt(format!("type_{}", property.id)) + .selected_text(format!("{:?}", selected_type)) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut selected_type, + ValueType::String, + "String", + ); + ui.selectable_value(&mut selected_type, ValueType::Float, "Float"); + ui.selectable_value(&mut selected_type, ValueType::Int, "Int"); + ui.selectable_value(&mut selected_type, ValueType::Bool, "Bool"); + ui.selectable_value(&mut selected_type, ValueType::Vec3, "Vec3"); + }); + + if selected_type != current_type { + property.value = match selected_type { + ValueType::String => Value::String(String::new()), + ValueType::Float => Value::Double(0.0), + ValueType::Int => Value::Int(0), + ValueType::Bool => Value::Bool(false), + ValueType::Vec3 => Value::Vec3([0.0, 0.0, 0.0]), + }; + } + + let speed = { + let input = ui.input(|i| i.modifiers); + if input.shift { + 0.01 + } else if cfg!(target_os = "macos") && input.mac_cmd + || !cfg!(target_os = "macos") && input.ctrl + { + 1.0 + } else { + 0.1 + } + }; + + match &mut property.value { + Value::String(s) => { + ui.add_sized([100.0, 20.0], egui::TextEdit::singleline(s)); + } + Value::Int(n) => { + ui.add(DragValue::new(n).speed(1.0)); + } + Value::Double(f) => { + ui.add(DragValue::new(f).speed(speed)); + } + Value::Bool(b) => { + if ui.button(if *b { "✅" } else { "❌" }).clicked() { + *b = !*b; + } + } + Value::Vec3(v) => { + ui.horizontal(|ui| { + ui.add(DragValue::new(&mut v[0]).speed(speed)); + ui.add(DragValue::new(&mut v[1]).speed(speed)); + ui.add(DragValue::new(&mut v[2]).speed(speed)); + }); + } + } + + if ui.button("🗑️").clicked() { + log::debug!("Trashing {}", property.key); + to_delete = Some(property.id); + } + + ui.end_row(); + } + + if let Some(id) = to_delete { + self.custom_properties.retain(|p| p.id != id); + } + + if let Some((id, new_key)) = to_rename { + if let Some(property) = + self.custom_properties.iter_mut().find(|p| p.id == id) + { + property.key = new_key; + } else { + warn!("Failed to rename property: id not found"); + } + } + + if ui.button("Add").clicked() { + log::debug!("Inserting new default value"); + let mut new_key = String::from("new_property"); + let mut counter = 1; + while self.custom_properties.iter().any(|p| p.key == new_key) { + new_key = format!("new_property_{}", counter); + counter += 1; + } + self.custom_properties.push(Property { + id: self.next_id, + key: new_key, + value: Value::default(), + }); + self.next_id += 1; + } + }); + }); + }); + } +} + impl CustomProperties { /// Creates a new [CustomProperties] pub fn new() -> Self { @@ -128,26 +291,6 @@ impl Default for CustomProperties { } } -#[typetag::serde] -impl SerializableComponent for CustomProperties { - fn as_any(&self) -> &dyn Any { - self - } - - fn clone_box(&self) -> Box<dyn SerializableComponent> { - Box::new(self.clone()) - } - - fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((value,))); - Ok(insert) - }) - } -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum Value { String(String), @@ -176,6 +319,40 @@ impl Display for Value { } } +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ValueType { + String, + Float, + Int, + Bool, + Vec3, +} + +impl From<Value> for ValueType { + fn from(value: Value) -> Self { + match value { + Value::String(_) => ValueType::String, + Value::Int(_) => ValueType::Int, + Value::Double(_) => ValueType::Float, + Value::Bool(_) => ValueType::Bool, + Value::Vec3(_) => ValueType::Vec3, + } + } +} + +impl From<&mut Value> for ValueType { + fn from(value: &mut Value) -> Self { + match value { + Value::String(_) => ValueType::String, + Value::Int(_) => ValueType::Int, + Value::Double(_) => ValueType::Float, + Value::Bool(_) => ValueType::Bool, + Value::Vec3(_) => ValueType::Vec3, + } + } +} + + pub mod shared { use hecs::World; use crate::properties::CustomProperties; @@ -5,13 +5,11 @@ pub mod scripting; use crate::camera::CameraComponent; use crate::hierarchy::{Children, Parent, SceneHierarchy}; -use crate::states::{SerializableCamera, Label, Light, Script, SerializedMeshRenderer, WorldLoadingStatus, PROJECT}; +use crate::states::{SerializableCamera, Label, SerializedLight, Script, SerializedMeshRenderer, WorldLoadingStatus, PROJECT}; use dropbear_engine::camera::Camera; use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform}; use dropbear_engine::graphics::SharedGraphicsContext; use dropbear_engine::lighting::{Light as EngineLight, LightComponent}; -use dropbear_traits::{ComponentInitContext, ComponentResources, SerializableComponent}; -use dropbear_traits::registry::ComponentRegistry; use glam::{DQuat, DVec3}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; use ron::ser::PrettyConfig; @@ -23,6 +21,7 @@ use std::sync::Arc; use crossbeam_channel::Sender; use egui::Ui; use hecs::Entity; +use crate::component::{Component, ComponentRegistry, SerializedComponent}; use crate::physics::collider::ColliderGroup; use crate::physics::kcc::KCC; use crate::physics::PhysicsState; @@ -35,7 +34,7 @@ pub struct SceneEntity { pub label: Label, #[serde(default)] - pub components: Vec<Box<dyn SerializableComponent>>, + pub components: Vec<Box<dyn SerializedComponent>>, #[serde(skip)] pub entity_id: Option<hecs::Entity>, @@ -125,28 +124,6 @@ impl SceneConfig { } } - /// Helper function to init a component and insert it into the world. - async fn init_component( - component: &Box<dyn SerializableComponent>, - entity: Entity, - world: &mut hecs::World, - resources: Arc<ComponentResources>, - label: &str, - ) -> anyhow::Result<()> { - let ctx = ComponentInitContext { entity, resources }; - let insert = component.init(ctx).await?; - insert.insert(world, entity).map_err(|e| { - anyhow::anyhow!( - "Failed to insert component '{}' for '{}': {}", - component.type_name(), - label, - e - ) - })?; - - Ok(()) - } - /// Write the scene config to a .eucs file pub fn write_to(&self, project_path: impl AsRef<Path>) -> anyhow::Result<()> { let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default()) @@ -482,7 +459,7 @@ impl SceneConfig { EngineLight::new(graphics.clone(), comp.clone(), trans, Some("Default Light")) .await; - let light_config = Light { + let light_config = SerializedLight { label: "Default Light".to_string(), transform: trans, light_component: comp.clone(), @@ -1,43 +0,0 @@ -//! Traits and helpers for dealing with spawn queues. - -use crate::scene::SceneEntity; -use dropbear_engine::future::{FutureHandle, FutureQueue}; -use dropbear_engine::graphics::SharedGraphicsContext; -use parking_lot::Mutex; -use std::sync::{Arc, LazyLock}; - -/// All spawns that are waiting to be spawned in. -pub static PENDING_SPAWNS: LazyLock<Mutex<Vec<PendingSpawn>>> = - LazyLock::new(|| Mutex::new(Vec::new())); - -/// A spawn that's waiting to be added into the world. -#[derive(Clone)] -pub struct PendingSpawn { - 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). - /// - /// If one is NOT specified, it will be created based off the information provided. It is **recommended** to set it to [`None`]. - pub handle: Option<FutureHandle>, -} - -/// Extension trait for checking the editor (or anything else) for any spawns -/// that are waiting to be polled and added -pub trait PendingSpawnController { - /// Checks up on the spawn list and spawns them into the world after it has been - /// asynchronously loaded. - /// - /// This is expected to be run on the update loop. - fn check_up( - &mut self, - graphics: Arc<SharedGraphicsContext>, - queue: Arc<FutureQueue>, - ) -> anyhow::Result<()>; -} - -/// Helper function to spawn a [`PendingSpawn`] -pub fn push_pending_spawn(spawn: PendingSpawn) { - log::debug!("Pushing spawn"); - PENDING_SPAWNS.lock().push(spawn); -} @@ -12,12 +12,12 @@ use dropbear_engine::lighting::LightComponent; use dropbear_engine::graphics::SharedGraphicsContext; use dropbear_engine::asset::ASSET_REGISTRY; use dropbear_engine::utils::{ResourceReference, ResourceReferenceType, EUCA_SCHEME}; -use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent}; use once_cell::sync::Lazy; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; use std::any::Any; +use std::collections::HashMap; use std::fmt; use std::fmt::{Display, Formatter}; use std::ops::{Deref, DerefMut}; @@ -25,7 +25,9 @@ use std::path::PathBuf; use std::sync::Arc; use egui::{CollapsingHeader, TextEdit, Ui}; use hecs::{Entity, World}; -use dropbear_traits::{Component, ComponentDescriptor}; +use dropbear_engine::model::AlphaMode; +use dropbear_engine::texture::TextureWrapMode; +use crate::component::{Component, ComponentDescriptor, SerializedComponent}; use crate::properties::Value; /// A global "singleton" that contains the configuration of a project. @@ -156,10 +158,13 @@ pub struct Script { pub tags: Vec<String>, } +#[typetag::serde] +impl SerializedComponent for Script {} + impl Component for Script { - type Serialized = Self; + type SerializedForm = Self; - fn static_descriptor() -> ComponentDescriptor { + fn descriptor() -> ComponentDescriptor { ComponentDescriptor { fqtn: "eucalyptus_core::states::Script".to_string(), type_name: "Script".to_string(), @@ -168,40 +173,47 @@ impl Component for Script { } } - fn deserialize(serialized: &Self::Serialized) -> Self { - serialized.clone() + async fn first_time(_graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> + where + Self: Sized + { + Ok(Self::default()) } - fn serialize(&self) -> Self::Serialized { - self.clone() + async fn init(ser: Self::SerializedForm, _graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + Ok(ser) + } + + fn update_component(&mut self, _world: &World, _entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) {} + + fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> { + Box::new(self.clone()) } fn inspect(&mut self, ui: &mut Ui) { - ui.vertical(|ui| { - CollapsingHeader::new("Logic") - .default_open(true) - .show(ui, |ui| { - let mut local_del: Option<usize> = None; - for (i, tag) in self.tags.iter_mut().enumerate() { - let current_width = ui.available_width(); - ui.horizontal(|ui| { - ui.add_sized( - [current_width * 70.0 / 100.0, 20.0], - TextEdit::singleline(tag), - ); - if ui.button("🗑️").clicked() { - local_del = Some(i); - } - }); - } - if let Some(i) = local_del { - self.tags.remove(i); - } - if ui.button("➕ Add").clicked() { - self.tags.push(String::new()) - } - }); - }); + CollapsingHeader::new("Logic") + .default_open(true) + .show(ui, |ui| { + let mut local_del: Option<usize> = None; + for (i, tag) in self.tags.iter_mut().enumerate() { + let current_width = ui.available_width(); + ui.horizontal(|ui| { + ui.add_sized( + [current_width * 70.0 / 100.0, 20.0], + TextEdit::singleline(tag), + ); + if ui.button("🗑️").clicked() { + local_del = Some(i); + } + }); + } + if let Some(i) = local_del { + self.tags.remove(i); + } + if ui.button("➕ Add").clicked() { + self.tags.push(String::new()) + } + }); } } @@ -253,8 +265,8 @@ impl SerializableCamera { }; let transform = Transform { - position: position, - rotation: rotation, + position, + rotation, scale: glam::DVec3::ONE, }; @@ -282,7 +294,7 @@ pub struct Property { // A serializable configuration struct for the [Light] type #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Light { +pub struct SerializedLight { pub label: String, pub transform: Transform, pub light_component: LightComponent, @@ -292,10 +304,10 @@ pub struct Light { pub entity_id: Option<hecs::Entity>, } -impl Default for Light { +impl Default for SerializedLight { fn default() -> Self { Self { - label: "New Light".to_string(), + label: "Default Light".to_string(), transform: Transform::default(), light_component: LightComponent::default(), enabled: true, @@ -424,189 +436,32 @@ 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)] pub struct SerializedMeshRenderer { + pub label: String, pub handle: ResourceReference, pub import_scale: Option<f32>, - pub texture_override: Option<ResourceReference>, -} - -impl SerializedMeshRenderer { - /// Creates a new [SerializedMeshRenderer] from an existing [MeshRenderer] by cloning data. - pub fn from_renderer(renderer: &MeshRenderer) -> Self { - let handle = renderer.model(); - let handle_ref = if handle.is_null() { - ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id }) - } else { - let registry = ASSET_REGISTRY.read(); - registry - .get_model(handle) - .map(|model| model.path.clone()) - .unwrap_or_else(|| { - ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id }) - }) - }; - - let texture_override = renderer.texture_override().map(|handle| { - let registry = ASSET_REGISTRY.read(); - let label = registry.get_label_from_texture_handle(handle); - let reference = label.and_then(|value| { - if value.starts_with(EUCA_SCHEME) { - Some(ResourceReference::from_reference(ResourceReferenceType::File(value))) - } else { - None - } - }); - - reference.unwrap_or_else(|| { - ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: handle.id }) - }) - }); - - Self { - handle: handle_ref, - import_scale: Some(renderer.import_scale()), - texture_override, - } - } -} - -#[typetag::serde] -impl SerializableComponent for Script { - fn as_any(&self) -> &dyn Any { - self - } - - fn clone_box(&self) -> Box<dyn SerializableComponent> { - Box::new(self.clone()) - } - - fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((value,))); - Ok(insert) - }) - } -} - -#[typetag::serde] -impl SerializableComponent for SerializableCamera { - fn as_any(&self) -> &dyn Any { - self - } - - fn clone_box(&self) -> Box<dyn SerializableComponent> { - Box::new(self.clone()) - } - - fn init(&self, ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let graphics = ctx - .resources - .get::<Arc<SharedGraphicsContext>>() - .ok_or_else(|| anyhow::anyhow!("SharedGraphicsContext missing for Camera3D init"))?; - - let builder = CameraBuilder::from(value.clone()); - let camera = Camera::new(graphics.clone(), builder, Some(value.label.as_str())); - let component = CameraComponent::from(value); - - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((camera, component))); - Ok(insert) - }) - } -} - -#[typetag::serde] -impl SerializableComponent for Light { - fn as_any(&self) -> &dyn Any { - self - } - - fn clone_box(&self) -> Box<dyn SerializableComponent> { - Box::new(self.clone()) - } - - fn init(&self, ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let graphics = ctx - .resources - .get::<Arc<SharedGraphicsContext>>() - .ok_or_else(|| anyhow::anyhow!("SharedGraphicsContext missing for Light init"))?; - - let engine_light = dropbear_engine::lighting::Light::new( - graphics.clone(), - value.light_component.clone(), - value.transform, - Some(value.label.as_str()), - ) - .await; - - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle(( - value.light_component.clone(), - engine_light, - value.clone(), - value.transform, - ))); - Ok(insert) - }) - } + pub texture_override: HashMap<String, SerializedMaterialCustomisation>, } -#[typetag::serde] -impl SerializableComponent for Label { - fn as_any(&self) -> &dyn Any { - self - } - - fn clone_box(&self) -> Box<dyn SerializableComponent> { - Box::new(self.clone()) - } - - fn init(&self, _ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((value,))); - Ok(insert) - }) - } -} - -#[typetag::serde] -impl SerializableComponent for SerializedMeshRenderer { - fn as_any(&self) -> &dyn Any { - self - } - - fn clone_box(&self) -> Box<dyn SerializableComponent> { - Box::new(self.clone()) - } - - fn init(&self, ctx: ComponentInitContext) -> ComponentInitFuture { - let value = self.clone(); - Box::pin(async move { - let graphics = ctx - .resources - .get::<Arc<SharedGraphicsContext>>() - .ok_or_else(|| anyhow::anyhow!("SharedGraphicsContext missing for MeshRenderer init"))?; - - let label = format!("Entity {:?}", ctx.entity); - let renderer = crate::utils::mesh_loader::load_mesh_renderer_from_serialized( - &value, - graphics.clone(), - &label, - ) - .await?; - let insert: Box<dyn dropbear_traits::ComponentInsert> = - Box::new(InsertBundle((renderer,))); - Ok(insert) - }) - } +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct SerializedMaterialCustomisation { + pub label: String, + pub diffuse_texture: Option<ResourceReference>, + pub tint: [f32; 4], + pub emissive_factor: [f32; 3], + pub metallic_factor: f32, + pub roughness_factor: f32, + pub alpha_mode: AlphaMode, + pub alpha_cutoff: Option<f32>, + pub double_sided: bool, + pub occlusion_strength: f32, + pub normal_scale: f32, + pub uv_tiling: [f32; 2], + pub texture_tag: Option<String>, + pub wrap_mode: TextureWrapMode, + pub emissive_texture: Option<ResourceReference>, + pub normal_texture: Option<ResourceReference>, + pub occlusion_texture: Option<ResourceReference>, + pub metallic_roughness_texture: Option<ResourceReference>, } @@ -1,3 +1,4 @@ +use std::sync::Arc; use crate::hierarchy::EntityTransformExt; use crate::ptr::WorldPtr; use crate::scripting::jni::utils::{FromJObject, ToJObject}; @@ -7,8 +8,54 @@ use dropbear_engine::entity::{EntityTransform, Transform}; use glam::{DQuat, DVec3}; use ::jni::objects::{JObject, JValue}; use ::jni::JNIEnv; +use egui::{CollapsingHeader, Ui}; +use hecs::{Entity, World}; +use dropbear_engine::graphics::SharedGraphicsContext; +use crate::component::{Component, ComponentDescriptor, SerializedComponent}; use crate::types::{NTransform, NVector3}; +#[typetag::serde] +impl SerializedComponent for EntityTransform {} + +impl Component for EntityTransform { + type SerializedForm = Self; + + fn descriptor() -> ComponentDescriptor { + ComponentDescriptor { + fqtn: "dropbear_engine::entity::EntityTransform".to_string(), + type_name: "EntityTransform".to_string(), + category: None, + description: Some("Allows the entity to have a space within the world".to_string()), + } + } + + async fn first_time(_: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + Ok(Self::default()) + } + + async fn init(ser: Self::SerializedForm, _: Arc<SharedGraphicsContext>) -> anyhow::Result<Self> { + Ok(ser) + } + + fn update_component(&mut self, _: &World, _: Entity, _: f32, _: Arc<SharedGraphicsContext>) {} + + fn save(&self, _: &World, _: Entity) -> Box<dyn SerializedComponent> { + Box::new(self.clone()) + } + + fn inspect(&mut self, ui: &mut Ui) { + CollapsingHeader::new("Entity Transform").show(ui, |ui| { + CollapsingHeader::new("Local").show(ui, |ui| { + self.local_mut().inspect(ui); + }); + ui.add_space(4.0); + CollapsingHeader::new("World").show(ui, |ui| { + self.world_mut().inspect(ui); + }); + }); + } +} + impl FromJObject for Transform { fn from_jobject(env: &mut JNIEnv, obj: &JObject) -> DropbearNativeResult<Self> { let pos_val = env.get_field(obj, "position", "Lcom/dropbear/math/Vector3d;") @@ -2,7 +2,6 @@ pub mod option; pub mod hashmap; -pub mod mesh_loader; use crate::states::Node; use dropbear_engine::utils::{ResourceReference, ResourceReferenceType, relative_path_from_euca}; @@ -1,84 +0,0 @@ -use std::sync::Arc; - -use dropbear_engine::asset::ASSET_REGISTRY; -use dropbear_engine::entity::MeshRenderer; -use dropbear_engine::graphics::SharedGraphicsContext; -use dropbear_engine::model::Model; -use dropbear_engine::procedural::ProcedurallyGeneratedObject; -use dropbear_engine::utils::{ResourceReference, ResourceReferenceType}; - -use crate::states::SerializedMeshRenderer; -use crate::utils::ResolveReference; - -pub async fn load_mesh_renderer_from_serialized( - serialized: &SerializedMeshRenderer, - graphics: Arc<SharedGraphicsContext>, - label: &str, -) -> anyhow::Result<MeshRenderer> { - let import_scale = serialized.import_scale.unwrap_or(1.0); - - let handle = match &serialized.handle.ref_type { - ResourceReferenceType::None => { - anyhow::bail!("Resource reference type is None for '{}'", label); - } - ResourceReferenceType::Unassigned { id } => { - let model = Model { - label: "None".to_string(), - hash: *id, - path: ResourceReference::from_reference(ResourceReferenceType::Unassigned { id: *id }), - meshes: Vec::new(), - materials: Vec::new(), - skins: Vec::new(), - animations: Vec::new(), - nodes: Vec::new(), - }; - - let mut registry = ASSET_REGISTRY.write(); - if let Some(existing) = registry.model_handle_by_hash(*id) { - existing - } else { - registry.add_model(model) - } - } - ResourceReferenceType::File(reference) => { - let path = serialized.handle.resolve()?; - log::debug!("Path for entity {} is {} from reference {}", label, path.display(), reference); - let buffer = std::fs::read(&path)?; - Model::load_from_memory_raw(graphics.clone(), buffer, Some(label), ASSET_REGISTRY.clone()).await? - } - ResourceReferenceType::Bytes(bytes) => { - log::info!("Loading entity '{}' from bytes [Len: {}]", label, bytes.len()); - Model::load_from_memory_raw(graphics.clone(), bytes, Some(label), ASSET_REGISTRY.clone()).await? - } - ResourceReferenceType::ProcObj(obj) => match obj { - dropbear_engine::procedural::ProcObj::Cuboid { size_bits } => { - let size = [ - f32::from_bits(size_bits[0]), - f32::from_bits(size_bits[1]), - f32::from_bits(size_bits[2]), - ]; - log::info!("Loading entity '{}' from cuboid: {:?}", label, size); - - let size_vec = glam::DVec3::new(size[0] as f64, size[1] as f64, size[2] as f64); - ProcedurallyGeneratedObject::cuboid(size_vec).build_model( - graphics.clone(), - None, - Some(label), - ASSET_REGISTRY.clone(), - ) - } - }, - }; - - let mut renderer = MeshRenderer::from_handle(handle); - renderer.set_import_scale(import_scale); - - if serialized.texture_override.is_some() { - log::debug!( - "texture_override is set for '{}' but not applied yet", - label - ); - } - - Ok(renderer) -} @@ -30,38 +30,6 @@ pub trait InspectableComponent { ); } -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ValueType { - String, - Float, - Int, - Bool, - Vec3, -} - -impl From<Value> for ValueType { - fn from(value: Value) -> Self { - match value { - Value::String(_) => ValueType::String, - Value::Int(_) => ValueType::Int, - Value::Double(_) => ValueType::Float, - Value::Bool(_) => ValueType::Bool, - Value::Vec3(_) => ValueType::Vec3, - } - } -} - -impl From<&mut Value> for ValueType { - fn from(value: &mut Value) -> Self { - match value { - Value::String(_) => ValueType::String, - Value::Int(_) => ValueType::Int, - Value::Double(_) => ValueType::Float, - Value::Bool(_) => ValueType::Bool, - Value::Vec3(_) => ValueType::Vec3, - } - } -} fn wrap_angle_degrees(angle: f64) -> f64 { (angle + 180.0).rem_euclid(360.0) - 180.0 @@ -82,128 +50,7 @@ impl InspectableComponent for CustomProperties { _signal: &mut Signal, _label: &mut String, ) { - ui.vertical(|ui| { - Grid::new("properties").striped(true).show(ui, |ui| { - ui.label(RichText::new("Key")); - ui.label(RichText::new("Type")); - ui.label(RichText::new("Value")); - ui.label(RichText::new("Action")); - ui.end_row(); - - let mut to_delete: Option<u64> = None; - let mut to_rename: Option<(u64, String)> = None; - - for (_i, property) in self.custom_properties.iter_mut().enumerate() { - let mut edited_key = property.key.clone(); - ui.add_sized([100.0, 20.0], TextEdit::singleline(&mut edited_key)); - - if edited_key != property.key { - to_rename = Some((property.id, edited_key)); - } - - let current_type = ValueType::from(&mut property.value); - let mut selected_type = current_type; - - ComboBox::from_id_salt(format!("type_{}", property.id)) - .selected_text(format!("{:?}", selected_type)) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut selected_type, - ValueType::String, - "String", - ); - ui.selectable_value(&mut selected_type, ValueType::Float, "Float"); - ui.selectable_value(&mut selected_type, ValueType::Int, "Int"); - ui.selectable_value(&mut selected_type, ValueType::Bool, "Bool"); - ui.selectable_value(&mut selected_type, ValueType::Vec3, "Vec3"); - }); - - if selected_type != current_type { - property.value = match selected_type { - ValueType::String => Value::String(String::new()), - ValueType::Float => Value::Double(0.0), - ValueType::Int => Value::Int(0), - ValueType::Bool => Value::Bool(false), - ValueType::Vec3 => Value::Vec3([0.0, 0.0, 0.0]), - }; - } - - let speed = { - let input = ui.input(|i| i.modifiers); - if input.shift { - 0.01 - } else if cfg!(target_os = "macos") && input.mac_cmd - || !cfg!(target_os = "macos") && input.ctrl - { - 1.0 - } else { - 0.1 - } - }; - - match &mut property.value { - Value::String(s) => { - ui.add_sized([100.0, 20.0], egui::TextEdit::singleline(s)); - } - Value::Int(n) => { - ui.add(DragValue::new(n).speed(1.0)); - } - Value::Double(f) => { - ui.add(DragValue::new(f).speed(speed)); - } - Value::Bool(b) => { - if ui.button(if *b { "✅" } else { "❌" }).clicked() { - *b = !*b; - } - } - Value::Vec3(v) => { - ui.horizontal(|ui| { - ui.add(DragValue::new(&mut v[0]).speed(speed)); - ui.add(DragValue::new(&mut v[1]).speed(speed)); - ui.add(DragValue::new(&mut v[2]).speed(speed)); - }); - } - } - - if ui.button("🗑️").clicked() { - log::debug!("Trashing {}", property.key); - to_delete = Some(property.id); - } - - ui.end_row(); - } - if let Some(id) = to_delete { - self.custom_properties.retain(|p| p.id != id); - } - - if let Some((id, new_key)) = to_rename { - if let Some(property) = - self.custom_properties.iter_mut().find(|p| p.id == id) - { - property.key = new_key; - } else { - warn!("Failed to rename property: id not found"); - } - } - - if ui.button("Add").clicked() { - log::debug!("Inserting new default value"); - let mut new_key = String::from("new_property"); - let mut counter = 1; - while self.custom_properties.iter().any(|p| p.key == new_key) { - new_key = format!("new_property_{}", counter); - counter += 1; - } - self.custom_properties.push(Property { - id: self.next_id, - key: new_key, - value: Value::default(), - }); - self.next_id += 1; - } - }); - }); } } @@ -68,6 +68,39 @@ typedef struct ColliderShapeFfi { typedef ColliderShapeFfi ColliderShape; +typedef enum NShapeCastStatusTag { + NShapeCastStatusTag_OutOfIterations = 0, + NShapeCastStatusTag_Converged = 1, + NShapeCastStatusTag_Failed = 2, + NShapeCastStatusTag_PenetratingOrWithinTargetDist = 3, +} NShapeCastStatusTag; + +typedef struct NShapeCastStatusOutOfIterations { +} NShapeCastStatusOutOfIterations; + +typedef struct NShapeCastStatusConverged { +} NShapeCastStatusConverged; + +typedef struct NShapeCastStatusFailed { +} NShapeCastStatusFailed; + +typedef struct NShapeCastStatusPenetratingOrWithinTargetDist { +} NShapeCastStatusPenetratingOrWithinTargetDist; + +typedef union NShapeCastStatusData { + NShapeCastStatusOutOfIterations OutOfIterations; + NShapeCastStatusConverged Converged; + NShapeCastStatusFailed Failed; + NShapeCastStatusPenetratingOrWithinTargetDist PenetratingOrWithinTargetDist; +} NShapeCastStatusData; + +typedef struct NShapeCastStatusFfi { + NShapeCastStatusTag tag; + NShapeCastStatusData data; +} NShapeCastStatusFfi; + +typedef NShapeCastStatusFfi NShapeCastStatus; + typedef enum NAnimationInterpolationTag { NAnimationInterpolationTag_Linear = 0, NAnimationInterpolationTag_Step = 1, @@ -146,58 +179,113 @@ typedef struct NChannelValuesFfi { typedef NChannelValuesFfi NChannelValues; -typedef enum NShapeCastStatusTag { - NShapeCastStatusTag_OutOfIterations = 0, - NShapeCastStatusTag_Converged = 1, - NShapeCastStatusTag_Failed = 2, - NShapeCastStatusTag_PenetratingOrWithinTargetDist = 3, -} NShapeCastStatusTag; - -typedef struct NShapeCastStatusOutOfIterations { -} NShapeCastStatusOutOfIterations; +typedef struct IndexNative { + uint32_t index; + uint32_t generation; +} IndexNative; -typedef struct NShapeCastStatusConverged { -} NShapeCastStatusConverged; +typedef struct NCollider { + IndexNative index; + uint64_t entity_id; + uint32_t id; +} NCollider; -typedef struct NShapeCastStatusFailed { -} NShapeCastStatusFailed; +typedef struct NShapeCastHit { + NCollider collider; + double distance; + NVector3 witness1; + NVector3 witness2; + NVector3 normal1; + NVector3 normal2; + NShapeCastStatus status; +} NShapeCastHit; -typedef struct NShapeCastStatusPenetratingOrWithinTargetDist { -} NShapeCastStatusPenetratingOrWithinTargetDist; +typedef struct RigidBodyContext { + IndexNative index; + uint64_t entity_id; +} RigidBodyContext; -typedef union NShapeCastStatusData { - NShapeCastStatusOutOfIterations OutOfIterations; - NShapeCastStatusConverged Converged; - NShapeCastStatusFailed Failed; - NShapeCastStatusPenetratingOrWithinTargetDist PenetratingOrWithinTargetDist; -} NShapeCastStatusData; +typedef struct AxisLock { + bool x; + bool y; + bool z; +} AxisLock; -typedef struct NShapeCastStatusFfi { - NShapeCastStatusTag tag; - NShapeCastStatusData data; -} NShapeCastStatusFfi; +typedef struct Progress { + size_t current; + size_t total; + const char* message; +} Progress; -typedef NShapeCastStatusFfi NShapeCastStatus; +typedef struct NColour { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} NColour; -typedef struct IndexNative { - uint32_t index; - uint32_t generation; -} IndexNative; +typedef struct NRange { + float start; + float end; +} NRange; -typedef struct IndexNativeArray { - IndexNative* values; +typedef struct i32Array { + int32_t* values; size_t length; size_t capacity; -} IndexNativeArray; +} i32Array; -typedef struct CharacterCollisionArray { - uint64_t entity_id; - IndexNativeArray collisions; -} CharacterCollisionArray; +typedef struct NNodeTransform { + NVector3 translation; + NQuaternion rotation; + NVector3 scale; +} NNodeTransform; + +typedef struct NNode { + const char* name; + const int32_t* parent; + i32Array children; + NNodeTransform transform; +} NNode; + +typedef struct NNodeArray { + NNode* values; + size_t length; + size_t capacity; +} NNodeArray; typedef void* WorldPtr; -typedef void* AssetRegistryPtr; +typedef struct NTransform { + NVector3 position; + NQuaternion rotation; + NVector3 scale; +} NTransform; + +typedef struct f64ArrayArray { + double* values; + size_t length; + size_t capacity; +} f64ArrayArray; + +typedef struct NSkin { + const char* name; + i32Array joints; + f64ArrayArray inverse_bind_matrices; + const int32_t* skeleton_root; +} NSkin; + +typedef struct NSkinArray { + NSkin* values; + size_t length; + size_t capacity; +} NSkinArray; + +typedef struct NColliderArray { + NCollider* values; + size_t length; + size_t capacity; +} NColliderArray; typedef struct f64Array { double* values; @@ -230,36 +318,43 @@ typedef struct NAnimationArray { size_t capacity; } NAnimationArray; -typedef struct NCollider { - IndexNative index; - uint64_t entity_id; - uint32_t id; -} NCollider; +typedef void* AssetRegistryPtr; -typedef struct NColliderArray { - NCollider* values; +typedef void* InputStatePtr; + +typedef struct IndexNativeArray { + IndexNative* values; size_t length; size_t capacity; -} NColliderArray; - -typedef void* CommandBufferPtr; +} IndexNativeArray; -typedef void* SceneLoaderPtr; +typedef struct CharacterCollisionArray { + uint64_t entity_id; + IndexNativeArray collisions; +} CharacterCollisionArray; typedef struct NVector2 { double x; double y; } NVector2; +typedef void* GraphicsContextPtr; + +typedef struct NAttenuation { + float constant; + float linear; + float quadratic; +} NAttenuation; + typedef struct u64Array { uint64_t* values; size_t length; size_t capacity; } u64Array; -typedef struct ConnectedGamepadIds { - u64Array ids; -} ConnectedGamepadIds; +typedef void* SceneLoaderPtr; + +typedef void* CommandBufferPtr; typedef struct NVector4 { double x; @@ -268,12 +363,6 @@ typedef struct NVector4 { double w; } NVector4; -typedef struct i32Array { - int32_t* values; - size_t length; - size_t capacity; -} i32Array; - typedef struct NModelVertex { NVector3 position; NVector3 normal; @@ -304,6 +393,11 @@ typedef struct NMeshArray { size_t capacity; } NMeshArray; +typedef struct RayHit { + NCollider collider; + double distance; +} RayHit; + typedef struct NMaterial { const char* name; uint64_t diffuse_texture; @@ -330,103 +424,9 @@ typedef struct NMaterialArray { typedef void* PhysicsStatePtr; -typedef struct NTransform { - NVector3 position; - NQuaternion rotation; - NVector3 scale; -} NTransform; - -typedef struct NNodeTransform { - NVector3 translation; - NQuaternion rotation; - NVector3 scale; -} NNodeTransform; - -typedef struct NNode { - const char* name; - const int32_t* parent; - i32Array children; - NNodeTransform transform; -} NNode; - -typedef struct NNodeArray { - NNode* values; - size_t length; - size_t capacity; -} NNodeArray; - -typedef struct NShapeCastHit { - NCollider collider; - double distance; - NVector3 witness1; - NVector3 witness2; - NVector3 normal1; - NVector3 normal2; - NShapeCastStatus status; -} NShapeCastHit; - -typedef struct NAttenuation { - float constant; - float linear; - float quadratic; -} NAttenuation; - -typedef struct Progress { - size_t current; - size_t total; - const char* message; -} Progress; - -typedef struct NRange { - float start; - float end; -} NRange; - -typedef struct f64ArrayArray { - double* values; - size_t length; - size_t capacity; -} f64ArrayArray; - -typedef struct NSkin { - const char* name; - i32Array joints; - f64ArrayArray inverse_bind_matrices; - const int32_t* skeleton_root; -} NSkin; - -typedef struct NSkinArray { - NSkin* values; - size_t length; - size_t capacity; -} NSkinArray; - -typedef struct RayHit { - NCollider collider; - double distance; -} RayHit; - -typedef struct RigidBodyContext { - IndexNative index; - uint64_t entity_id; -} RigidBodyContext; - -typedef struct AxisLock { - bool x; - bool y; - bool z; -} AxisLock; - -typedef void* InputStatePtr; - -typedef void* GraphicsContextPtr; - -typedef struct NColour { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; -} NColour; +typedef struct ConnectedGamepadIds { + u64Array ids; +} ConnectedGamepadIds; int32_t dropbear_gamepad_is_button_pressed(InputStatePtr input, uint64_t gamepad_id, int32_t button_ordinal, bool* out0); int32_t dropbear_gamepad_get_left_stick_position(InputStatePtr input, uint64_t gamepad_id, NVector2* out0); @@ -459,10 +459,6 @@ int32_t dropbear_collider_get_collider_translation(PhysicsStatePtr physics, cons int32_t dropbear_collider_set_collider_translation(PhysicsStatePtr physics, const NCollider* collider, const NVector3* translation); int32_t dropbear_collider_get_collider_rotation(PhysicsStatePtr physics, const NCollider* collider, NVector3* out0); int32_t dropbear_collider_set_collider_rotation(PhysicsStatePtr physics, const NCollider* collider, const NVector3* rotation); -int32_t dropbear_kcc_kcc_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0); -int32_t dropbear_kcc_move_character(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NVector3* translation, double delta_time); -int32_t dropbear_kcc_set_rotation(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NQuaternion* rotation); -int32_t dropbear_kcc_get_hit(WorldPtr world, uint64_t entity, CharacterCollisionArray* out0); int32_t dropbear_rigidbody_exists_for_entity(WorldPtr world, PhysicsStatePtr physics, uint64_t entity, IndexNative* out0, bool* out0_present); int32_t dropbear_rigidbody_get_rigidbody_mode(WorldPtr _world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, int32_t* out0); int32_t dropbear_rigidbody_set_rigidbody_mode(WorldPtr world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, int32_t mode); @@ -487,6 +483,10 @@ int32_t dropbear_rigidbody_set_rigidbody_lock_rotation(WorldPtr world, PhysicsSt int32_t dropbear_rigidbody_get_rigidbody_children(WorldPtr _world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, NColliderArray* out0); int32_t dropbear_rigidbody_apply_impulse(WorldPtr world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, double x, double y, double z); int32_t dropbear_rigidbody_apply_torque_impulse(WorldPtr world, PhysicsStatePtr physics, const RigidBodyContext* rigidbody, double x, double y, double z); +int32_t dropbear_kcc_kcc_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0); +int32_t dropbear_kcc_move_character(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NVector3* translation, double delta_time); +int32_t dropbear_kcc_set_rotation(WorldPtr world, PhysicsStatePtr physics_state, uint64_t entity, const NQuaternion* rotation); +int32_t dropbear_kcc_get_hit(WorldPtr world, uint64_t entity, CharacterCollisionArray* out0); int32_t dropbear_scripting_load_scene_async(CommandBufferPtr command_buffer, SceneLoaderPtr scene_loader, const char* scene_name, uint64_t* out0); int32_t dropbear_scripting_load_scene_async_with_loading(CommandBufferPtr command_buffer, SceneLoaderPtr scene_loader, const char* scene_name, const char* loading_scene, uint64_t* out0); int32_t dropbear_scripting_switch_to_scene_immediate(CommandBufferPtr command_buffer, const char* scene_name); @@ -494,40 +494,6 @@ int32_t dropbear_scripting_get_scene_load_handle_scene_name(SceneLoaderPtr scene int32_t dropbear_scripting_switch_to_scene_async(CommandBufferPtr command_buffer, SceneLoaderPtr scene_loader, uint64_t scene_id); int32_t dropbear_scripting_get_scene_load_progress(SceneLoaderPtr scene_loader, uint64_t scene_id, Progress* out0); int32_t dropbear_scripting_get_scene_load_status(SceneLoaderPtr scene_loader, uint64_t scene_id, uint32_t* out0); -int32_t dropbear_engine_get_asset(AssetRegistryPtr asset, const char* label, const AssetKind* kind, uint64_t* out0, bool* out0_present); -int32_t dropbear_asset_model_get_label(AssetRegistryPtr asset, uint64_t model_handle, char** out0); -int32_t dropbear_asset_model_get_meshes(AssetRegistryPtr asset, uint64_t model_handle, NMeshArray* out0); -int32_t dropbear_asset_model_get_materials(AssetRegistryPtr asset, uint64_t model_handle, NMaterialArray* out0); -int32_t dropbear_asset_model_get_skins(AssetRegistryPtr asset, uint64_t model_handle, NSkinArray* out0); -int32_t dropbear_asset_model_get_animations(AssetRegistryPtr asset, uint64_t model_handle, NAnimationArray* out0); -int32_t dropbear_asset_model_get_nodes(AssetRegistryPtr asset, uint64_t model_handle, NNodeArray* out0); -int32_t dropbear_asset_texture_get_label(AssetRegistryPtr asset_manager, uint64_t texture_handle, char** out0, bool* out0_present); -int32_t dropbear_asset_texture_get_width(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0); -int32_t dropbear_asset_texture_get_height(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0); -int32_t dropbear_camera_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0); -int32_t dropbear_camera_get_eye(WorldPtr world, uint64_t entity, NVector3* out0); -int32_t dropbear_camera_set_eye(WorldPtr world, uint64_t entity, const NVector3* eye); -int32_t dropbear_camera_get_target(WorldPtr world, uint64_t entity, NVector3* out0); -int32_t dropbear_camera_set_target(WorldPtr world, uint64_t entity, const NVector3* target); -int32_t dropbear_camera_get_up(WorldPtr world, uint64_t entity, NVector3* out0); -int32_t dropbear_camera_set_up(WorldPtr world, uint64_t entity, const NVector3* up); -int32_t dropbear_camera_get_aspect(WorldPtr world, uint64_t entity, double* out0); -int32_t dropbear_camera_get_fovy(WorldPtr world, uint64_t entity, double* out0); -int32_t dropbear_camera_set_fovy(WorldPtr world, uint64_t entity, double fovy); -int32_t dropbear_camera_get_znear(WorldPtr world, uint64_t entity, double* out0); -int32_t dropbear_camera_set_znear(WorldPtr world, uint64_t entity, double znear); -int32_t dropbear_camera_get_zfar(WorldPtr world, uint64_t entity, double* out0); -int32_t dropbear_camera_set_zfar(WorldPtr world, uint64_t entity, double zfar); -int32_t dropbear_camera_get_yaw(WorldPtr world, uint64_t entity, double* out0); -int32_t dropbear_camera_set_yaw(WorldPtr world, uint64_t entity, double yaw); -int32_t dropbear_camera_get_pitch(WorldPtr world, uint64_t entity, double* out0); -int32_t dropbear_camera_set_pitch(WorldPtr world, uint64_t entity, double pitch); -int32_t dropbear_camera_get_speed(WorldPtr world, uint64_t entity, double* out0); -int32_t dropbear_camera_set_speed(WorldPtr world, uint64_t entity, double speed); -int32_t dropbear_camera_get_sensitivity(WorldPtr world, uint64_t entity, double* out0); -int32_t dropbear_camera_set_sensitivity(WorldPtr world, uint64_t entity, double sensitivity); -int32_t dropbear_engine_get_entity(WorldPtr world, const char* label, uint64_t* out0); -int32_t dropbear_engine_quit(CommandBufferPtr command_buffer); int32_t dropbear_entity_label_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0); int32_t dropbear_entity_get_label(WorldPtr world, uint64_t entity, char** out0); int32_t dropbear_entity_get_children(WorldPtr world, uint64_t entity, u64Array* out0); @@ -567,9 +533,6 @@ int32_t dropbear_lighting_get_casts_shadows(WorldPtr world, uint64_t entity, boo int32_t dropbear_lighting_set_casts_shadows(WorldPtr world, uint64_t entity, bool casts_shadows); int32_t dropbear_lighting_get_depth(WorldPtr world, uint64_t entity, NRange* out0); int32_t dropbear_lighting_set_depth(WorldPtr world, uint64_t entity, const NRange* depth); -int32_t dropbear_mesh_get_texture(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t* out0, bool* out0_present); -int32_t dropbear_mesh_set_texture_override(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t texture_handle); -int32_t dropbear_mesh_set_material_tint(WorldPtr world, AssetRegistryPtr asset, GraphicsContextPtr graphics, uint64_t entity, const char* material_name, float r, float g, float b, float a); int32_t dropbear_physics_get_gravity(PhysicsStatePtr physics, NVector3* out0); int32_t dropbear_physics_set_gravity(PhysicsStatePtr physics, const NVector3* gravity); int32_t dropbear_physics_raycast(PhysicsStatePtr physics, const NVector3* origin, const NVector3* dir, double time_of_impact, bool solid, RayHit* out0, bool* out0_present); @@ -598,5 +561,42 @@ int32_t dropbear_transform_set_local_transform(WorldPtr world, uint64_t entity, int32_t dropbear_transform_get_world_transform(WorldPtr world, uint64_t entity, NTransform* out0); int32_t dropbear_transform_set_world_transform(WorldPtr world, uint64_t entity, const NTransform* transform); int32_t dropbear_transform_propogate_transform(WorldPtr world, uint64_t entity, NTransform* out0); +int32_t dropbear_engine_get_entity(WorldPtr world, const char* label, uint64_t* out0); +int32_t dropbear_engine_quit(CommandBufferPtr command_buffer); +int32_t dropbear_engine_get_asset(AssetRegistryPtr asset, const char* label, const AssetKind* kind, uint64_t* out0, bool* out0_present); +int32_t dropbear_asset_model_get_label(AssetRegistryPtr asset, uint64_t model_handle, char** out0); +int32_t dropbear_asset_model_get_meshes(AssetRegistryPtr asset, uint64_t model_handle, NMeshArray* out0); +int32_t dropbear_asset_model_get_materials(AssetRegistryPtr asset, uint64_t model_handle, NMaterialArray* out0); +int32_t dropbear_asset_model_get_skins(AssetRegistryPtr asset, uint64_t model_handle, NSkinArray* out0); +int32_t dropbear_asset_model_get_animations(AssetRegistryPtr asset, uint64_t model_handle, NAnimationArray* out0); +int32_t dropbear_asset_model_get_nodes(AssetRegistryPtr asset, uint64_t model_handle, NNodeArray* out0); +int32_t dropbear_asset_texture_get_label(AssetRegistryPtr asset_manager, uint64_t texture_handle, char** out0, bool* out0_present); +int32_t dropbear_asset_texture_get_width(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0); +int32_t dropbear_asset_texture_get_height(AssetRegistryPtr asset_manager, uint64_t texture_handle, uint32_t* out0); +int32_t dropbear_mesh_get_texture(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t* out0, bool* out0_present); +int32_t dropbear_mesh_set_texture_override(WorldPtr world, AssetRegistryPtr asset, uint64_t entity, const char* material_name, uint64_t texture_handle); +int32_t dropbear_mesh_set_material_tint(WorldPtr world, AssetRegistryPtr asset, GraphicsContextPtr graphics, uint64_t entity, const char* material_name, float r, float g, float b, float a); +int32_t dropbear_camera_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0); +int32_t dropbear_camera_get_eye(WorldPtr world, uint64_t entity, NVector3* out0); +int32_t dropbear_camera_set_eye(WorldPtr world, uint64_t entity, const NVector3* eye); +int32_t dropbear_camera_get_target(WorldPtr world, uint64_t entity, NVector3* out0); +int32_t dropbear_camera_set_target(WorldPtr world, uint64_t entity, const NVector3* target); +int32_t dropbear_camera_get_up(WorldPtr world, uint64_t entity, NVector3* out0); +int32_t dropbear_camera_set_up(WorldPtr world, uint64_t entity, const NVector3* up); +int32_t dropbear_camera_get_aspect(WorldPtr world, uint64_t entity, double* out0); +int32_t dropbear_camera_get_fovy(WorldPtr world, uint64_t entity, double* out0); +int32_t dropbear_camera_set_fovy(WorldPtr world, uint64_t entity, double fovy); +int32_t dropbear_camera_get_znear(WorldPtr world, uint64_t entity, double* out0); +int32_t dropbear_camera_set_znear(WorldPtr world, uint64_t entity, double znear); +int32_t dropbear_camera_get_zfar(WorldPtr world, uint64_t entity, double* out0); +int32_t dropbear_camera_set_zfar(WorldPtr world, uint64_t entity, double zfar); +int32_t dropbear_camera_get_yaw(WorldPtr world, uint64_t entity, double* out0); +int32_t dropbear_camera_set_yaw(WorldPtr world, uint64_t entity, double yaw); +int32_t dropbear_camera_get_pitch(WorldPtr world, uint64_t entity, double* out0); +int32_t dropbear_camera_set_pitch(WorldPtr world, uint64_t entity, double pitch); +int32_t dropbear_camera_get_speed(WorldPtr world, uint64_t entity, double* out0); +int32_t dropbear_camera_set_speed(WorldPtr world, uint64_t entity, double speed); +int32_t dropbear_camera_get_sensitivity(WorldPtr world, uint64_t entity, double* out0); +int32_t dropbear_camera_set_sensitivity(WorldPtr world, uint64_t entity, double sensitivity); #endif /* DROPBEAR_H */