tirbofish/dropbear · commit
d57ac5490eeaa463537294cd7e82d43f8df241be
a bunch of fixes amirite
Signature present but could not be verified.
Unverified
@@ -88,6 +88,7 @@ features = "0.10" puffin_http = "0.16" dyn-clone = "1.0" downcast-rs = "2.0" +float-derive = "0.1" [workspace.dependencies.image] version = "0.24" @@ -47,6 +47,7 @@ image.workspace = true puffin.workspace = true bitflags.workspace = true puffin_http.workspace = true +float-derive.workspace = true #yakui-wgpu.workspace = true #yakui.workspace = true @@ -112,9 +112,9 @@ impl AnimationComponent { transform.translation = match channel.interpolation { AnimationInterpolation::Step => values[prev_idx], AnimationInterpolation::Linear => { - let start = values[prev_idx]; - let end = values[next_idx]; - start.lerp(end, factor) + let start = values[prev_idx]; + let end = values[next_idx]; + start.lerp(end, factor) }, AnimationInterpolation::CubicSpline => { let t = factor; @@ -283,6 +283,26 @@ impl AssetRegistry { pub fn get_label_from_model_handle(&self, handle: Handle<Model>) -> Option<String> { self.model_labels.iter().find_map(|(label, h)| if *h == handle { Some(label.clone()) } else { None }) } + + /// Returns a list of all loaded textures with their handles, labels, and references. + pub fn list_textures(&self) -> Vec<(Handle<Texture>, Option<String>, Option<ResourceReference>)> { + self.textures + .values() + .map(|texture| ( + texture.hash.map(Handle::new).unwrap_or(Handle::NULL), + texture.label.clone(), + texture.reference.clone(), + )) + .collect() + } + + /// Finds a texture handle by its resource reference. + pub fn get_texture_handle_by_reference(&self, reference: &ResourceReference) -> Option<Handle<Texture>> { + self.textures + .values() + .find(|texture| texture.reference.as_ref() == Some(reference)) + .and_then(|texture| texture.hash.map(Handle::new)) + } } impl Default for AssetRegistry { @@ -5,7 +5,6 @@ use crate::{ utils::{ResourceReference}, texture::{Texture, TextureWrapMode} }; -// use image::GenericImageView; use parking_lot::{RwLock}; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -1325,7 +1324,7 @@ pub trait Vertex { /// }; /// ``` #[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable, Serialize, Deserialize)] pub struct ModelVertex { pub position: [f32; 3], pub normal: [f32; 3], @@ -1397,6 +1396,35 @@ impl Vertex for ModelVertex { } } +impl PartialEq for ModelVertex { + fn eq(&self, other: &Self) -> bool { + self.position.map(f32::to_bits) == other.position.map(f32::to_bits) + && self.normal.map(f32::to_bits) == other.normal.map(f32::to_bits) + && self.tangent.map(f32::to_bits) == other.tangent.map(f32::to_bits) + && self.tex_coords0.map(f32::to_bits) == other.tex_coords0.map(f32::to_bits) + && self.tex_coords1.map(f32::to_bits) == other.tex_coords1.map(f32::to_bits) + && self.colour0.map(f32::to_bits) == other.colour0.map(f32::to_bits) + && self.joints0 == other.joints0 + && self.weights0.map(f32::to_bits) == other.weights0.map(f32::to_bits) + } +} + +// Eq is just a marker trait — no methods needed +impl Eq for ModelVertex {} + +impl Hash for ModelVertex { + fn hash<H: Hasher>(&self, state: &mut H) { + for v in &self.position { v.to_bits().hash(state); } + for v in &self.normal { v.to_bits().hash(state); } + for v in &self.tangent { v.to_bits().hash(state); } + for v in &self.tex_coords0{ v.to_bits().hash(state); } + for v in &self.tex_coords1{ v.to_bits().hash(state); } + for v in &self.colour0 { v.to_bits().hash(state); } + self.joints0.hash(state); + for v in &self.weights0 { v.to_bits().hash(state); } + } +} + #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct MaterialUniform { @@ -2,12 +2,12 @@ use std::sync::Arc; use std::mem::size_of; use glam::DMat4; use slank::include_slang; -use wgpu::{BufferAddress, CompareFunction, DepthBiasState, StencilState, VertexAttribute, VertexFormat}; +use wgpu::{BufferAddress, CompareFunction, DepthBiasState, StencilState}; use crate::buffer::{StorageBuffer}; use crate::entity::{EntityTransform, Transform}; use crate::graphics::SharedGraphicsContext; use crate::lighting::{Light, LightArrayUniform, LightComponent, MAX_LIGHTS}; -use crate::model::Vertex; +use crate::model::{ModelVertex, Vertex}; use crate::pipelines::DropbearShaderPipeline; use crate::shader::Shader; use crate::texture::Texture; @@ -44,7 +44,7 @@ impl DropbearShaderPipeline for LightCubePipeline { compilation_options: Default::default(), buffers: &[ // model - VertexInput::desc(), + ModelVertex::desc(), // instance InstanceInput::desc(), ], @@ -178,29 +178,6 @@ impl LightCubePipeline { } } -#[repr(C)] -#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -pub struct VertexInput { - position: [f32; 3], -} - -impl Vertex for VertexInput { - fn desc() -> wgpu::VertexBufferLayout<'static> { - wgpu::VertexBufferLayout { - array_stride: size_of::<VertexInput>() as BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - // position - VertexAttribute { - format: VertexFormat::Float32x3, - offset: 0, - shader_location: 0, - } - ], - } - } -} - /// As mapped in `shaders/light.slang` as /// ```wgsl /// struct InstanceInput { @@ -72,6 +72,6 @@ impl ProcedurallyGeneratedObject { 20, 21, 22, 22, 23, 20, // left ]; - Self { vertices, indices } + Self { vertices, indices, ty: crate::procedural::ProcObjType::Cuboid } } } @@ -13,57 +13,51 @@ use serde::{Deserialize, Serialize}; use wgpu::util::DeviceExt; pub mod cube; -// pub mod plane; #[derive( Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize )] -pub enum ProcObj { - /// A parameterized cuboid (box) generated at runtime. - /// - /// Stored as IEEE-754 `f32` bit patterns so the reference remains hashable. - /// Values can be reconstructed with `f32::from_bits`. - /// - /// The `size_bits` represent the full extents (width, height, depth). - Cuboid { size_bits: [u32; 3] }, +pub enum ProcObjType { + Cuboid, } /// An object that comes with a template, and is generated through parameter input. +#[derive( + Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize +)] pub struct ProcedurallyGeneratedObject { pub vertices: Vec<ModelVertex>, pub indices: Vec<u32>, + pub ty: ProcObjType, } impl ProcedurallyGeneratedObject { - /// Builds a GPU-backed model from this procedural mesh. - /// - /// - `material`: optional material; when `None`, a cached 1x1 grey texture is used. - /// - `label`: optional cache label; when `None`, a stable hash-based label is generated. - pub fn build_model( - self, + /// Constructs a [`Model`] and returns the model itself instead of adding to the registry. + pub fn construct( + &self, graphics: Arc<SharedGraphicsContext>, material: Option<Material>, label: Option<&str>, + hash: Option<u64>, registry: Arc<RwLock<AssetRegistry>>, - ) -> Handle<Model> { - puffin::profile_function!(); - let mut hasher = DefaultHasher::new(); - hasher.write(bytemuck::cast_slice(&self.vertices)); - hasher.write(bytemuck::cast_slice(&self.indices)); - let hash = hasher.finish(); + ) -> Model { + let mut _rguard = registry.write(); + + let hash = if let Some(hash) = hash { + hash + } else { + let mut hasher = DefaultHasher::new(); + hasher.write(bytemuck::cast_slice(&self.vertices)); + hasher.write(bytemuck::cast_slice(&self.indices)); + hasher.finish() + }; let label = label .map(|s| s.to_string()) .unwrap_or_else(|| format!("procedural_{hash:016x}")); - let mut _rguard = registry.write(); - - if let Some(handle) = _rguard.model_handle_by_hash(hash) { - return handle; - } - - let vertices = self.vertices; - let indices = self.indices; + let vertices = self.vertices.clone(); + let indices = self.indices.clone(); let vertex_buffer = graphics .device @@ -115,7 +109,7 @@ impl ProcedurallyGeneratedObject { let model = Model { label: label.clone(), hash, - path: ResourceReference::from_bytes(hash.to_le_bytes()), + path: ResourceReference::from_reference(crate::utils::ResourceReferenceType::ProcObj(self.clone())), meshes: vec![mesh], materials: vec![material], skins: Vec::new(), @@ -123,6 +117,43 @@ impl ProcedurallyGeneratedObject { nodes: Vec::new(), }; - _rguard.add_model_with_label(label, model) + model + } + + /// Builds a GPU-backed model from this procedural mesh. + /// + /// - `material`: optional material; when `None`, a cached 1x1 grey texture is used. + /// - `label`: optional cache label; when `None`, a stable hash-based label is generated. + pub fn build_model( + &self, + graphics: Arc<SharedGraphicsContext>, + material: Option<Material>, + label: Option<&str>, + registry: Arc<RwLock<AssetRegistry>>, + ) -> Handle<Model> { + puffin::profile_function!(); + let mut hasher = DefaultHasher::new(); + hasher.write(bytemuck::cast_slice(&self.vertices)); + hasher.write(bytemuck::cast_slice(&self.indices)); + let hash = hasher.finish(); + + let label_str = label + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("procedural_{hash:016x}")); + + { + let mut _rguard = registry.read(); + + if let Some(handle) = _rguard.model_handle_by_hash(hash) { + return handle; + } + } + + let model = Self::construct(&self, graphics, material, label, Some(hash), registry.clone()); + + { + let mut _rguard = registry.write(); + _rguard.add_model_with_label(label_str, model) + } } } @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::path::Path; -use crate::procedural::ProcObj; +use crate::procedural::{ProcedurallyGeneratedObject}; pub const EUCA_SCHEME: &str = "euca://"; @@ -103,7 +103,7 @@ pub enum ResourceReferenceType { /// An object that can be generated at runtime with the usage of vertices and indices, as well /// as a solid grey mesh. - ProcObj(ProcObj), + ProcObj(ProcedurallyGeneratedObject), } impl Default for ResourceReferenceType { @@ -58,6 +58,7 @@ impl Component for Camera { if let Ok((cam, comp)) = world.query_one::<(&Camera, &CameraComponent)>(entity).get() { Box::new(SerializableCamera::from_ecs_camera(cam, comp)) } else { + crate::warn!("Unable to save Camera3D's properties: Not found within world"); Box::new(SerializableCamera::default()) } } @@ -69,11 +70,6 @@ impl InspectableComponent for Camera { let mut changed = false; ui.horizontal(|ui| { - ui.label("Label"); - changed |= ui.text_edit_singleline(&mut self.label).changed(); - }); - - ui.horizontal(|ui| { ui.label("Eye"); changed |= ui.add(egui::DragValue::new(&mut self.eye.x).speed(0.1)).changed(); changed |= ui.add(egui::DragValue::new(&mut self.eye.y).speed(0.1)).changed(); @@ -1,16 +1,19 @@ use std::any::TypeId; use std::collections::HashMap; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::time::{SystemTime, UNIX_EPOCH}; use std::future::Future; use std::pin::Pin; use std::sync::Arc; -use egui::{CollapsingHeader, ComboBox, DragValue, RichText, Ui, UiBuilder}; +use egui::{CollapsingHeader, ComboBox, DragValue, RichText, UiBuilder}; use hecs::{Entity, World}; -use serde::{Deserialize, Serialize}; +pub 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::{ProcObj, ProcedurallyGeneratedObject}; +use dropbear_engine::procedural::{ProcObjType, ProcedurallyGeneratedObject}; use dropbear_engine::texture::Texture; use dropbear_engine::utils::{ResourceReference, ResourceReferenceType}; use crate::hierarchy::EntityTransformExt; @@ -502,27 +505,8 @@ impl Component for MeshRenderer { ) .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(), - ) - } + ResourceReferenceType::ProcObj(obj) => { + obj.build_model(graphics.clone(), None, None, ASSET_REGISTRY.clone()) }, }; @@ -596,76 +580,30 @@ impl Component for MeshRenderer { fn update_component(&mut self, world: &World, _physics: &mut PhysicsState, entity: Entity, _dt: f32, _graphics: Arc<SharedGraphicsContext>) { if let Ok(transform) = world.query_one::<&EntityTransform>(entity).get() { - self.update(&transform.propagate(&world, entity)) + self.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 (label, handle) = if let Some(model) = model { + (model.label.clone(), model.path.clone()) + } else { + if !self.model().is_null() { + log::warn!("MeshRenderer save: missing model handle {} in registry", self.model().id); + } + ("None".to_string(), ResourceReference::default()) + }; 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 - } - }) - }); - + // Save texture references directly from the material snapshot + let diffuse_texture = mat.diffuse_texture.reference.clone(); + let normal_texture = mat.normal_texture.reference.clone(); + let emissive_texture = mat.emissive_texture.as_ref().and_then(|t| t.reference.clone()); + let occlusion_texture = mat.occlusion_texture.as_ref().and_then(|t| t.reference.clone()); + let metallic_roughness_texture = mat.metallic_roughness_texture.as_ref().and_then(|t| t.reference.clone()); texture_override.insert(label.to_string(), SerializedMaterialCustomisation { label: label.clone(), @@ -690,9 +628,9 @@ impl Component for MeshRenderer { } Box::new(SerializedMeshRenderer { - label: model.unwrap().label.clone(), - handle: Default::default(), - import_scale: None, + label, + handle, + import_scale: Some(self.import_scale()), texture_override, }) } @@ -708,28 +646,103 @@ impl InspectableComponent for MeshRenderer { || uri.ends_with(".fbx") } - let apply_cuboid = |renderer: &mut MeshRenderer, size: [f32; 3]| { - let size_vec = glam::DVec3::new(size[0] as f64, size[1] as f64, size[2] as f64); - let handle = ProcedurallyGeneratedObject::cuboid(size_vec).build_model( - graphics.clone(), - None, - Some("Cuboid"), - ASSET_REGISTRY.clone(), - ); - - let size_bits = [size[0].to_bits(), size[1].to_bits(), size[2].to_bits()]; - { - let mut registry = ASSET_REGISTRY.write(); - if let Some(model) = registry.get_model(handle).cloned() { - let mut model = model; - model.path = ResourceReference::from_reference(ResourceReferenceType::ProcObj( - ProcObj::Cuboid { size_bits }, - )); - registry.update_model(handle, model); + fn is_probably_texture_uri(uri: &str) -> bool { + let uri = uri.to_ascii_lowercase(); + uri.ends_with(".png") + || uri.ends_with(".jpg") + || uri.ends_with(".jpeg") + || uri.ends_with(".tga") + || uri.ends_with(".bmp") + || uri.ends_with(".webp") + } + + fn proc_obj_size(obj: &ProcedurallyGeneratedObject) -> Option<[f32; 3]> { + if obj.ty != ProcObjType::Cuboid { + return None; + } + + let mut min = [f32::INFINITY; 3]; + let mut max = [f32::NEG_INFINITY; 3]; + for v in &obj.vertices { + let pos = v.position; + for i in 0..3 { + min[i] = min[i].min(pos[i]); + max[i] = max[i].max(pos[i]); } } - renderer.set_model(handle); + if min.iter().any(|v| !v.is_finite()) || max.iter().any(|v| !v.is_finite()) { + return None; + } + + Some([max[0] - min[0], max[1] - min[1], max[2] - min[2]]) + } + + let apply_cuboid = |renderer: &mut MeshRenderer, size: [f32; 3], force_new: bool| { + let size_vec = glam::DVec3::new(size[0] as f64, size[1] as f64, size[2] as f64); + let current_model = renderer.model(); + + let proc_obj = ProcedurallyGeneratedObject::cuboid(size_vec); + if force_new { + let mut model = proc_obj.construct( + graphics.clone(), + None, + None, + None, + ASSET_REGISTRY.clone(), + ); + let mut hasher = DefaultHasher::new(); + model.hash.hash(&mut hasher); + if let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) { + duration.as_nanos().hash(&mut hasher); + } + let new_hash = hasher.finish(); + model.hash = new_hash; + model.label = format!("Cuboid {:016x}", new_hash); + + let handle = { + let mut asset = ASSET_REGISTRY.write(); + asset.add_model(model) + }; + renderer.set_model(handle); + } else if current_model.is_null() { + let handle = proc_obj.build_model( + graphics.clone(), + None, + None, + ASSET_REGISTRY.clone(), + ); + renderer.set_model(handle); + } else { + let existing_label = { + let asset = ASSET_REGISTRY.read(); + asset.get_model(current_model).map(|model| model.label.clone()) + }; + + if let Some(existing_label) = existing_label { + let mut model = proc_obj.construct( + graphics.clone(), + None, + None, + None, + ASSET_REGISTRY.clone(), + ); + model.hash = current_model.id; + model.label = existing_label; + + let mut asset = ASSET_REGISTRY.write(); + asset.update_model(current_model, model); + } else { + let handle = proc_obj.build_model( + graphics.clone(), + None, + None, + ASSET_REGISTRY.clone(), + ); + renderer.set_model(handle); + } + } + renderer.reset_texture_override(); }; @@ -750,12 +763,14 @@ impl InspectableComponent for MeshRenderer { let model_title = match &model_reference.ref_type { ResourceReferenceType::None => "None".to_string(), - ResourceReferenceType::ProcObj(ProcObj::Cuboid { .. }) => "Cuboid".to_string(), + ResourceReferenceType::ProcObj(obj) => match obj.ty { + ProcObjType::Cuboid => "Cuboid".to_string(), + }, _ => model_label, }; ui.vertical(|ui| { - let expand_id = ui.make_persistent_id(format!("mesh_renderer_expand_{}", self.model().id)); + let expand_id = ui.make_persistent_id("mesh_renderer_expand"); let mut expanded = ui .ctx() .data_mut(|d| d.get_temp::<bool>(expand_id).unwrap_or(false)); @@ -764,13 +779,25 @@ impl InspectableComponent for MeshRenderer { let mut choose_proc_cuboid = false; let mut choose_none = false; + let drag_id = egui::Id::new(DRAGGED_ASSET_ID); + let dragging_valid_model = ui.ctx().data_mut(|d| { + d.get_temp::<Option<ResourceReference>>(drag_id) + .unwrap_or(None) + .and_then(|r| r.as_uri().map(|u| is_probably_model_uri(u))) + .unwrap_or(false) + }); + let (rect, response) = ui.allocate_exact_size( egui::vec2(ui.available_width(), 72.0), egui::Sense::click(), ); - let fill = if response.hovered() { + let fill = if dragging_valid_model && response.hovered() { + ui.visuals().selection.bg_fill + } else if response.hovered() { ui.visuals().widgets.hovered.bg_fill + } else if dragging_valid_model { + ui.visuals().widgets.active.bg_fill } else { ui.visuals().widgets.inactive.bg_fill }; @@ -824,7 +851,7 @@ impl InspectableComponent for MeshRenderer { .selectable_label( matches!( model_reference.ref_type, - ResourceReferenceType::ProcObj(ProcObj::Cuboid { .. }) + ResourceReferenceType::ProcObj(_) ), "Cuboid", ) @@ -875,14 +902,12 @@ impl InspectableComponent for MeshRenderer { if choose_proc_cuboid { let default_size = match &model_reference.ref_type { - ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits }) => [ - f32::from_bits(size_bits[0]), - f32::from_bits(size_bits[1]), - f32::from_bits(size_bits[2]), - ], + ResourceReferenceType::ProcObj(obj) => { + proc_obj_size(obj).unwrap_or([1.0, 1.0, 1.0]) + } _ => [1.0, 1.0, 1.0], }; - apply_cuboid(self, default_size); + apply_cuboid(self, default_size, true); } else if choose_none { self.set_model(Handle::NULL); self.reset_texture_override(); @@ -917,15 +942,10 @@ impl InspectableComponent for MeshRenderer { } } - if let ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits }) = &model_reference.ref_type { - let mut size = [ - f32::from_bits(size_bits[0]), - f32::from_bits(size_bits[1]), - f32::from_bits(size_bits[2]), - ]; - - ui.label(RichText::new("Cuboid").strong()); - ui.horizontal(|ui| { + if let ResourceReferenceType::ProcObj(obj) = &model_reference.ref_type { + if let Some(mut size) = proc_obj_size(obj) { + ui.label(RichText::new("Cuboid").strong()); + ui.horizontal(|ui| { ui.label("Extents:"); let mut changed = false; ui.label("X"); @@ -942,11 +962,31 @@ impl InspectableComponent for MeshRenderer { .changed(); if changed { - apply_cuboid(self, size); + // Preserve material customizations across cuboid size change + let saved_materials = self.material_snapshot.clone(); + apply_cuboid(self, size, false); + // Re-apply material customizations to the new model + for (name, saved_mat) in saved_materials { + if let Some(mat) = self.material_snapshot.get_mut(&name) { + mat.tint = saved_mat.tint; + mat.emissive_factor = saved_mat.emissive_factor; + mat.metallic_factor = saved_mat.metallic_factor; + mat.roughness_factor = saved_mat.roughness_factor; + mat.alpha_mode = saved_mat.alpha_mode; + mat.alpha_cutoff = saved_mat.alpha_cutoff; + mat.double_sided = saved_mat.double_sided; + mat.occlusion_strength = saved_mat.occlusion_strength; + mat.normal_scale = saved_mat.normal_scale; + mat.uv_tiling = saved_mat.uv_tiling; + mat.wrap_mode = saved_mat.wrap_mode; + mat.texture_tag = saved_mat.texture_tag.clone(); + } + } } }); ui.separator(); + } } ui.label("Materials"); @@ -1089,6 +1129,339 @@ impl InspectableComponent for MeshRenderer { Some(texture_tag) }; } + + // Texture customization slots + ui.add_space(8.0); + ui.label(RichText::new("Textures").strong()); + + // Check if a valid texture is being dragged + let drag_id = egui::Id::new(DRAGGED_ASSET_ID); + let dragging_valid_texture = ui.ctx().data_mut(|d| { + d.get_temp::<Option<ResourceReference>>(drag_id) + .unwrap_or(None) + .and_then(|r| r.as_uri().map(|u| is_probably_texture_uri(u))) + .unwrap_or(false) + }); + + // Helper to create texture slot UI + let texture_slot = |ui: &mut egui::Ui, + label: &str, + _id_salt: &str, + current_texture: &Texture, + _graphics: Arc<SharedGraphicsContext>, + dragging_valid: bool| -> Option<Texture> { + let mut result = None; + ui.horizontal(|ui| { + ui.label(label); + + let texture_label = current_texture.label.clone() + .unwrap_or_else(|| "Default".to_string()); + + let (rect, response) = ui.allocate_exact_size( + egui::vec2(ui.available_width().min(150.0), 24.0), + egui::Sense::click(), + ); + + let fill = if dragging_valid && response.hovered() { + ui.visuals().selection.bg_fill + } else if response.hovered() { + ui.visuals().widgets.hovered.bg_fill + } else if dragging_valid { + ui.visuals().widgets.active.bg_fill + } else { + ui.visuals().widgets.inactive.bg_fill + }; + ui.painter().rect_filled(rect, 2.0, fill); + ui.painter().rect_stroke( + rect, 2.0, + ui.visuals().widgets.inactive.bg_stroke, + egui::StrokeKind::Inside, + ); + ui.painter().text( + rect.center(), + egui::Align2::CENTER_CENTER, + &texture_label, + egui::FontId::default(), + ui.visuals().text_color(), + ); + + // Handle texture drag-drop + let pointer_released = ui.input(|i| i.pointer.any_released()); + if pointer_released && response.hovered() { + let drag_id = egui::Id::new(DRAGGED_ASSET_ID); + let dragged_reference = ui + .ctx() + .data_mut(|d| d.get_temp::<Option<ResourceReference>>(drag_id).unwrap_or(None)); + if let Some(reference) = dragged_reference { + if let Some(uri) = reference.as_uri() { + if is_probably_texture_uri(uri) { + // Try to find the texture in the registry + if let Some(handle) = ASSET_REGISTRY.read() + .get_texture_handle_by_reference(&reference) + { + if let Some(tex) = ASSET_REGISTRY.read() + .get_texture(handle) + .cloned() + { + result = Some(tex); + } + } + ui.ctx().data_mut(|d| { + d.insert_temp(drag_id, None::<ResourceReference>) + }); + } + } + } + } + + if response.hovered() { + response.on_hover_text("Drop a texture here"); + } + }); + result + }; + + // Diffuse texture slot + if let Some(new_tex) = texture_slot( + ui, "Diffuse", + &format!("diffuse_tex_{}", material_name), + &material.diffuse_texture, + graphics.clone(), + dragging_valid_texture, + ) { + material.diffuse_texture = new_tex; + } + + // Normal texture slot + if let Some(new_tex) = texture_slot( + ui, "Normal", + &format!("normal_tex_{}", material_name), + &material.normal_texture, + graphics.clone(), + dragging_valid_texture, + ) { + material.normal_texture = new_tex; + } + + // Emissive texture slot (optional) + ui.horizontal(|ui| { + ui.label("Emissive"); + + let texture_label = material.emissive_texture + .as_ref() + .and_then(|t| t.label.clone()) + .unwrap_or_else(|| "None".to_string()); + + let (rect, response) = ui.allocate_exact_size( + egui::vec2(ui.available_width().min(150.0), 24.0), + egui::Sense::click(), + ); + + let fill = if dragging_valid_texture && response.hovered() { + ui.visuals().selection.bg_fill + } else if response.hovered() { + ui.visuals().widgets.hovered.bg_fill + } else if dragging_valid_texture { + ui.visuals().widgets.active.bg_fill + } else { + ui.visuals().widgets.inactive.bg_fill + }; + ui.painter().rect_filled(rect, 2.0, fill); + ui.painter().rect_stroke( + rect, 2.0, + ui.visuals().widgets.inactive.bg_stroke, + egui::StrokeKind::Inside, + ); + ui.painter().text( + rect.center(), + egui::Align2::CENTER_CENTER, + &texture_label, + egui::FontId::default(), + ui.visuals().text_color(), + ); + + let pointer_released = ui.input(|i| i.pointer.any_released()); + if pointer_released && response.hovered() { + let drag_id = egui::Id::new(DRAGGED_ASSET_ID); + let dragged_reference = ui + .ctx() + .data_mut(|d| d.get_temp::<Option<ResourceReference>>(drag_id).unwrap_or(None)); + if let Some(reference) = dragged_reference { + if let Some(uri) = reference.as_uri() { + if is_probably_texture_uri(uri) { + if let Some(handle) = ASSET_REGISTRY.read() + .get_texture_handle_by_reference(&reference) + { + if let Some(tex) = ASSET_REGISTRY.read() + .get_texture(handle) + .cloned() + { + material.emissive_texture = Some(tex); + } + } + ui.ctx().data_mut(|d| { + d.insert_temp(drag_id, None::<ResourceReference>) + }); + } + } + } + } + + if response.hovered() { + response.on_hover_text("Drop a texture here"); + } + + // Clear button for optional texture + if material.emissive_texture.is_some() && ui.small_button("X").clicked() { + material.emissive_texture = None; + } + }); + + // Metallic/Roughness texture slot (optional) + ui.horizontal(|ui| { + ui.label("Metal/Rough"); + + let texture_label = material.metallic_roughness_texture + .as_ref() + .and_then(|t| t.label.clone()) + .unwrap_or_else(|| "None".to_string()); + + let (rect, response) = ui.allocate_exact_size( + egui::vec2(ui.available_width().min(150.0), 24.0), + egui::Sense::click(), + ); + + let fill = if dragging_valid_texture && response.hovered() { + ui.visuals().selection.bg_fill + } else if response.hovered() { + ui.visuals().widgets.hovered.bg_fill + } else if dragging_valid_texture { + ui.visuals().widgets.active.bg_fill + } else { + ui.visuals().widgets.inactive.bg_fill + }; + ui.painter().rect_filled(rect, 2.0, fill); + ui.painter().rect_stroke( + rect, 2.0, + ui.visuals().widgets.inactive.bg_stroke, + egui::StrokeKind::Inside, + ); + ui.painter().text( + rect.center(), + egui::Align2::CENTER_CENTER, + &texture_label, + egui::FontId::default(), + ui.visuals().text_color(), + ); + + let pointer_released = ui.input(|i| i.pointer.any_released()); + if pointer_released && response.hovered() { + let drag_id = egui::Id::new(DRAGGED_ASSET_ID); + let dragged_reference = ui + .ctx() + .data_mut(|d| d.get_temp::<Option<ResourceReference>>(drag_id).unwrap_or(None)); + if let Some(reference) = dragged_reference { + if let Some(uri) = reference.as_uri() { + if is_probably_texture_uri(uri) { + if let Some(handle) = ASSET_REGISTRY.read() + .get_texture_handle_by_reference(&reference) + { + if let Some(tex) = ASSET_REGISTRY.read() + .get_texture(handle) + .cloned() + { + material.metallic_roughness_texture = Some(tex); + } + } + ui.ctx().data_mut(|d| { + d.insert_temp(drag_id, None::<ResourceReference>) + }); + } + } + } + } + + if response.hovered() { + response.on_hover_text("Drop a texture here"); + } + + if material.metallic_roughness_texture.is_some() && ui.small_button("X").clicked() { + material.metallic_roughness_texture = None; + } + }); + + // Occlusion texture slot (optional) + ui.horizontal(|ui| { + ui.label("Occlusion"); + + let texture_label = material.occlusion_texture + .as_ref() + .and_then(|t| t.label.clone()) + .unwrap_or_else(|| "None".to_string()); + + let (rect, response) = ui.allocate_exact_size( + egui::vec2(ui.available_width().min(150.0), 24.0), + egui::Sense::click(), + ); + + let fill = if dragging_valid_texture && response.hovered() { + ui.visuals().selection.bg_fill + } else if response.hovered() { + ui.visuals().widgets.hovered.bg_fill + } else if dragging_valid_texture { + ui.visuals().widgets.active.bg_fill + } else { + ui.visuals().widgets.inactive.bg_fill + }; + ui.painter().rect_filled(rect, 2.0, fill); + ui.painter().rect_stroke( + rect, 2.0, + ui.visuals().widgets.inactive.bg_stroke, + egui::StrokeKind::Inside, + ); + ui.painter().text( + rect.center(), + egui::Align2::CENTER_CENTER, + &texture_label, + egui::FontId::default(), + ui.visuals().text_color(), + ); + + let pointer_released = ui.input(|i| i.pointer.any_released()); + if pointer_released && response.hovered() { + let drag_id = egui::Id::new(DRAGGED_ASSET_ID); + let dragged_reference = ui + .ctx() + .data_mut(|d| d.get_temp::<Option<ResourceReference>>(drag_id).unwrap_or(None)); + if let Some(reference) = dragged_reference { + if let Some(uri) = reference.as_uri() { + if is_probably_texture_uri(uri) { + if let Some(handle) = ASSET_REGISTRY.read() + .get_texture_handle_by_reference(&reference) + { + if let Some(tex) = ASSET_REGISTRY.read() + .get_texture(handle) + .cloned() + { + material.occlusion_texture = Some(tex); + } + } + ui.ctx().data_mut(|d| { + d.insert_temp(drag_id, None::<ResourceReference>) + }); + } + } + } + } + + if response.hovered() { + response.on_hover_text("Drop a texture here"); + } + + if material.occlusion_texture.is_some() && ui.small_button("X").clicked() { + material.occlusion_texture = None; + } + }); } } }); @@ -265,12 +265,12 @@ impl SceneConfig { fn register_physics_for_entity(&mut self, world: &mut hecs::World, entity: hecs::Entity) { if let Ok(( - label, - e_trans, - rigid, - col_group, - kcc - )) = world.query_one::<( + label, + e_trans, + rigid, + col_group, + kcc + )) = world.query_one::<( &Label, &EntityTransform, Option<&mut RigidBody>, @@ -392,33 +392,6 @@ impl SceneConfig { if !has_light { log::info!("No lights in scene, spawning default light"); - let legacy_lights: Vec<hecs::Entity> = world - .query::<(Entity, &Label)>() - .iter() - .filter_map(|(entity, label)| { - if label.as_str() == "Default Light" { - Some(entity) - } else { - None - } - }) - .collect(); - - for entity in legacy_lights { - if let Err(err) = world.despawn(entity) { - log::warn!( - "Failed to remove legacy 'Default Light' entity {:?}: {}", - entity, - err - ); - } else { - log::debug!( - "Removed legacy 'Default Light' placeholder entity {:?}", - entity - ); - } - } - if let Some(s) = progress_sender { let _ = s.send(WorldLoadingStatus::LoadingEntity { index: 0, @@ -503,34 +476,6 @@ impl SceneConfig { } else { log::info!("No debug or starting camera found, creating viewport camera for editor"); - let legacy_cameras: Vec<hecs::Entity> = world - .query::<(Entity, &Label)>() - .iter() - .filter_map(|(entity, label)| { - if label.as_str() == "Viewport Camera" { - log::debug!("Found 'Viewport Camera' entity: {:?}", entity); - Some(entity) - } else { - None - } - }) - .collect(); - - for entity in legacy_cameras { - if let Err(err) = world.despawn(entity) { - log::warn!( - "Failed to remove legacy 'Viewport Camera' entity {:?}: {}", - entity, - err - ); - } else { - log::debug!( - "Removed legacy 'Viewport Camera' placeholder entity {:?}", - entity - ); - } - } - if let Some(s) = progress_sender { let _ = s.send(WorldLoadingStatus::LoadingEntity { index: 0, @@ -279,8 +279,8 @@ impl SerializableCamera { fov: camera.settings.fov_y as f32, near: camera.znear as f32, far: camera.zfar as f32, - speed: component.settings.speed as f32, - sensitivity: component.settings.sensitivity as f32, + speed: camera.settings.speed as f32, + sensitivity: camera.settings.sensitivity as f32, starting_camera: component.starting_camera, } } @@ -1,13 +1,28 @@ use std::{cmp::Ordering, fs, hash::DefaultHasher, io, path::Path}; use std::hash::{Hash, Hasher}; use dropbear_engine::{graphics::NO_TEXTURE, utils::ResourceReference}; +use dropbear_engine::asset::ASSET_REGISTRY; +use dropbear_engine::entity::MeshRenderer; +use dropbear_engine::model::Model; +use dropbear_engine::texture::Texture; +use eucalyptus_core::utils::ResolveReference; use egui_ltreeview::{Action, NodeBuilder, TreeViewBuilder}; use eucalyptus_core::states::PROJECT; use hecs::Entity; +use log::{info, warn}; use crate::editor::{ComponentNodeSelection, DraggedAsset, EditorTabViewer, FsEntry, StaticallyKept, TABS_GLOBAL}; use eucalyptus_core::component::DRAGGED_ASSET_ID; +#[derive(Clone, Copy, Debug)] +enum TextureSlot { + Diffuse, + Normal, + Emissive, + MetallicRoughness, + Occlusion, +} + impl<'a> EditorTabViewer<'a> { pub(crate) fn show_asset_viewer(&mut self, ui: &mut egui::Ui) { let mut cfg = TABS_GLOBAL.lock(); @@ -23,7 +38,7 @@ impl<'a> EditorTabViewer<'a> { let (_resp, action) = egui_ltreeview::TreeView::new(egui::Id::new("asset_viewer")).show(ui, |builder| { builder.node(Self::dir_node("euca://")); - Self::build_resource_branch(&mut cfg, builder, &project_root); + self.build_resource_branch(&mut cfg, builder, &project_root); Self::build_scripts_branch(&mut cfg, builder, &project_root); Self::build_scene_branch(&mut cfg, builder, &project_root); builder.close_dir(); @@ -59,6 +74,7 @@ impl<'a> EditorTabViewer<'a> { } fn build_resource_branch( + &mut self, cfg: &mut StaticallyKept, builder: &mut TreeViewBuilder<u64>, project_root: &Path, @@ -67,7 +83,7 @@ impl<'a> EditorTabViewer<'a> { builder.node(Self::dir_node_labeled(label, "resources")); let resources_root = project_root.join("resources"); if resources_root.exists() { - Self::walk_resource_directory(cfg, builder, &resources_root, &resources_root); + self.walk_resource_directory(cfg, builder, &resources_root, &resources_root); } else { Self::add_placeholder_leaf(builder, "euca://resources/missing", "missing"); } @@ -75,6 +91,7 @@ impl<'a> EditorTabViewer<'a> { } fn walk_resource_directory( + &mut self, cfg: &mut StaticallyKept, builder: &mut TreeViewBuilder<u64>, base_path: &Path, @@ -96,22 +113,81 @@ impl<'a> EditorTabViewer<'a> { let full_label = Self::resource_label(base_path, &entry.path); if entry.is_dir { builder.node(Self::dir_node_labeled(&full_label, &entry.name)); - Self::walk_resource_directory(cfg, builder, base_path, &entry.path); + self.walk_resource_directory(cfg, builder, base_path, &entry.path); builder.close_dir(); } else { if entry.name.eq_ignore_ascii_case("resources.eucc") { continue; } + let reference = ResourceReference::from_euca_uri(&full_label) + .unwrap_or_else(|_| ResourceReference::default()); cfg.asset_node_assets.insert( Self::asset_node_id(&full_label), DraggedAsset { name: entry.name.clone(), - path: ResourceReference::from_euca_uri(&full_label) - .unwrap_or_else(|_| ResourceReference::default()), + path: reference.clone(), }, ); - builder.node(Self::leaf_node_labeled(&full_label, &entry.name)); + let is_model = Self::is_model_file(&entry.name); + let is_texture = Self::is_texture_file(&entry.name); + let entry_name = entry.name.clone(); + let reference_for_menu = reference.clone(); + let menu = Self::leaf_node_labeled(&full_label, &entry.name).context_menu(|ui| { + if is_model { + if ui.button("Load to memory").clicked() { + ui.close(); + self.queue_model_load(reference_for_menu.clone(), entry_name.clone()); + } + } + + if is_texture { + if ui.button("Load to memory").clicked() { + ui.close(); + self.queue_texture_load(reference_for_menu.clone(), entry_name.clone()); + } + + ui.separator(); + ui.menu_button("Choose", |ui| { + if ui.button("Diffuse").clicked() { + ui.close(); + self.apply_texture_slot( + reference_for_menu.clone(), + TextureSlot::Diffuse, + ); + } + if ui.button("Normal").clicked() { + ui.close(); + self.apply_texture_slot( + reference_for_menu.clone(), + TextureSlot::Normal, + ); + } + if ui.button("Emissive").clicked() { + ui.close(); + self.apply_texture_slot( + reference_for_menu.clone(), + TextureSlot::Emissive, + ); + } + if ui.button("Metal/Rough").clicked() { + ui.close(); + self.apply_texture_slot( + reference_for_menu.clone(), + TextureSlot::MetallicRoughness, + ); + } + if ui.button("Occlusion").clicked() { + ui.close(); + self.apply_texture_slot( + reference_for_menu.clone(), + TextureSlot::Occlusion, + ); + } + }); + } + }); + builder.node(menu); } } } @@ -129,7 +205,7 @@ impl<'a> EditorTabViewer<'a> { } fn build_scripts_branch( - cfg: &mut StaticallyKept, + _cfg: &mut StaticallyKept, builder: &mut TreeViewBuilder<u64>, project_root: &Path, ) { @@ -137,7 +213,7 @@ impl<'a> EditorTabViewer<'a> { builder.node(Self::dir_node_labeled(label, "scripts")); let scripts_root = project_root.join("src"); if !scripts_root.exists() { - Self::walk_resource_directory(cfg, builder, &scripts_root, &scripts_root); + Self::add_placeholder_leaf(builder, "euca://scripts/missing", "missing"); builder.close_dir(); return; } @@ -494,6 +570,120 @@ impl<'a> EditorTabViewer<'a> { builder.node(Self::leaf_node_labeled(id_source, label)); } + + fn is_model_file(name: &str) -> bool { + let name = name.to_ascii_lowercase(); + name.ends_with(".glb") || name.ends_with(".gltf") + } + + fn is_texture_file(name: &str) -> bool { + let name = name.to_ascii_lowercase(); + name.ends_with(".png") + || name.ends_with(".jpg") + || name.ends_with(".jpeg") + || name.ends_with(".tga") + || name.ends_with(".bmp") + || name.ends_with(".webp") + } + + fn queue_model_load(&self, reference: ResourceReference, label: String) { + if ASSET_REGISTRY + .read() + .get_model_handle_by_reference(&reference) + .is_some() + { + info!("Model already loaded: {}", label); + return; + } + + let graphics = self.graphics.clone(); + let queue = graphics.future_queue.clone(); + queue.push(async move { + let path = reference.resolve()?; + let buffer = fs::read(&path)?; + let handle = Model::load_from_memory_raw( + graphics.clone(), + buffer, + None, + ASSET_REGISTRY.clone(), + ) + .await?; + + let mut registry = ASSET_REGISTRY.write(); + if let Some(model) = registry.get_model(handle).cloned() { + let mut model = model; + model.path = reference.clone(); + model.label = label.clone(); + registry.update_model(handle, model); + registry.label_model(label.clone(), handle); + } + + Ok::<(), anyhow::Error>(()) + }); + } + + fn queue_texture_load(&self, reference: ResourceReference, label: String) { + if ASSET_REGISTRY + .read() + .get_texture_handle_by_reference(&reference) + .is_some() + { + info!("Texture already loaded: {}", label); + return; + } + + let graphics = self.graphics.clone(); + let queue = graphics.future_queue.clone(); + queue.push(async move { + let path = reference.resolve()?; + let texture = Texture::from_file(graphics.clone(), &path, Some(&label)).await?; + let mut registry = ASSET_REGISTRY.write(); + registry.add_texture_with_label(label.clone(), texture); + Ok::<(), anyhow::Error>(()) + }); + } + + fn apply_texture_slot(&mut self, reference: ResourceReference, slot: TextureSlot) { + let Some(entity) = *self.selected_entity else { + warn!("Unable to apply texture: no entity selected"); + return; + }; + + let Some(handle) = ASSET_REGISTRY + .read() + .get_texture_handle_by_reference(&reference) + else { + warn!("Texture not loaded in memory, load it first"); + return; + }; + + let texture = { + let registry = ASSET_REGISTRY.read(); + registry.get_texture(handle).cloned() + }; + + let Some(texture) = texture else { + warn!("Texture handle missing from registry"); + return; + }; + + if let Ok(renderer) = self.world.query_one::<&mut MeshRenderer>(entity).get() { + for material in renderer.material_snapshot.values_mut() { + match slot { + TextureSlot::Diffuse => material.diffuse_texture = texture.clone(), + TextureSlot::Normal => material.normal_texture = texture.clone(), + TextureSlot::Emissive => material.emissive_texture = Some(texture.clone()), + TextureSlot::MetallicRoughness => { + material.metallic_roughness_texture = Some(texture.clone()) + } + TextureSlot::Occlusion => material.occlusion_texture = Some(texture.clone()), + } + } + } else { + warn!("Selected entity has no MeshRenderer"); + } + } + pub(crate) fn handle_tree_selection(&mut self, cfg: &mut StaticallyKept, items: &[u64]) { for node_id in items { self.resolve_tree_node(cfg, *node_id); @@ -2,7 +2,7 @@ use egui_ltreeview::{NodeBuilder, TreeViewBuilder}; use eucalyptus_core::{component::ComponentRegistry, hierarchy::{Children, Hierarchy, Parent}, physics::{collider::ColliderGroup, rigidbody::RigidBody}, states::{Label, PROJECT}}; use hecs::{Entity, World}; -use crate::editor::{EditorTabViewer, Signal, StaticallyKept, TABS_GLOBAL}; +use crate::editor::{Editor, EditorTabViewer, Signal, StaticallyKept, TABS_GLOBAL}; impl<'a> EditorTabViewer<'a> { pub(crate) fn entity_list(&mut self, ui: &mut egui::Ui) { @@ -24,7 +24,8 @@ impl<'a> EditorTabViewer<'a> { .label(format!("Scene: {}", current_scene_name)) .context_menu(|ui| { if ui.button("New Empty Entity").clicked() { - self.world.spawn((Label::new("Blank Entity"),)); + let label = Editor::unique_label_for_world(self.world, "Blank Entity"); + self.world.spawn((label,)); ui.close(); } }), @@ -56,7 +57,8 @@ impl<'a> EditorTabViewer<'a> { .context_menu(|ui| { ui.menu_button("New", |ui| { if ui.button("Child").clicked() { - let child = world.spawn((Label::new("New Entity"),)); + let label = Editor::unique_label_for_world(world, "New Entity"); + let child = world.spawn((label,)); Hierarchy::set_parent(world, child, entity); ui.close(); } @@ -30,6 +30,7 @@ use eucalyptus_core::{register_components, APP_INFO}; use eucalyptus_core::hierarchy::{Children, SceneHierarchy}; use eucalyptus_core::scene::{SceneConfig, SceneEntity}; use eucalyptus_core::states::Label; +use std::collections::HashSet; use eucalyptus_core::{ camera::{CameraComponent, CameraType, DebugCamera}, fatal, info, @@ -285,6 +286,55 @@ impl Editor { }) } + pub(crate) fn unique_label_for_world(world: &World, base: &str) -> Label { + fn parse_suffix(name: &str) -> Option<(&str, u32)> { + let mut parts = name.rsplitn(2, '.'); + let suffix = parts.next()?; + let root = parts.next()?; + if suffix.len() == 3 && suffix.chars().all(|c| c.is_ascii_digit()) { + if let Ok(value) = suffix.parse::<u32>() { + return Some((root, value)); + } + } + None + } + + let mut existing = HashSet::new(); + for (_, label) in world.query::<(Entity, &Label)>().iter() { + existing.insert(label.as_str().to_string()); + } + + if !existing.contains(base) { + return Label::new(base); + } + + let (root, base_suffix) = match parse_suffix(base) { + Some((root, suffix)) => (root.to_string(), suffix), + None => (base.to_string(), 0), + }; + + let mut max_suffix = base_suffix; + for name in &existing { + if name == &root { + continue; + } + if let Some((candidate_root, suffix)) = parse_suffix(name) { + if candidate_root == root { + max_suffix = max_suffix.max(suffix); + } + } + } + + let mut next = max_suffix.max(1); + loop { + let candidate = format!("{root}.{next:03}"); + if !existing.contains(&candidate) { + return Label::new(candidate.as_str()); + } + next += 1; + } + } + fn double_key_pressed(&mut self, key: KeyCode) -> bool { let now = Instant::now(); @@ -1076,6 +1126,64 @@ impl Editor { }); }); + egui::TopBottomPanel::bottom("status bar") + .resizable(false) + .show(ctx, |ui| { + let active_camera = *self.active_camera.lock(); + let (label, is_debug, active_entity) = if let Some(entity) = active_camera { + let camera_label = self + .world + .get::<&Label>(entity) + .map(|label| label.as_str().to_string()) + .ok() + .or_else(|| { + self.world + .get::<&Camera>(entity) + .map(|camera| camera.label.clone()) + .ok() + }) + .unwrap_or_else(|| "Camera".to_string()); + let is_debug = self + .world + .get::<&CameraComponent>(entity) + .map(|comp| matches!(comp.camera_type, CameraType::Debug)) + .unwrap_or(false); + (camera_label, is_debug, Some(entity)) + } else { + ("Viewport Camera".to_string(), true, None) + }; + + let selected_entity = self.selected_entity; + let selected_has_camera = selected_entity + .and_then(|entity| { + self.world + .query_one::<(&Camera, &CameraComponent)>(entity) + .get() + .ok() + .map(|_| entity) + }) + .is_some(); + + let is_selected_active = selected_entity + .and_then(|entity| active_entity.map(|active| (entity, active))) + .map(|(selected, active)| selected == active) + .unwrap_or(false); + + let text_color = if is_debug { + ui.visuals().weak_text_color() + } else if selected_has_camera && is_selected_active { + egui::Color32::from_rgb(80, 200, 120) + } else if selected_has_camera { + egui::Color32::from_rgb(245, 230, 160) + } else { + ui.visuals().weak_text_color() + }; + + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.colored_label(text_color, format!("Viewing through {label}")); + }); + }); + let editor_ptr = self as *mut Editor; let Some(view) = self.texture_id else { @@ -1,4 +1,6 @@ use crate::editor::{EditorTabViewer, TABS_GLOBAL}; +use dropbear_engine::camera::Camera; +use eucalyptus_core::camera::CameraComponent; impl<'a> EditorTabViewer<'a> { pub(crate) fn resource_inspector(&mut self, ui: &mut egui::Ui) { @@ -13,6 +15,30 @@ impl<'a> EditorTabViewer<'a> { ui.label(format!("Entity ID: {}", inspect_entity.id())); ui.separator(); + if self + .world + .query_one::<(&Camera, &CameraComponent)>(inspect_entity) + .get() + .is_ok() + { + let is_active = self + .active_camera + .lock() + .map_or(false, |active| active == inspect_entity); + ui.horizontal(|ui| { + let label = if is_active { + "Viewing Through This Camera" + } else { + "View Through This Camera" + }; + if ui.add_enabled(!is_active, egui::Button::new(label)).clicked() { + let mut active_camera = self.active_camera.lock(); + *active_camera = Some(inspect_entity); + } + }); + ui.separator(); + } + self.component_registry .inspect_components(self.world, inspect_entity, ui, self.graphics.clone()); } @@ -610,11 +610,13 @@ impl Scene for Editor { }) .unwrap_or(false); + log_once::debug_once!("show_hitboxes = {}, current_scene_name = {:?}", show_hitboxes, self.current_scene_name); + if show_hitboxes { if let Some(collider_pipeline) = &self.collider_wireframe_pipeline { let mut render_pass = encoder .begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("model render pass"), + label: Some("collider wireframe render pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: hdr.view(), depth_slice: None, @@ -643,8 +645,12 @@ impl Scene for Editor { HashMap::new(); let mut q = self.world.query::<(&EntityTransform, &ColliderGroup)>(); + let mut entity_count = 0; + let mut collider_count = 0; for (entity_transform, group) in q.iter() { + entity_count += 1; for collider in &group.colliders { + collider_count += 1; let world_tf = entity_transform.sync(); let entity_matrix = DMat4::from_rotation_translation( @@ -659,6 +665,7 @@ impl Scene for Editor { let final_matrix = entity_matrix * offset_matrix; + let color = [0.0, 1.0, 0.0, 1.0]; let instance = ColliderInstanceRaw::from_matrix(final_matrix, color); @@ -673,6 +680,7 @@ impl Scene for Editor { }); } } + log_once::debug_once!("Collider wireframe: {} entities with colliders, {} total colliders", entity_count, collider_count); if !instances_by_shape.is_empty() { let total_instances: usize = @@ -45,6 +45,11 @@ impl SignalController for Editor { } Signal::Copy(_) => Ok(()), Signal::Paste(scene_entity) => { + let mut scene_entity = scene_entity.clone(); + scene_entity.label = Editor::unique_label_for_world( + self.world.as_ref(), + scene_entity.label.as_str(), + ); let spawn = PendingSpawn { scene_entity: scene_entity.clone(), handle: None, @@ -68,39 +68,6 @@ 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, @@ -179,59 +146,141 @@ 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 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 struct NRange { + float start; + float end; +} NRange; + typedef struct IndexNative { uint32_t index; uint32_t generation; } IndexNative; -typedef struct IndexNativeArray { - IndexNative* values; - size_t length; - size_t capacity; -} IndexNativeArray; - -typedef struct CharacterCollisionArray { - uint64_t entity_id; - IndexNativeArray collisions; -} CharacterCollisionArray; - typedef struct NCollider { IndexNative index; uint64_t entity_id; uint32_t id; } NCollider; -typedef struct NShapeCastHit { +typedef struct RayHit { NCollider collider; double distance; - NVector3 witness1; - NVector3 witness2; - NVector3 normal1; - NVector3 normal2; - NShapeCastStatus status; -} NShapeCastHit; +} RayHit; -typedef void* PhysicsStatePtr; +typedef struct AxisLock { + bool x; + bool y; + bool z; +} AxisLock; + +typedef struct i32Array { + int32_t* values; + size_t length; + size_t capacity; +} i32Array; + +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 void* AssetRegistryPtr; typedef struct NVector2 { double x; double y; } NVector2; +typedef void* InputStatePtr; + +typedef struct u64Array { + uint64_t* values; + size_t length; + size_t capacity; +} u64Array; + +typedef struct NAttenuation { + float constant; + float linear; + float quadratic; +} NAttenuation; + +typedef struct NColour { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} NColour; + typedef struct NTransform { NVector3 position; NQuaternion rotation; NVector3 scale; } NTransform; -typedef struct NRange { - float start; - float end; -} NRange; +typedef struct Progress { + size_t current; + size_t total; + const char* message; +} Progress; -typedef void* InputStatePtr; +typedef struct IndexNativeArray { + IndexNative* values; + size_t length; + size_t capacity; +} IndexNativeArray; -typedef void* SceneLoaderPtr; +typedef struct CharacterCollisionArray { + uint64_t entity_id; + IndexNativeArray collisions; +} CharacterCollisionArray; typedef struct f64Array { double* values; @@ -264,66 +313,17 @@ typedef struct NAnimationArray { size_t capacity; } NAnimationArray; -typedef struct i32Array { - int32_t* values; - size_t length; - size_t capacity; -} i32Array; - -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* GraphicsContextPtr; - -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 NAttenuation { - float constant; - float linear; - float quadratic; -} NAttenuation; - -typedef void* AssetRegistryPtr; +typedef void* WorldPtr; -typedef struct NColour { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; -} NColour; +typedef struct NShapeCastHit { + NCollider collider; + double distance; + NVector3 witness1; + NVector3 witness2; + NVector3 normal1; + NVector3 normal2; + NShapeCastStatus status; +} NShapeCastHit; typedef struct NVector4 { double x; @@ -362,35 +362,6 @@ typedef struct NMeshArray { size_t capacity; } NMeshArray; -typedef struct u64Array { - uint64_t* values; - size_t length; - size_t capacity; -} u64Array; - -typedef struct ConnectedGamepadIds { - u64Array ids; -} ConnectedGamepadIds; - -typedef void* CommandBufferPtr; - -typedef struct NColliderArray { - NCollider* values; - size_t length; - size_t capacity; -} NColliderArray; - -typedef struct Progress { - size_t current; - size_t total; - const char* message; -} Progress; - -typedef struct RayHit { - NCollider collider; - double distance; -} RayHit; - typedef struct NMaterial { const char* name; uint64_t diffuse_texture; @@ -415,18 +386,47 @@ typedef struct NMaterialArray { size_t capacity; } NMaterialArray; +typedef void* SceneLoaderPtr; + typedef struct RigidBodyContext { IndexNative index; uint64_t entity_id; } RigidBodyContext; -typedef struct AxisLock { - bool x; - bool y; - bool z; -} AxisLock; +typedef struct ConnectedGamepadIds { + u64Array ids; +} ConnectedGamepadIds; -typedef void* WorldPtr; +typedef struct NColliderArray { + NCollider* values; + size_t length; + size_t capacity; +} NColliderArray; + +typedef void* CommandBufferPtr; + +typedef void* GraphicsContextPtr; + +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* PhysicsStatePtr; 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,6 +459,10 @@ 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); @@ -483,10 +487,6 @@ 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,6 +494,18 @@ 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_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); @@ -510,6 +522,38 @@ int32_t dropbear_input_get_last_mouse_pos(InputStatePtr input, NVector2* out0); int32_t dropbear_input_is_cursor_hidden(InputStatePtr input, bool* out0); int32_t dropbear_input_set_cursor_hidden(CommandBufferPtr command_buffer, InputStatePtr input, bool hidden); int32_t dropbear_input_get_connected_gamepads(InputStatePtr input, ConnectedGamepadIds* out0); +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); +int32_t dropbear_physics_shape_cast(PhysicsStatePtr physics, const NVector3* origin, const NVector3* direction, const ColliderShape* shape, double time_of_impact, bool solid, NShapeCastHit* out0, bool* out0_present); +int32_t dropbear_physics_is_overlapping(PhysicsStatePtr physics, const NCollider* collider1, const NCollider* collider2, bool* out0); +int32_t dropbear_physics_is_triggering(PhysicsStatePtr physics, const NCollider* collider1, const NCollider* collider2, bool* out0); +int32_t dropbear_physics_is_touching(PhysicsStatePtr physics, uint64_t entity1, uint64_t entity2, bool* 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); int32_t dropbear_lighting_light_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0); int32_t dropbear_lighting_get_position(WorldPtr world, uint64_t entity, NVector3* out0); int32_t dropbear_lighting_set_position(WorldPtr world, uint64_t entity, const NVector3* position); @@ -533,13 +577,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_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); -int32_t dropbear_physics_shape_cast(PhysicsStatePtr physics, const NVector3* origin, const NVector3* direction, const ColliderShape* shape, double time_of_impact, bool solid, NShapeCastHit* out0, bool* out0_present); -int32_t dropbear_physics_is_overlapping(PhysicsStatePtr physics, const NCollider* collider1, const NCollider* collider2, bool* out0); -int32_t dropbear_physics_is_triggering(PhysicsStatePtr physics, const NCollider* collider1, const NCollider* collider2, bool* out0); -int32_t dropbear_physics_is_touching(PhysicsStatePtr physics, uint64_t entity1, uint64_t entity2, bool* out0); int32_t dropbear_properties_custom_properties_exists_for_entity(WorldPtr world, uint64_t entity, bool* out0); int32_t dropbear_properties_get_string_property(WorldPtr world, uint64_t entity, const char* key, char** out0, bool* out0_present); int32_t dropbear_properties_get_int_property(WorldPtr world, uint64_t entity, const char* key, int32_t* out0, bool* out0_present); @@ -561,42 +598,5 @@ 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 */