tirbofish/dropbear · diff
refactor: edit compiles, but no components being shown.
Signature present but could not be verified.
Unverified
@@ -1,9 +1,11 @@ -use dropbear_traits::{Component, ComponentDescriptor, ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent}; +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; @@ -73,6 +75,29 @@ impl Component for AnimationComponent { // 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 { @@ -347,6 +347,7 @@ impl Transform { pub struct MeshRenderer { import_scale: f32, + pub is_selected: bool, handle: Handle<Model>, pub instance: Instance, previous_matrix: DMat4, @@ -361,6 +362,7 @@ impl MeshRenderer { previous_matrix: DMat4::IDENTITY, import_scale: 1.0, texture_override: None, + is_selected: false, } } @@ -382,6 +384,7 @@ impl MeshRenderer { import_scale: 1.0, previous_matrix: DMat4::IDENTITY, texture_override: None, + is_selected: false, }) } @@ -21,6 +21,7 @@ pub mod mipmap; pub mod sky; pub mod features; pub mod animation; +pub use dropbear_traits as component; features! { pub mod build { @@ -53,6 +53,7 @@ pub trait Component { 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; @@ -61,6 +62,28 @@ 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 { @@ -93,13 +116,13 @@ pub struct ComponentInitContext { pub type ComponentInitFuture = Pin<Box<dyn Future<Output = anyhow::Result<Box<dyn ComponentInsert>>> + Send + 'static>>; -pub trait ComponentInsert: Send { +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> ComponentInsert for InsertBundle<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(()) @@ -1,9 +1,11 @@ use std::any::TypeId; use std::collections::HashMap; +use std::sync::Arc; +use egui::Ui; use hecs::{Entity, World}; -use crate::SerializableComponent; +use crate::{Component, ComponentResources, ComponentUpdateContext, SerializableComponent}; pub struct ComponentRegistry { next_id: u32, @@ -13,12 +15,15 @@ pub struct ComponentRegistry { } 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 { @@ -40,6 +45,48 @@ impl ComponentRegistry { 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; @@ -62,6 +109,8 @@ impl ComponentRegistry { remove_fn: Some(|world, entity| { let _ = world.remove_one::<T>(entity); }), + inspect_fn, + update_fn, }; self.entries.insert(id, entry); @@ -137,6 +186,54 @@ impl ComponentRegistry { 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() } @@ -1,5 +1,5 @@ //! Additional information and context for cameras from the [`dropbear_engine::camera`] -use crate::states::Camera3D; +use crate::states::SerializableCamera; use dropbear_engine::camera::{Camera, CameraBuilder, CameraSettings}; use dropbear_traits::{ComponentInitContext, ComponentInitFuture, InsertBundle, SerializableComponent}; use glam::DVec3; @@ -56,8 +56,8 @@ impl SerializableComponent for CameraComponent { } } -impl From<Camera3D> for CameraBuilder { - fn from(value: Camera3D) -> Self { +impl From<SerializableCamera> for CameraBuilder { + fn from(value: SerializableCamera) -> Self { let forward = value.transform.rotation * DVec3::Z; let up = if matches!(value.camera_type, CameraType::Debug | CameraType::Normal) { DVec3::Y @@ -81,8 +81,8 @@ impl From<Camera3D> for CameraBuilder { } } -impl From<Camera3D> for CameraComponent { - fn from(value: Camera3D) -> Self { +impl From<SerializableCamera> for CameraComponent { + fn from(value: SerializableCamera) -> Self { let settings = CameraSettings::new( value.speed as f64, value.sensitivity as f64, @@ -35,7 +35,7 @@ use crate::camera::CameraComponent; use crate::physics::collider::ColliderGroup; use crate::physics::kcc::KCC; use crate::physics::rigidbody::RigidBody; -use crate::states::{Camera3D, Light, Script, SerializedMeshRenderer}; +use crate::states::{SerializableCamera, Light, Script, SerializedMeshRenderer}; /// The appdata directory for storing any information. /// @@ -45,27 +45,20 @@ pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo { author: "tirbofish", }; -#[unsafe(no_mangle)] -pub extern "C" fn get_rustc_version() -> *const u8 { - let meta = rustc_version_runtime::version_meta(); - let meta_string = format!("{:?}", meta); - Box::leak(meta_string.into_boxed_str()).as_ptr() -} - /// Registers all available and potential serializers and deserializers of an entity. pub fn register_components( component_registry: &mut ComponentRegistry, ) { - component_registry.register_with_default::<EntityTransform>(); + 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::<Camera3D>(); + 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::<AnimationComponent>(); + component_registry.register_with_default_component::<AnimationComponent>(); component_registry.register_converter::<MeshRenderer, SerializedMeshRenderer, _>( |_, _, renderer| { @@ -73,7 +66,7 @@ pub fn register_components( }, ); - component_registry.register_converter::<CameraComponent, Camera3D, _>( + component_registry.register_converter::<CameraComponent, SerializableCamera, _>( |world, entity, component| { let Ok(camera) = world.get::<&Camera>(entity) else { log::debug!( @@ -83,7 +76,7 @@ pub fn register_components( return None; }; - Some(Camera3D::from_ecs_camera(&camera, component)) + Some(SerializableCamera::from_ecs_camera(&camera, component)) }, ); @@ -5,7 +5,7 @@ pub mod scripting; use crate::camera::CameraComponent; use crate::hierarchy::{Children, Parent, SceneHierarchy}; -use crate::states::{Camera3D, Label, Light, Script, SerializedMeshRenderer, WorldLoadingStatus, PROJECT}; +use crate::states::{SerializableCamera, Label, Light, Script, SerializedMeshRenderer, WorldLoadingStatus, PROJECT}; use dropbear_engine::camera::Camera; use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform}; use dropbear_engine::graphics::SharedGraphicsContext; @@ -23,7 +23,7 @@ use std::fmt::{Display, Formatter}; use std::ops::{Deref, DerefMut}; use std::path::PathBuf; use std::sync::Arc; -use egui::Ui; +use egui::{CollapsingHeader, TextEdit, Ui}; use hecs::{Entity, World}; use dropbear_traits::{Component, ComponentDescriptor}; use crate::properties::Value; @@ -156,8 +156,57 @@ pub struct Script { pub tags: Vec<String>, } +impl Component for Script { + type Serialized = Self; + + fn static_descriptor() -> ComponentDescriptor { + ComponentDescriptor { + fqtn: "eucalyptus_core::states::Script".to_string(), + type_name: "Script".to_string(), + category: Some("Logic".to_string()), + description: Some("A script component that can be attached to entities.".to_string()), + } + } + + fn deserialize(serialized: &Self::Serialized) -> Self { + serialized.clone() + } + + fn serialize(&self) -> Self::Serialized { + 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()) + } + }); + }); + } +} + #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Camera3D { +pub struct SerializableCamera { pub label: String, pub transform: Transform, pub camera_type: CameraType, @@ -173,7 +222,7 @@ pub struct Camera3D { pub starting_camera: bool, } -impl Default for Camera3D { +impl Default for SerializableCamera { fn default() -> Self { let default = CameraComponent::new(); Self { @@ -191,7 +240,7 @@ impl Default for Camera3D { } } -impl Camera3D { +impl SerializableCamera { pub fn from_ecs_camera(camera: &Camera, component: &CameraComponent) -> Self { let position = glam::DVec3::from_array(camera.eye.to_array()); let target = glam::DVec3::from_array(camera.target.to_array()); @@ -444,7 +493,7 @@ impl SerializableComponent for Script { } #[typetag::serde] -impl SerializableComponent for Camera3D { +impl SerializableComponent for SerializableCamera { fn as_any(&self) -> &dyn Any { self } @@ -1,80 +1,79 @@ -use crate::editor::component::InspectableComponent; use crate::editor::{Signal, StaticallyKept, UndoableAction}; use dropbear_engine::camera::Camera; use egui::{Ui}; use eucalyptus_core::camera::{CameraComponent}; use hecs::Entity; -impl InspectableComponent for Camera { - fn inspect( - &mut self, - _entity: &mut Entity, - _cfg: &mut StaticallyKept, - ui: &mut Ui, - _undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - _label: &mut String, - ) { - ui.vertical(|ui| { - ui.horizontal(|ui| { - ui.label("Position:"); - ui.label(format!( - "{:.2}, {:.2}, {:.2}", - self.eye.x, self.eye.y, self.eye.z - )); - if ui.button("Reset").clicked() { - self.eye = glam::DVec3::ZERO; - } - }); - - ui.horizontal(|ui| { - ui.label("Target:"); - ui.label(format!( - "{:.2}, {:.2}, {:.2}", - self.target.x, self.target.y, self.target.z - )); - if ui.button("Reset").clicked() { - self.target = glam::DVec3::ZERO; - } - }); - }); - } -} - -impl InspectableComponent for CameraComponent { - fn inspect( - &mut self, - _entity: &mut Entity, - _cfg: &mut StaticallyKept, - ui: &mut Ui, - _undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - _label: &mut String, - ) { - ui.vertical(|ui| { - ui.horizontal(|ui| { - ui.label("Speed:"); - ui.add( - egui::DragValue::new(&mut self.settings.speed) - .speed(0.1), - ); - }); - - ui.horizontal(|ui| { - ui.label("Sensitivity:"); - ui.add( - egui::DragValue::new(&mut self.settings.sensitivity) - .speed(0.0001) - .range(0.0001..=1.5), - ); - }); - - ui.horizontal(|ui| { - ui.label("FOV:"); - ui.add( - egui::Slider::new(&mut self.settings.fov_y, 10.0..=120.0).suffix("°"), - ); - }); - }); - } -} +// impl InspectableComponent for Camera { +// fn inspect( +// &mut self, +// _entity: &mut Entity, +// _cfg: &mut StaticallyKept, +// ui: &mut Ui, +// _undo_stack: &mut Vec<UndoableAction>, +// _signal: &mut Signal, +// _label: &mut String, +// ) { +// ui.vertical(|ui| { +// ui.horizontal(|ui| { +// ui.label("Position:"); +// ui.label(format!( +// "{:.2}, {:.2}, {:.2}", +// self.eye.x, self.eye.y, self.eye.z +// )); +// if ui.button("Reset").clicked() { +// self.eye = glam::DVec3::ZERO; +// } +// }); +// +// ui.horizontal(|ui| { +// ui.label("Target:"); +// ui.label(format!( +// "{:.2}, {:.2}, {:.2}", +// self.target.x, self.target.y, self.target.z +// )); +// if ui.button("Reset").clicked() { +// self.target = glam::DVec3::ZERO; +// } +// }); +// }); +// } +// } +// +// impl InspectableComponent for CameraComponent { +// fn inspect( +// &mut self, +// _entity: &mut Entity, +// _cfg: &mut StaticallyKept, +// ui: &mut Ui, +// _undo_stack: &mut Vec<UndoableAction>, +// _signal: &mut Signal, +// _label: &mut String, +// ) { +// ui.vertical(|ui| { +// ui.horizontal(|ui| { +// ui.label("Speed:"); +// ui.add( +// egui::DragValue::new(&mut self.settings.speed) +// .speed(0.1), +// ); +// }); +// +// ui.horizontal(|ui| { +// ui.label("Sensitivity:"); +// ui.add( +// egui::DragValue::new(&mut self.settings.sensitivity) +// .speed(0.0001) +// .range(0.0001..=1.5), +// ); +// }); +// +// ui.horizontal(|ui| { +// ui.label("FOV:"); +// ui.add( +// egui::Slider::new(&mut self.settings.fov_y, 10.0..=120.0).suffix("°"), +// ); +// }); +// }); +// } +// } @@ -1,5 +1,16 @@ +use std::{cmp::Ordering, fs, hash::DefaultHasher, io, path::Path}; +use std::hash::{Hash, Hasher}; +use dropbear_engine::{graphics::NO_TEXTURE, utils::ResourceReference}; +use egui_ltreeview::{Action, NodeBuilder, TreeViewBuilder}; +use eucalyptus_core::states::PROJECT; +use hecs::Entity; + +use crate::editor::{ComponentNodeSelection, DraggedAsset, EditorTabViewer, FsEntry, StaticallyKept, TABS_GLOBAL}; + impl<'a> EditorTabViewer<'a> { - fn show_asset_viewer(&mut self, cfg: &mut StaticallyKept, ui: &mut egui::Ui) { + pub(crate) fn show_asset_viewer(&mut self, ui: &mut egui::Ui) { + let mut cfg = TABS_GLOBAL.lock(); + let project_root = { let project = PROJECT.read(); if project.project_path.as_os_str().is_empty() { @@ -11,9 +22,9 @@ 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(cfg, builder, &project_root); - Self::build_scripts_branch(cfg, builder, &project_root); - Self::build_scene_branch(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(); }); @@ -479,13 +490,13 @@ impl<'a> EditorTabViewer<'a> { builder.node(Self::leaf_node_labeled(id_source, label)); } - fn handle_tree_selection(&mut self, cfg: &mut StaticallyKept, items: &[u64]) { + 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); } } - fn handle_tree_activate( + pub(crate) fn handle_tree_activate( &mut self, cfg: &mut StaticallyKept, activate: &egui_ltreeview::Activate<u64>, @@ -493,7 +504,7 @@ impl<'a> EditorTabViewer<'a> { self.handle_tree_selection(cfg, &activate.selected); } - fn handle_tree_drag( + pub(crate) fn handle_tree_drag( &mut self, cfg: &mut StaticallyKept, drag: &egui_ltreeview::DragAndDrop<u64>, @@ -506,7 +517,7 @@ impl<'a> EditorTabViewer<'a> { } } - fn handle_tree_move( + pub(crate) fn handle_tree_move( &mut self, cfg: &mut StaticallyKept, drag: &egui_ltreeview::DragAndDrop<u64>, @@ -552,7 +563,7 @@ impl<'a> EditorTabViewer<'a> { let component_id = selection.component_type_id; let matches = self .component_registry - .find_components_by_numeric_id(&*self.world, component_id); + .find_components_by_numeric_id(component_id); if matches.is_empty() { log::warn!("Component id #{} not found in world", component_id); @@ -562,7 +573,7 @@ impl<'a> EditorTabViewer<'a> { for (entity, component) in matches { log::debug!( "Serializable component '{}' (id #{}) attached to entity {:?}", - component.type_name(), + component, component_id, entity ); @@ -1,3 +1,9 @@ +use std::path::PathBuf; + +use egui::{Margin, RichText}; + +use crate::editor::{EditorTabViewer, console_error::{ConsoleItem, ErrorLevel}}; + impl<'a> EditorTabViewer<'a> { pub fn build_console(&mut self, ui: &mut egui::Ui) { fn analyse_error(log: &Vec<String>) -> Vec<ConsoleItem> { @@ -122,7 +128,7 @@ impl<'a> EditorTabViewer<'a> { ); } Err(e) => { - warn!( + eucalyptus_core::warn!( "Failed to open '{}' in VS Code: {}", location_arg, e ); @@ -1,7 +1,7 @@ //! This module should describe the different components that are editable in the resource inspector. use crate::editor::{Signal, StaticallyKept, UndoableAction}; -use dropbear_engine::asset::{AssetHandle, ASSET_REGISTRY}; +use dropbear_engine::asset::{ASSET_REGISTRY}; use dropbear_engine::attenuation::ATTENUATION_PRESETS; use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform}; use dropbear_engine::lighting::LightType; @@ -16,7 +16,6 @@ use hecs::Entity; use std::time::Instant; use dropbear_engine::procedural::ProcObj; use eucalyptus_core::properties::{CustomProperties, Value}; -use eucalyptus_core::ui::UIComponent; /// A trait that can added to any component that allows you to inspect the value in the editor. pub trait InspectableComponent { @@ -910,31 +909,7 @@ impl InspectableComponent for Script { _signal: &mut Signal, _label: &mut String, ) { - 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()) - } - }); - }); + } } @@ -1549,67 +1524,4 @@ impl InspectableComponent for Camera3D { }); ui.separator(); } -} - -impl InspectableComponent for UIComponent { - fn inspect( - &mut self, - _entity: &mut Entity, - cfg: &mut StaticallyKept, - ui: &mut Ui, - _undo_stack: &mut Vec<UndoableAction>, - _signal: &mut Signal, - _label: &mut String, - ) { - ui.vertical(|ui| { - ui.checkbox(&mut self.disabled, "Disable rendering of UI"); - - ui.separator(); - - let (rect, response) = ui.allocate_exact_size( - egui::vec2(ui.available_width(), 72.0), - egui::Sense::click(), - ); - - let fill = if response.hovered() { - ui.visuals().widgets.hovered.bg_fill - } else { - ui.visuals().widgets.inactive.bg_fill - }; - - ui.painter().rect_filled(rect, 4.0, fill); - ui.painter().rect_stroke( - rect, - 4.0, - ui.visuals().widgets.inactive.bg_stroke, - egui::StrokeKind::Inside, - ); - - let mut card_ui = ui.new_child( - UiBuilder::new() - .layout(egui::Layout::centered_and_justified(egui::Direction::LeftToRight)) - .max_rect(rect), - ); - - if let Some(uri) = self.ui_file.as_uri() { - card_ui.label(uri); - } else { - card_ui.label("Drop .kts file here"); - } - - let pointer_released = ui.input(|i| i.pointer.any_released()); - if pointer_released && response.hovered() { - if let Some(asset) = cfg.dragged_asset.clone() { - if let Some(uri) = asset.path.as_uri() { - if uri.ends_with(".kts") { - if let Ok(new_ref) = ResourceReference::from_euca_uri(uri) { - self.ui_file = new_ref; - } - cfg.dragged_asset = None; - } - } - } - } - }); - } } @@ -1,42 +1,24 @@ use super::*; -use crate::editor::{ - console_error::{ConsoleItem, ErrorLevel}, - ViewportMode, -}; +use crate::editor::ViewportMode; use std::{ - cmp::Ordering, - collections::{hash_map::DefaultHasher, HashMap}, - fs, - hash::{Hash, Hasher}, - io, - path::{Path, PathBuf}, + collections::HashMap + , + hash::Hash + , + path::PathBuf, sync::LazyLock, }; -use crate::editor::component::InspectableComponent; +use crate::editor::console::EucalyptusConsole; use crate::plugin::PluginRegistry; -use dropbear_engine::graphics::NO_TEXTURE; use dropbear_engine::utils::ResourceReference; -use dropbear_engine::{ - entity::{EntityTransform, MeshRenderer, Transform}, - lighting::LightComponent, -}; -use egui::{self, CollapsingHeader, Margin, RichText}; +use dropbear_engine::entity::{EntityTransform, Transform}; +use egui::{self}; use egui_dock::TabViewer; -use egui_ltreeview::{Action, NodeBuilder, TreeViewBuilder}; -use eucalyptus_core::{hierarchy::{Children, Hierarchy, Parent}, ui::UIComponent}; -use eucalyptus_core::states::{Label, Light, Script, PROJECT}; use eucalyptus_core::traits::registry::ComponentRegistry; -use hecs::{Entity, EntityBuilder, World}; -use indexmap::Equivalent; -use log; +use hecs::{Entity, World}; use parking_lot::Mutex; -use transform_gizmo_egui::{EnumSet, Gizmo, GizmoConfig, GizmoExt, GizmoMode, GizmoOrientation}; -use eucalyptus_core::physics::collider::{Collider, ColliderGroup}; -use eucalyptus_core::physics::kcc::KCC; -use eucalyptus_core::physics::rigidbody::RigidBody; -use eucalyptus_core::properties::CustomProperties; -use crate::editor::console::EucalyptusConsole; +use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode, GizmoOrientation}; pub struct EditorTabViewer<'a> { pub view: egui::TextureId, @@ -115,8 +97,8 @@ pub struct StaticallyKept { show_context_menu: bool, context_menu_pos: egui::Pos2, context_menu_tab: Option<EditorTab>, - is_focused: bool, - old_pos: Transform, + pub(crate) is_focused: bool, + pub(crate) old_pos: Transform, pub(crate) scale_locked: bool, pub(crate) old_label_entity: Option<hecs::Entity>, @@ -131,18 +113,18 @@ pub struct StaticallyKept { pub(crate) transform_rotation_cache: HashMap<Entity, glam::DVec3>, pub(crate) dragged_asset: Option<DraggedAsset>, - asset_node_assets: HashMap<u64, DraggedAsset>, + pub(crate) asset_node_assets: HashMap<u64, DraggedAsset>, - component_node_ids: HashMap<ComponentNodeKey, u64>, - component_node_lookup: HashMap<u64, ComponentNodeKey>, - next_component_node_id: u64, + pub(crate) component_node_ids: HashMap<ComponentNodeKey, u64>, + pub(crate) component_node_lookup: HashMap<u64, ComponentNodeKey>, + pub(crate) next_component_node_id: u64, pub(crate) last_component_lookup: Option<ComponentNodeSelection>, pub(crate) pending_component_drag: Option<ComponentNodeSelection>, pub(crate) root_node_selected: bool, } impl StaticallyKept { - fn next_component_node_id(&mut self) -> u64 { + pub(crate) fn next_component_node_id(&mut self) -> u64 { if self.next_component_node_id == 0 { self.next_component_node_id = 1; } @@ -154,7 +136,7 @@ impl StaticallyKept { id } - fn component_node_id(&mut self, entity: Entity, component_type_id: u64) -> u64 { + pub(crate) fn component_node_id(&mut self, entity: Entity, component_type_id: u64) -> u64 { let key = ComponentNodeKey::new(entity, component_type_id); if let Some(id) = self.component_node_ids.get(&key) { *id @@ -166,13 +148,13 @@ impl StaticallyKept { } } - fn component_selection(&self, node_id: u64) -> Option<ComponentNodeSelection> { + pub(crate) fn component_selection(&self, node_id: u64) -> Option<ComponentNodeSelection> { self.component_node_lookup .get(&node_id) .map(|key| key.as_selection(node_id)) } - fn remember_component_lookup(&mut self, selection: ComponentNodeSelection) { + pub(crate) fn remember_component_lookup(&mut self, selection: ComponentNodeSelection) { self.last_component_lookup = Some(selection); } } @@ -199,13 +181,12 @@ impl<'a> TabViewer for EditorTabViewer<'a> { } fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { - let mut cfg = TABS_GLOBAL.lock(); - ui.ctx().input(|i| { if i.pointer.button_pressed(egui::PointerButton::Secondary) && let Some(pos) = i.pointer.hover_pos() && ui.available_rect_before_wrap().contains(pos) { + let mut cfg = TABS_GLOBAL.lock(); cfg.show_context_menu = true; cfg.context_menu_pos = pos; cfg.context_menu_tab = Some(tab.clone()); @@ -220,7 +201,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> { self.entity_list(ui); } EditorTab::AssetViewer => { - self.show_asset_viewer(&mut cfg, ui); + self.show_asset_viewer(ui); } EditorTab::ResourceInspector => { self.resource_inspector(ui); @@ -314,592 +295,12 @@ impl<'a> TabViewer for EditorTabViewer<'a> { } } -impl<'a> EditorTabViewer<'a> { - fn show_asset_viewer(&mut self, cfg: &mut StaticallyKept, ui: &mut egui::Ui) { - let project_root = { - let project = PROJECT.read(); - if project.project_path.as_os_str().is_empty() { - ui.label("Open a project to browse assets."); - return; - } - project.project_path.clone() - }; - - 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(cfg, builder, &project_root); - Self::build_scripts_branch(cfg, builder, &project_root); - Self::build_scene_branch(cfg, builder, &project_root); - builder.close_dir(); - }); - - for a in action { - match a { - Action::SetSelected(selected) => { - log_once::debug_once!("Selected: {:?}", selected); - } - Action::Move(moved) => { - log_once::debug_once!("Moved: {:?}", moved); - } - Action::Drag(dragged) => { - log_once::debug_once!("Dragged: {:?}", dragged); - - if let Some(&node_id) = dragged.source.first() { - if let Some(asset) = cfg.asset_node_assets.get(&node_id).cloned() { - cfg.dragged_asset = Some(asset); - } - } - } - Action::Activate(activated) => { - log_once::debug_once!("Activated: {:?}", activated); - } - Action::DragExternal(_) => {} - Action::MoveExternal(_) => {} - } - } - } - - fn build_resource_branch( - cfg: &mut StaticallyKept, - builder: &mut TreeViewBuilder<u64>, - project_root: &Path, - ) { - let label = "euca://resources"; - 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); - } else { - Self::add_placeholder_leaf(builder, "euca://resources/missing", "missing"); - } - builder.close_dir(); - } - - fn walk_resource_directory( - cfg: &mut StaticallyKept, - builder: &mut TreeViewBuilder<u64>, - base_path: &Path, - current_path: &Path, - ) { - let entries = match Self::sorted_entries(current_path) { - Ok(entries) => entries, - Err(err) => { - log_once::warn_once!( - "Unable to enumerate resources at '{}': {}", - current_path.parent().unwrap_or(current_path).display(), - err - ); - return; - } - }; - - for entry in entries { - 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); - builder.close_dir(); - } else { - if entry.name.eq_ignore_ascii_case("resources.eucc") { - continue; - } - - 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()), - }, - ); - builder.node(Self::leaf_node_labeled(&full_label, &entry.name)); - } - } - } - - fn resource_label(base_path: &Path, path: &Path) -> String { - let relative = path - .strip_prefix(base_path) - .map(|rel| rel.to_string_lossy().replace('\\', "/")) - .unwrap_or_else(|_| path.to_string_lossy().replace('\\', "/")); - if relative.is_empty() { - "euca://resources".to_string() - } else { - format!("euca://resources/{}", relative) - } - } - - fn build_scripts_branch( - cfg: &mut StaticallyKept, - builder: &mut TreeViewBuilder<u64>, - project_root: &Path, - ) { - let label = "euca://scripts"; - 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); - builder.close_dir(); - return; - } - - let entries = match Self::sorted_entries(&scripts_root) { - Ok(entries) => entries, - Err(err) => { - log::warn!( - "Unable to enumerate scripts at '{}': {}", - scripts_root.display(), - err - ); - builder.close_dir(); - return; - } - }; - - let mut had_content = false; - for entry in entries { - if entry.is_dir { - let source_label = format!("{}/{}", label, entry.name); - builder.node(Self::dir_node_labeled(&source_label, &entry.name)); - if Self::build_script_source_set(builder, &entry.path, &source_label) { - had_content = true; - } - builder.close_dir(); - } else if !entry.name.eq_ignore_ascii_case("source.eucc") { - let file_label = format!("{}/{}", label, entry.name); - builder.node(Self::leaf_node_labeled(&file_label, &entry.name)); - had_content = true; - } - } - - if !had_content { - Self::add_placeholder_leaf(builder, "euca://scripts/empty", "empty"); - } - - builder.close_dir(); - } - - fn build_script_source_set( - builder: &mut TreeViewBuilder<u64>, - source_path: &Path, - source_label: &str, - ) -> bool { - let entries = match Self::sorted_entries(source_path) { - Ok(entries) => entries, - Err(err) => { - log::warn!( - "Unable to enumerate source set at '{}': {}", - source_path.display(), - err - ); - Self::add_placeholder_leaf( - builder, - &format!("{source_label}/unreadable"), - "unreadable", - ); - return true; - } - }; - - let mut had_content = false; - for entry in entries { - if entry.is_dir { - if entry.name.eq_ignore_ascii_case("kotlin") { - if Self::build_kotlin_tree(builder, &entry.path, source_label) { - had_content = true; - } - } else { - let child_label = format!("{}/{}", source_label, entry.name); - builder.node(Self::dir_node_labeled(&child_label, &entry.name)); - Self::build_plain_directory(builder, &entry.path, &child_label); - builder.close_dir(); - had_content = true; - } - } else if !entry.name.eq_ignore_ascii_case("source.eucc") { - let file_label = format!("{}/{}", source_label, entry.name); - builder.node(Self::leaf_node_labeled(&file_label, &entry.name)); - had_content = true; - } - } - - if !had_content { - Self::add_placeholder_leaf(builder, &format!("{source_label}/empty"), "empty"); - had_content = true; - } - - had_content - } - - fn build_plain_directory( - builder: &mut TreeViewBuilder<u64>, - dir_path: &Path, - parent_label: &str, - ) { - let entries = match Self::sorted_entries(dir_path) { - Ok(entries) => entries, - Err(err) => { - log::warn!( - "Unable to enumerate directory '{}': {}", - dir_path.display(), - err - ); - Self::add_placeholder_leaf( - builder, - &format!("{parent_label}/unreadable"), - "unreadable", - ); - return; - } - }; - - if entries.is_empty() { - Self::add_placeholder_leaf(builder, &format!("{parent_label}/empty"), "empty"); - return; - } - - for entry in entries { - let child_label = format!("{}/{}", parent_label, entry.name); - if entry.is_dir { - builder.node(Self::dir_node_labeled(&child_label, &entry.name)); - Self::build_plain_directory(builder, &entry.path, &child_label); - builder.close_dir(); - } else { - builder.node(Self::leaf_node_labeled(&child_label, &entry.name)); - } - } - } - - fn build_kotlin_tree( - builder: &mut TreeViewBuilder<u64>, - kotlin_path: &Path, - source_label: &str, - ) -> bool { - let entries = match Self::sorted_entries(kotlin_path) { - Ok(entries) => entries, - Err(err) => { - log::warn!( - "Unable to enumerate kotlin sources at '{}': {}", - kotlin_path.display(), - err - ); - Self::add_placeholder_leaf( - builder, - &format!("{source_label}/unreadable"), - "unreadable", - ); - return true; - } - }; - - if entries.is_empty() { - Self::add_placeholder_leaf( - builder, - &format!("{source_label}/no_kotlin_files"), - "no kotlin files", - ); - return true; - } - - let mut had_entries = false; - for entry in entries { - if entry.is_dir { - Self::build_kotlin_package_collapsed( - builder, - &entry.path, - source_label, - vec![entry.name.clone()], - ); - had_entries = true; - } else { - let file_id = format!("{}/{}", source_label, entry.name); - builder.node(Self::leaf_node_labeled(&file_id, &entry.name)); - had_entries = true; - } - } - - had_entries - } - - fn build_kotlin_package_collapsed( - builder: &mut TreeViewBuilder<u64>, - dir_path: &Path, - parent_path_str: &str, - accumulated_parts: Vec<String>, - ) { - let entries = match Self::sorted_entries(dir_path) { - Ok(entries) => entries, - Err(err) => { - let package_suffix = accumulated_parts.join("."); - let full_path_str = format!("{}/{}", parent_path_str, package_suffix); - log::warn!( - "Unable to enumerate package '{}' ({}): {}", - package_suffix, - dir_path.display(), - err - ); - Self::add_placeholder_leaf( - builder, - &format!("{full_path_str}/unreadable"), - "unreadable", - ); - return; - } - }; - - let subdirs: Vec<&FsEntry> = entries.iter().filter(|e| e.is_dir).collect(); - let files: Vec<&FsEntry> = entries.iter().filter(|e| !e.is_dir).collect(); - - if files.is_empty() && subdirs.len() == 1 { - let subdir = subdirs[0]; - let mut new_parts = accumulated_parts; - new_parts.push(subdir.name.clone()); - Self::build_kotlin_package_collapsed(builder, &subdir.path, parent_path_str, new_parts); - } else { - let package_suffix = accumulated_parts.join("."); - let full_path_str = format!("{}/{}", parent_path_str, package_suffix); - - builder.node(Self::dir_node_labeled(&full_path_str, &package_suffix)); - - for file in files { - let file_id = format!("{}/{}", full_path_str, file.name); - builder.node(Self::leaf_node_labeled(&file_id, &file.name)); - } - - for subdir in subdirs { - Self::build_kotlin_package_collapsed( - builder, - &subdir.path, - &full_path_str, - vec![subdir.name.clone()], - ); - } - - builder.close_dir(); - } - } - - fn build_scene_branch( - _cfg: &mut StaticallyKept, - builder: &mut TreeViewBuilder<u64>, - project_root: &Path, - ) { - let label = "euca://scenes"; - builder.node(Self::dir_node_labeled(label, "scenes")); - let scenes_root = project_root.join("scenes"); - if !scenes_root.exists() { - Self::add_placeholder_leaf(builder, "euca://scenes/missing", "missing"); - builder.close_dir(); - return; - } - - let entries = match Self::sorted_entries(&scenes_root) { - Ok(entries) => entries, - Err(err) => { - log::warn!( - "Unable to enumerate scenes at '{}': {}", - scenes_root.display(), - err - ); - Self::add_placeholder_leaf(builder, "euca://scenes/unreadable", "unreadable"); - builder.close_dir(); - return; - } - }; - - let mut had_entries = false; - for entry in entries { - if entry.is_dir { - let child_label = format!("{}/{}", label, entry.name); - builder.node(Self::dir_node_labeled(&child_label, &entry.name)); - Self::build_plain_directory(builder, &entry.path, &child_label); - builder.close_dir(); - had_entries = true; - } else if entry - .path - .extension() - .and_then(|ext| ext.to_str()) - .map(|ext| ext.eq_ignore_ascii_case("eucs")) - .unwrap_or(false) - { - let file_label = format!("{}/{}", label, entry.name); - builder.node(Self::leaf_node_labeled(&file_label, &entry.name)); - had_entries = true; - } - } - - if !had_entries { - Self::add_placeholder_leaf(builder, "euca://scenes/no_scenes", "no scenes"); - } - - builder.close_dir(); - } - - fn sorted_entries(path: &Path) -> io::Result<Vec<FsEntry>> { - let mut entries = Vec::new(); - for entry in fs::read_dir(path)? { - let entry = entry?; - let file_type = entry.file_type()?; - let name = entry.file_name().to_string_lossy().to_string(); - entries.push(FsEntry { - path: entry.path(), - name_lower: name.to_lowercase(), - name, - is_dir: file_type.is_dir(), - }); - } - - entries.sort_by(|a, b| match b.is_dir.cmp(&a.is_dir) { - Ordering::Equal => a.name_lower.cmp(&b.name_lower), - other => other, - }); - - Ok(entries) - } - - fn asset_node_id(label: &str) -> u64 { - let mut hasher = DefaultHasher::new(); - label.hash(&mut hasher); - let mut id = hasher.finish(); - if id == 0 { - id = 1; - } - id - } - - fn dir_node<'ui>(label: &str) -> NodeBuilder<'ui, u64> { - Self::with_icon(NodeBuilder::dir(Self::asset_node_id(label)).label(label.to_string())) - } - - fn dir_node_labeled<'ui>(id_source: &str, label: &str) -> NodeBuilder<'ui, u64> { - Self::with_icon(NodeBuilder::dir(Self::asset_node_id(id_source)).label(label.to_string())) - } - - fn leaf_node_labeled<'ui>(id_source: &str, label: &str) -> NodeBuilder<'ui, u64> { - Self::with_icon(NodeBuilder::leaf(Self::asset_node_id(id_source)).label(label.to_string())) - } - - fn with_icon<'ui>(builder: NodeBuilder<'ui, u64>) -> NodeBuilder<'ui, u64> { - builder.icon(|ui| { - egui_extras::install_image_loaders(ui.ctx()); - Self::draw_asset_icon(ui) - }) - } - - fn draw_asset_icon(ui: &mut egui::Ui) { - let image = egui::Image::from_bytes("bytes://asset-viewer-icon", NO_TEXTURE) - .max_size(egui::vec2(14.0, 14.0)); - ui.add(image); - } - - fn add_placeholder_leaf(builder: &mut TreeViewBuilder<u64>, id_source: &str, label: &str) { - builder.node(Self::leaf_node_labeled(id_source, label)); - } - - fn handle_tree_selection(&mut self, cfg: &mut StaticallyKept, items: &[u64]) { - for node_id in items { - self.resolve_tree_node(cfg, *node_id); - } - } - - fn handle_tree_activate( - &mut self, - cfg: &mut StaticallyKept, - activate: &egui_ltreeview::Activate<u64>, - ) { - self.handle_tree_selection(cfg, &activate.selected); - } - - fn handle_tree_drag( - &mut self, - cfg: &mut StaticallyKept, - drag: &egui_ltreeview::DragAndDrop<u64>, - ) { - if let Some(&node_id) = drag.source.first() { - if let Some(selection) = cfg.component_selection(node_id) { - cfg.pending_component_drag = Some(selection); - self.inspect_component_selection(cfg, selection); - } - } - } - - fn handle_tree_move( - &mut self, - cfg: &mut StaticallyKept, - drag: &egui_ltreeview::DragAndDrop<u64>, - ) { - let selection = cfg.pending_component_drag.take().or_else(|| { - drag.source - .first() - .and_then(|node_id| cfg.component_selection(*node_id)) - }); - - if let Some(selection) = selection { - self.inspect_component_selection(cfg, selection); - if let Some(target_entity) = Self::entity_from_node_id(drag.target) { - log::info!( - "Component id #{} ready to drop onto entity {:?}", - selection.component_type_id, - target_entity - ); - } - } - } - - fn resolve_tree_node(&mut self, cfg: &mut StaticallyKept, node_id: u64) { - if node_id == u64::MAX { - log_once::debug_once!("Root node has been selected"); - cfg.root_node_selected = true; - *self.selected_entity = None; - } else if let Some(selection) = cfg.component_selection(node_id) { - cfg.root_node_selected = false; - self.inspect_component_selection(cfg, selection); - } else if let Some(entity) = Self::entity_from_node_id(node_id) { - cfg.root_node_selected = false; - *self.selected_entity = Some(entity); - } - } - - fn inspect_component_selection( - &mut self, - cfg: &mut StaticallyKept, - selection: ComponentNodeSelection, - ) { - cfg.remember_component_lookup(selection); - let component_id = selection.component_type_id; - let matches = self - .component_registry - .find_components_by_numeric_id(&*self.world, component_id); - - if matches.is_empty() { - log::warn!("Component id #{} not found in world", component_id); - return; - } - - for (entity, component) in matches { - log::debug!( - "Serializable component '{}' (id #{}) attached to entity {:?}", - component.type_name(), - component_id, - entity - ); - } - } - - fn entity_from_node_id(node_id: u64) -> Option<Entity> { - if node_id == u64::MAX { - None - } else { - Entity::from_bits(node_id) - } - } -} - #[derive(Clone)] -struct FsEntry { - path: PathBuf, - name: String, - name_lower: String, - is_dir: bool, +pub(crate) struct FsEntry { + pub(crate) path: PathBuf, + pub(crate) name: String, + pub(crate) name_lower: String, + pub(crate) is_dir: bool, } #[derive(Debug, Clone, Copy)] @@ -1,8 +1,12 @@ -use crate::editor::{EditorTabViewer, TABS_GLOBAL}; +use egui_ltreeview::{NodeBuilder, TreeViewBuilder}; +use eucalyptus_core::{hierarchy::{Children, Hierarchy, Parent}, physics::{collider::ColliderGroup, rigidbody::RigidBody}, states::{Label, PROJECT}, traits::registry::ComponentRegistry}; +use hecs::{Entity, World}; + +use crate::editor::{EditorTabViewer, Signal, StaticallyKept, TABS_GLOBAL}; impl<'a> EditorTabViewer<'a> { pub(crate) fn entity_list(&mut self, ui: &mut egui::Ui) { - let cfg = TABS_GLOBAL.lock(); + let mut cfg = TABS_GLOBAL.lock(); let (_response, action) = egui_ltreeview::TreeView::new(egui::Id::new( "model_entity_list", @@ -59,21 +63,10 @@ impl<'a> EditorTabViewer<'a> { }); ui.menu_button("Add", |ui| { log_once::debug_once!("Available components: "); - for (id, name) in registry.iter_available_components() { - log_once::debug_once!("id: {}, name: {}", id, name); - // if name.contains("EntityTransform") { - // continue; - // } - let short_name = - name.split("::").last().unwrap_or(name); - let display_name = - if short_name == "SerializedMeshRenderer" { - "MeshRenderer" - } else { - short_name - }; - - if ui.button(display_name).clicked() { + for (id, fqtn) in registry.iter_available_components() { + log_once::debug_once!("id: {}, name: {}", id, fqtn); + + if ui.button(fqtn).clicked() { if let Some(component) = registry.create_default_component(id) { *signal = Signal::AddComponent(entity, component); } @@ -100,7 +93,7 @@ impl<'a> EditorTabViewer<'a> { continue; }; let component_node_id = - cfg.component_node_id(entity, component_type_id); + cfg.component_node_id(entity, component_type_id as u64); let display = format!("{} (id #{component_type_id})", component.display_name()); @@ -1,6 +1,6 @@ pub mod asset_viewer; pub mod build_console; -pub mod component; +// pub mod component; pub mod console_error; pub mod console; pub mod dock; @@ -23,7 +23,7 @@ use dropbear_engine::entity::EntityTransform; use dropbear_engine::graphics::InstanceRaw; use dropbear_engine::pipelines::light_cube::LightCubePipeline; use dropbear_engine::texture::{Texture, TextureWrapMode}; -use dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{SharedGraphicsContext}, model::{ModelId, MODEL_CACHE}, scene::SceneCommand, DropbearWindowBuilder, WindowData}; +use dropbear_engine::{camera::Camera, entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{SharedGraphicsContext}, scene::SceneCommand, DropbearWindowBuilder, WindowData}; use egui::{self, Context}; use egui_dock::{DockArea, DockState, NodeIndex, Style}; use eucalyptus_core::{register_components, APP_INFO}; @@ -83,7 +83,7 @@ pub struct Editor { pub dock_state: DockState<EditorTab>, pub texture_id: Option<egui::TextureId>, pub size: Extent3d, - pub instance_buffer_cache: HashMap<ModelId, ResizableBuffer<InstanceRaw>>, + pub instance_buffer_cache: HashMap<u64, ResizableBuffer<InstanceRaw>>, pub collider_wireframe_geometry_cache: HashMap<ColliderShapeKey, WireframeGeometry>, pub collider_instance_buffer: Option<ResizableBuffer<ColliderInstanceRaw>>, pub color: Color, @@ -95,6 +95,8 @@ pub struct Editor { pub collider_wireframe_pipeline: Option<ColliderWireframePipeline>, pub mipmapper: Option<MipMapper>, pub sky_pipeline: Option<SkyPipeline>, + pub(crate) default_skinning_buffer: Option<wgpu::Buffer>, + pub(crate) default_skinning_bind_group: Option<wgpu::BindGroup>, pub active_camera: Arc<Mutex<Option<Entity>>>, @@ -124,7 +126,6 @@ pub struct Editor { // might as well save some memory if its not required... // #[allow(unused)] // unused to allow for JVM to startup // pub(crate) script_manager: ScriptManager, - pub play_mode_backup: Option<PlayModeBackup>, /// State of the input pub(crate) input_state: Box<InputState>, @@ -242,7 +243,6 @@ impl Editor { gizmo_mode: EnumSet::empty(), gizmo_orientation: GizmoOrientation::Global, console: EucalyptusConsole::new(None), - play_mode_backup: None, input_state: Box::new(InputState::new()), light_cube_pipeline: None, active_camera: Arc::new(Mutex::new(None)), @@ -279,6 +279,8 @@ impl Editor { collider_instance_buffer: None, mipmapper: None, sky_pipeline: None, + default_skinning_buffer: None, + default_skinning_bind_group: None, }) } @@ -658,11 +660,6 @@ impl Editor { self.shader_globals = None; self.texture_id = None; self.light_cube_pipeline = None; - - { - let mut cache = MODEL_CACHE.lock(); - cache.clear(); - } } fn start_async_scene_load(&mut self, mut scene: SceneConfig, graphics: std::sync::Arc<dropbear_engine::graphics::SharedGraphicsContext>) { @@ -1183,119 +1180,6 @@ impl Editor { stats.show_window = open_flag; } - /// Restores transform components back to its original state before PlayMode. - pub fn restore(&mut self) -> anyhow::Result<()> { - if let Some(window) = &self.window { - let _ = window.set_cursor_grab(CursorGrabMode::None); - } - - if let Some(backup) = &self.play_mode_backup { - for ( - entity_id, - original_mesh_renderer, - original_transform, - original_properties, - original_script, - ) in &backup.entities - { - if let Ok(mut mesh_renderer) = self.world.get::<&mut MeshRenderer>(*entity_id) { - mesh_renderer.clone_from(original_mesh_renderer); - mesh_renderer.sync_asset_registry(); - } - - if let Ok(mut transform) = self.world.get::<&mut EntityTransform>(*entity_id) { - *transform = *original_transform; - } - - if let Ok(mut properties) = self.world.get::<&mut CustomProperties>(*entity_id) { - properties.clone_from(original_properties); - } - - let has_script = self.world.get::<&Script>(*entity_id).is_ok(); - match (has_script, original_script) { - (true, Some(original)) => { - if let Ok(mut script) = self.world.get::<&mut Script>(*entity_id) { - *script = original.clone(); - } - } - (true, None) => { - let _ = self.world.remove_one::<Script>(*entity_id); - } - (false, Some(original)) => { - let _ = self.world.insert_one(*entity_id, original.clone()); - } - (false, None) => {} - } - } - - for (entity_id, original_camera, original_camera_component) in &backup.camera_data { - if let Ok(mut camera) = self.world.get::<&mut Camera>(*entity_id) { - *camera = original_camera.clone(); - } - - if let Ok(mut camera_component) = self.world.get::<&mut CameraComponent>(*entity_id) - { - *camera_component = original_camera_component.clone(); - } - } - - log::info!("Restored scene from play mode backup"); - - self.play_mode_backup = None; - Ok(()) - } else { - Err(anyhow::anyhow!("No play mode backup found to restore")) - } - } - - pub fn create_backup(&mut self) -> anyhow::Result<()> { - let mut entities = Vec::new(); - - for (entity_id, mesh_renderer, transform, properties) in self - .world - .query::<(Entity, &MeshRenderer, &EntityTransform, &CustomProperties)>() - .iter() - { - let script = self - .world - .query_one::<&Script>(entity_id) - .get() - .ok() - .cloned(); - entities.push(( - entity_id, - mesh_renderer.clone(), - *transform, - properties.clone(), - script, - )); - } - - let mut camera_data = Vec::new(); - - for (entity_id, camera, component) in - self.world.query::<(Entity, &Camera, &CameraComponent)>().iter() - { - camera_data.push((entity_id, camera.clone(), component.clone())); - } - - let backup = PlayModeBackup { - entities, - camera_data, - }; - - let entity_count = backup.entities.len(); - let camera_count = backup.camera_data.len(); - self.play_mode_backup = Some(backup); - - log::info!( - "Created play mode backup with {} entities and {} cameras", - entity_count, - camera_count - ); - Ok(()) - } - pub fn switch_to_debug_camera(&mut self) { let debug_camera = self .world @@ -1538,53 +1422,9 @@ pub enum Signal { LogEntities, /// Adds a new component instance using the async init pipeline. AddComponent(hecs::Entity, Box<dyn SerializableComponent>), - - /// Loads a model from a URI/path and swaps it onto an existing MeshRenderer (or adds one if missing). - ReplaceModel(hecs::Entity, String), - - /// Clears the currently selected model (sets MeshRenderer to an unassigned placeholder). - ClearModel(hecs::Entity), - - /// Legacy model load signal used by entity spawning flows. - LoadModel(hecs::Entity, String), - - /// Switches the entity's MeshRenderer to a procedural cuboid. - SetProceduralCuboid(hecs::Entity, [f32; 3]), - /// Updates the extents for an existing procedural cuboid renderer. - UpdateProceduralCuboid(hecs::Entity, [f32; 3]), - - /// Applies a diffuse texture to a material by loading from a URI/path. - SetMaterialTexture(hecs::Entity, String, String, TextureWrapMode), - - /// Changes the sampler wrap mode for a material. - SetMaterialWrapMode(hecs::Entity, String, TextureWrapMode), - - /// Sets UV tiling (repeat counts) for a material. - SetMaterialUvTiling(hecs::Entity, String, [f32; 2]), - /// Removes the current material texture (replaces with a neutral fallback). - ClearMaterialTexture(hecs::Entity, String), - /// Sets a material tint colour (RGBA, unmultiplied). - SetMaterialTint(hecs::Entity, String, [f32; 4]), - - /// Sets (bakes) the import scale for an entity's MeshRenderer. - /// - /// This updates the MeshRenderer's baked import scale (saved into the scene and used at runtime). - SetModelImportScale(hecs::Entity, f32), RequestNewWindow(WindowData), } -#[derive(Clone)] -pub struct PlayModeBackup { - entities: Vec<( - Entity, - MeshRenderer, - EntityTransform, - CustomProperties, - Option<Script>, - )>, - camera_data: Vec<(Entity, Camera, CameraComponent)>, -} - #[derive(Debug)] pub enum EditorState { Editing, @@ -1,303 +1,20 @@ -use egui::CollapsingHeader; -use hecs::Entity; -use indexmap::Equivalent; -use dropbear_engine::camera::Camera; -use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform}; -use dropbear_engine::lighting::LightComponent; -use eucalyptus_core::camera::{CameraComponent, CameraType}; -use eucalyptus_core::properties::CustomProperties; -use eucalyptus_core::states::{Label, Light, Script}; -use eucalyptus_core::{success, warn}; -use eucalyptus_core::physics::collider::{Collider, ColliderGroup}; -use eucalyptus_core::physics::kcc::KCC; -use eucalyptus_core::physics::rigidbody::RigidBody; -use crate::editor::{EditorTabViewer, UndoableAction, TABS_GLOBAL}; +use crate::editor::{EditorTabViewer, TABS_GLOBAL}; impl<'a> EditorTabViewer<'a> { pub(crate) fn resource_inspector(&mut self, ui: &mut egui::Ui) { let mut cfg = TABS_GLOBAL.lock(); let local_scene_settings = cfg.root_node_selected; - let mut local_add_collider: Option<Entity> = None; if let Some(entity) = self.selected_entity { - let mut local_set_initial_camera = false; - let mut inspect_entity = *entity; - let world = &self.world; + let inspect_entity = *entity; if !cfg.root_node_selected { - if let Ok((label, )) = world.query_one::<(&mut Label,)>(inspect_entity).get() { - label.inspect( - &mut inspect_entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut String::new(), - ); + ui.label(format!("Entity ID: {}", inspect_entity.id())); + ui.separator(); - ui.label(format!("Entity ID: {}", inspect_entity.id())); - - ui.separator(); - - // mesh renderer - if let Ok(e) = world.query_one::<&mut MeshRenderer>(inspect_entity).get() - { - CollapsingHeader::new("MeshRenderer").default_open(true).show(ui, |ui| { - e.inspect( - &mut inspect_entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut String::new(), - ); - }); - } - - // entity transform - if let Ok(t) = world.query_one::<&mut EntityTransform>(inspect_entity).get() - { - CollapsingHeader::new("Transform").default_open(true).show(ui, |ui| { - t.inspect( - &mut inspect_entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut String::new(), - ); - }); - ui.separator(); - } - - // custom properties - if let Ok(props) = world.query_one::<&mut CustomProperties>(inspect_entity).get() - { - CollapsingHeader::new("Custom Properties").default_open(true).show(ui, |ui| { - props.inspect( - &mut inspect_entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - label.as_mut_string(), - ); - }); - ui.separator(); - } - - // camera - if let Ok((camera, camera_component)) = world - .query_one::<(&mut Camera, &mut CameraComponent)>(inspect_entity).get() - { - CollapsingHeader::new("Camera").default_open(true).show(ui, |ui| { - camera.inspect( - &mut inspect_entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut String::new(), - ); - - ui.separator(); - - camera_component.inspect( - &mut inspect_entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut camera.label.clone(), - ); - - ui.separator(); - - // camera controller - let mut active_camera = self.active_camera.lock(); - - if active_camera.equivalent(&Some(*entity)) { - ui.label("Status: Currently viewing through camera"); - } else { - ui.label("Status: Not viewing through this camera"); - } - - if ui.button("Set as active camera").clicked() { - *active_camera = Some(*entity); - log::info!( - "Currently viewing from camera angle '{}'", - camera.label - ); - } - - if camera_component.starting_camera { - if ui.button("Stop making camera initial").clicked() { - log::debug!("'Stop making camera initial' button clicked"); - camera_component.starting_camera = false; - success!("Removed {} from starting camera", camera.label); - } - } else if ui.button("Set as initial camera").clicked() { - log::debug!("'Set as initial camera' button clicked"); - if matches!(camera_component.camera_type, CameraType::Debug) { - warn!( - "Cannot set any cameras of type 'Debug' to initial camera" - ); - } else { - local_set_initial_camera = true - } - } - }); - ui.separator(); - } - - // light - if let Ok((light, comp, transform)) = world.query_one::<(&mut Light, &mut LightComponent, &mut Transform)>(inspect_entity).get() - { - light.transform = *transform; - light.light_component = comp.clone(); - - light.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut String::new(), - ); - - *transform = light.transform; - *comp = light.light_component.clone(); - ui.separator(); - } - - // script - if let Ok((script, ui_c)) = world.query_one::<(Option<&mut Script>, Option<&mut UIComponent>)>(*entity).get() - { - CollapsingHeader::new("Script").default_open(true).show(ui, |ui| { - if let Some(s) = script { - s.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - label.as_mut_string(), - ); - } - - if let Some(ui_c) = ui_c { - CollapsingHeader::new("UI").default_open(true).show(ui, |ui| { - ui_c.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - label.as_mut_string(), - ); - }); - } - }); - ui.separator(); - } - - // physics - if let Ok((rigid, colliders, kcc)) = world.query_one::<(Option<&mut RigidBody>, Option<&mut ColliderGroup>, Option<&mut KCC>)>(*entity).get() - { - if rigid.is_some() || colliders.is_some() || kcc.is_some() { - CollapsingHeader::new("Physics").default_open(true).show(ui, |ui| { - - if let Some(kcc) = kcc { - CollapsingHeader::new("Kinematic Character Controller").default_open(true).show(ui, |ui| { - kcc.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - label.as_mut_string(), - ); - }); - ui.separator(); - } - - if let Some(rigid) = rigid { - CollapsingHeader::new("RigidBody").default_open(true).show(ui, |ui| { - rigid.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - label.as_mut_string(), - ); - }); - ui.separator(); - } - - if let Some(col) = colliders { - CollapsingHeader::new("Colliders").default_open(true).show(ui, |ui| { - let mut to_remove: Option<usize> = None; - - for (index, c) in col.colliders.iter_mut().enumerate() { - ui.horizontal(|ui| { - let header = CollapsingHeader::new(format!("Collider {}", index + 1)) - .default_open(true); - - header.show(ui, |ui| { - c.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - label.as_mut_string(), - ); - }); - - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.button("🗑").on_hover_text("Remove collider").clicked() { - to_remove = Some(index); - } - }); - }); - - ui.separator(); - } - - if let Some(index) = to_remove { - col.colliders.remove(index); - } - - if ui.button("➕ Add new collider").clicked() { - local_add_collider = Some(*entity); - } - }); - } - - ui.separator(); - }); - } - ui.separator(); - } - } else { - log_once::debug_once!("Unable to query entity inside resource inspector"); - } - } - - if local_set_initial_camera { - for (id, comp) in self.world.query::<(Entity, &mut CameraComponent)>().iter() { - comp.starting_camera = false; - self.undo_stack - .push(UndoableAction::RemoveStartingCamera(id)) - } - - if let Ok(comp) = self.world.query_one_mut::<&mut CameraComponent>(*entity) - { - success!("This camera is currently set as the initial camera"); - comp.starting_camera = true; - } + self.component_registry + .inspect_components(self.world, inspect_entity, ui); } } else if !local_scene_settings { ui.label("No entity selected, therefore no info to provide. Go on, what are you waiting for? Click an entity!"); @@ -307,23 +24,5 @@ impl<'a> EditorTabViewer<'a> { log_once::debug_once!("Rendering scene settings"); self.scene_settings(&mut cfg, ui); } - - if let Some(e) = local_add_collider { - let mut manual_edit = false; - if let Ok(col) = self.world.query_one::<Option<&mut ColliderGroup>>(e).get() - { - if let Some(col) = col { - let mut collider = Collider::new(); - collider.id = col.colliders.len() as u32 + 1; - col.insert(collider); - } else { - manual_edit = true; - } - } - - if manual_edit { - let _ = self.world.insert_one(e, ColliderGroup::new()); - } - } } } @@ -1,14 +1,15 @@ use crossbeam_channel::unbounded; +use dropbear_engine::animation::AnimationComponent; use dropbear_engine::buffer::ResizableBuffer; -use glam::DMat4; +use glam::{DMat4, Mat4}; use wgpu::util::DeviceExt; use std::collections::HashMap; +use std::sync::Arc; use super::*; use crate::signal::SignalController; use crate::spawn::PendingSpawnController; -use dropbear_engine::asset::{PointerKind, ASSET_REGISTRY}; +use dropbear_engine::asset::{ASSET_REGISTRY, Handle}; use dropbear_engine::graphics::{CommandEncoder, InstanceRaw}; -use dropbear_engine::model::MODEL_CACHE; use dropbear_engine::{ entity::{EntityTransform, MeshRenderer, Transform}, lighting::{Light, LightComponent, MAX_LIGHTS}, @@ -21,6 +22,7 @@ use log; use parking_lot::Mutex; use wgpu::Color; use winit::{event_loop::ActiveEventLoop, keyboard::KeyCode}; +use dropbear_engine::component::ComponentResources; use eucalyptus_core::physics::collider::ColliderGroup; use eucalyptus_core::physics::collider::ColliderShapeKey; use eucalyptus_core::physics::collider::shader::{ColliderInstanceRaw, create_wireframe_geometry}; @@ -122,6 +124,14 @@ impl Scene for Editor { } } + { + let mut resources = ComponentResources::new(); + resources.insert(graphics.clone()); + let resources = Arc::new(resources); + self.component_registry + .update_components(self.world.as_mut(), dt, &resources); + } + if !self.is_world_loaded.is_fully_loaded() { log::debug!("Scene is not fully loaded, initialising..."); return; @@ -204,9 +214,6 @@ impl Scene for Editor { } } - let cache_mutex_ptr = std::sync::LazyLock::force(&MODEL_CACHE) as *const _; - ASSET_REGISTRY.add_pointer(PointerKind::Const("model_cache"), cache_mutex_ptr as usize); - if let Some((_, tab)) = self.dock_state.find_active_focused() { self.is_viewport_focused = matches!(tab, EditorTab::Viewport); } else { @@ -347,30 +354,14 @@ impl Scene for Editor { } fn render<'a>(&mut self, graphics: Arc<SharedGraphicsContext>) { - self.size = graphics.viewport_texture.size; - self.texture_id = Some(*graphics.texture_id.clone()); - self.window = Some(graphics.window.clone()); + self.editor_specific_render(&graphics); let hdr = graphics.hdr.read(); - self.show_ui(&graphics.get_egui_context()); - let clear_color = Color { - r: 100.0 / 255.0, - g: 149.0 / 255.0, - b: 237.0 / 255.0, - a: 1.0, - }; - self.color = clear_color; - eucalyptus_core::logging::render(&graphics.get_egui_context()); - - let mut encoder = CommandEncoder::new(graphics.clone(), Some("editor viewport encoder")); - - let cam = { - let c = self.active_camera.lock(); - *c - }; + let mut encoder = CommandEncoder::new(graphics.clone(), Some("runtime viewport encoder")); - let Some(active_camera) = cam else { + let active_camera = {self.active_camera.lock().as_ref().cloned()}; + let Some(active_camera) = active_camera else { return; }; log_once::debug_once!("Active camera found: {:?}", active_camera); @@ -438,32 +429,34 @@ impl Scene for Editor { globals.write(&graphics.queue); } - let renderers = { - let mut renderers = Vec::new(); - let mut query = self.world.query::<&MeshRenderer>(); - for renderer in query.iter() { - renderers.push(renderer.clone()); - } - renderers - }; + let mut static_batches: HashMap<u64, Vec<InstanceRaw>> = HashMap::new(); + let mut animated_instances: Vec<(u64, InstanceRaw, wgpu::BindGroup)> = Vec::new(); + + { + let mut query = self + .world + .query::<(&MeshRenderer, Option<&AnimationComponent>)>(); - let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new(); - for renderer in &renderers { - model_batches - .entry(renderer.model_id()) - .or_default() - .push(renderer.instance.to_raw()); + for (renderer, animation) in query.iter() { + let handle = renderer.model(); + if handle.is_null() { + continue; + } + + let instance = renderer.instance.to_raw(); + if let Some(bind_group) = animation.and_then(|anim| anim.bind_group.clone()) { + animated_instances.push((handle.id, instance, bind_group)); + } else { + static_batches.entry(handle.id).or_default().push(instance); + } + } } + let registry = ASSET_REGISTRY.read(); let mut prepared_models = Vec::new(); - for (model_id, instances) in model_batches { - let model_opt = { - let cache = MODEL_CACHE.lock(); - cache.values().find(|model| model.id == model_id).cloned() - }; - - let Some(model) = model_opt else { - log_once::error_once!("Missing model {:?} in cache", model_id); + for (handle, instances) in static_batches { + let Some(model) = registry.get_model(Handle::new(handle)).cloned() else { + log_once::error_once!("Missing model handle {} in registry", handle); continue; }; @@ -499,12 +492,13 @@ impl Scene for Editor { } } + let registry = ASSET_REGISTRY.read(); { let mut render_pass = encoder .begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("light cube render pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: hdr.view(), + view: hdr.view(), depth_slice: None, resolve_target: None, ops: wgpu::Operations { @@ -527,17 +521,54 @@ impl Scene for Editor { render_pass.set_pipeline(light_pipeline.pipeline()); for (light, component) in &lights { render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..)); - if component.visible { - render_pass.draw_light_model( - &light.cube_model, - &camera.bind_group, - &light.bind_group - ); + if !component.visible { + continue; } + + let Some(model) = registry.get_model(light.cube_model) else { + log_once::error_once!( + "Missing light cube model handle {} in registry", + light.cube_model.id + ); + continue; + }; + + render_pass.draw_light_model( + model, + &camera.bind_group, + &light.bind_group, + ); } } } + if self.default_skinning_bind_group.is_none() { + let identity = [Mat4::IDENTITY.to_cols_array_2d()]; + let buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("default skinning buffer"), + contents: bytemuck::cast_slice(&identity), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("default skinning bind group"), + layout: &graphics.layouts.skinning_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }], + }); + + self.default_skinning_buffer = Some(buffer); + self.default_skinning_bind_group = Some(bind_group); + } + + let default_skinning_bind_group = self + .default_skinning_bind_group + .as_ref() + .expect("Default skinning bind group not initialized"); + + // model rendering if let Some(lcp) = &self.light_cube_pipeline { for (model, instance_buffer, instance_count) in prepared_models { let globals_bind_group = &self @@ -572,15 +603,85 @@ impl Scene for Editor { render_pass.set_pipeline(pipeline.pipeline()); render_pass.set_vertex_buffer(1, instance_buffer.slice(..)); render_pass.set_bind_group(3, globals_bind_group, &[]); - render_pass.draw_model_instanced( - &model, - 0..instance_count, - &camera.bind_group, - lcp.bind_group(), + + for mesh in &model.meshes { + let material = &model.materials[mesh.material]; + render_pass.draw_mesh_instanced( + mesh, + material, + 0..instance_count, + &camera.bind_group, + lcp.bind_group(), + Some(default_skinning_bind_group), + ); + } + } + } + + if let Some(lcp) = &self.light_cube_pipeline { + let globals_bind_group = &self + .shader_globals + .as_ref() + .expect("Shader globals not initialised") + .bind_group; + + for (handle, instance, skin_bind_group) in animated_instances { + let Some(model) = registry.get_model(Handle::new(handle)).cloned() else { + log_once::error_once!("Missing model handle {} in registry", handle); + continue; + }; + + let instance_buffer = graphics.device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Runtime Animated Instance Buffer"), + contents: bytemuck::cast_slice(&[instance]), + usage: wgpu::BufferUsages::VERTEX, + }, ); + + let mut render_pass = encoder + .begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("model render pass (animated)"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: hdr.view(), + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &graphics.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + occlusion_query_set: None, + timestamp_writes: None, + }); + + render_pass.set_pipeline(pipeline.pipeline()); + render_pass.set_vertex_buffer(1, instance_buffer.slice(..)); + render_pass.set_bind_group(3, globals_bind_group, &[]); + + for mesh in &model.meshes { + let material = &model.materials[mesh.material]; + render_pass.draw_mesh_instanced( + mesh, + material, + 0..1, + &camera.bind_group, + lcp.bind_group(), + Some(&skin_bind_group), + ); + } } } + // collider pipeline { let show_hitboxes = self .current_scene_name @@ -714,44 +815,44 @@ impl Scene for Editor { ); } } -} + } } + } - if let Some(sky) = &self.sky_pipeline { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("sky render pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: hdr.view(), - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - depth_slice: None, - })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &graphics.depth_texture.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }), - stencil_ops: None, + if let Some(sky) = &self.sky_pipeline { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("sky render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: hdr.view(), + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &graphics.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, }), - timestamp_writes: None, - occlusion_query_set: None, - }); + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); - render_pass.set_pipeline(&sky.pipeline); - render_pass.set_bind_group(0, &camera.bind_group, &[]); - render_pass.set_bind_group(1, &sky.bind_group, &[]); - render_pass.draw(0..3, 0..1); - } + render_pass.set_pipeline(&sky.pipeline); + render_pass.set_bind_group(0, &camera.bind_group, &[]); + render_pass.set_bind_group(1, &sky.bind_group, &[]); + render_pass.draw(0..3, 0..1); + } - hdr.process(&mut encoder, &graphics.viewport_texture.view); + hdr.process(&mut encoder, &graphics.viewport_texture.view); - if let Err(e) = encoder.submit() { - log_once::error_once!("{}", e); - } + if let Err(e) = encoder.submit() { + log_once::error_once!("{}", e); } } @@ -760,4 +861,15 @@ impl Scene for Editor { fn run_command(&mut self) -> SceneCommand { std::mem::replace(&mut self.scene_command, SceneCommand::None) } +} + +impl Editor { + fn editor_specific_render(&mut self, graphics: &Arc<SharedGraphicsContext>) { + self.size = graphics.viewport_texture.size; + self.texture_id = Some(*graphics.texture_id.clone()); + self.window = Some(graphics.window.clone()); + + self.show_ui(&graphics.get_egui_context()); + eucalyptus_core::logging::render(&graphics.get_egui_context()); + } } @@ -1,3 +1,3 @@ -pub mod rigidbody; -pub mod collider; -pub mod kcc; +// pub mod rigidbody; +// pub mod collider; +// pub mod kcc; @@ -2,7 +2,6 @@ use egui::{ComboBox, Ui}; use hecs::Entity; use eucalyptus_core::physics::collider::{Collider, ColliderShape}; use eucalyptus_core::states::Label; -use crate::editor::component::InspectableComponent; use crate::editor::{Signal, StaticallyKept, UndoableAction}; impl InspectableComponent for Collider { @@ -4,13 +4,13 @@ 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_engine::model::{LoadedModel, Material, Model, MODEL_CACHE}; +use dropbear_engine::model::{Material, Model}; use dropbear_engine::procedural::{ProcedurallyGeneratedObject, ProcObj}; use dropbear_engine::texture::{Texture, TextureWrapMode}; use dropbear_engine::utils::{ relative_path_from_euca, EUCA_SCHEME, ResourceReference, ResourceReferenceType, }; -use dropbear_traits::{ComponentInitContext, ComponentInsert, ComponentResources}; +use dropbear_engine::component::{ComponentInitContext, ComponentInsert, ComponentResources}; use egui::Align2; use eucalyptus_core::camera::{CameraComponent, CameraType}; use eucalyptus_core::properties::CustomProperties; @@ -90,14 +90,6 @@ impl SignalController for Editor { self.signal = Signal::Copy(scene_entity.clone()); Ok(()) } - - Signal::SetModelImportScale(entity, scale) => { - if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) { - renderer.set_import_scale(*scale); - } - self.signal = Signal::None; - Ok(()) - } Signal::Delete => { if let Some(sel_e) = &self.selected_entity { let is_viewport_cam = @@ -588,417 +580,6 @@ impl SignalController for Editor { self.signal = Signal::None; Ok(()) } - - Signal::ClearModel(entity) => { - let unassigned_id = (*entity).to_bits().get(); - let reference = ResourceReference::from_reference( - ResourceReferenceType::Unassigned { id: unassigned_id }, - ); - - let model = std::sync::Arc::new(Model { - label: "None".to_string(), - hash: unassigned_id, - path: reference, - meshes: Vec::new(), - materials: Vec::new(), - skins: Vec::new(), - animations: Vec::new(), - nodes: Vec::new(), - }); - let loaded_model = LoadedModel::new_raw(&dropbear_engine::asset::ASSET_REGISTRY, model); - - if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) { - renderer.set_handle(loaded_model); - } else { - let renderer = MeshRenderer::from_handle(loaded_model); - let _ = self.world.insert_one(*entity, renderer); - } - - self.signal = Signal::None; - Ok(()) - } - Signal::ReplaceModel(entity, uri) => { - let graphics_clone = graphics.clone(); - let uri_clone = uri.clone(); - let future = async move { - let mut loaded_model = if is_legacy_internal_cube_uri(&uri_clone) { - let size = glam::DVec3::new(1.0, 1.0, 1.0); - let size_bits = [1.0f32.to_bits(), 1.0f32.to_bits(), 1.0f32.to_bits()]; - let mut loaded = ProcedurallyGeneratedObject::cuboid(size) - .build_model(graphics_clone.clone(), None, Some("Cuboid"), ASSET_REGISTRY.clone()); - - let model = loaded.make_mut(); - model.path = ResourceReference::from_reference(ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits })); - loaded.refresh_registry(); - loaded - } else { - let path = resolve_editor_path(&uri_clone); - Model::load(graphics_clone.clone(), &path, Some(&uri_clone), None).await? - }; - - // Ensure imports start as pure white; users can tint later. - { - let model = loaded_model.make_mut(); - for material in &mut model.materials { - material.set_tint(graphics_clone.as_ref(), [1.0, 1.0, 1.0, 1.0]); - } - } - - loaded_model.refresh_registry(); - Ok::<LoadedModel, anyhow::Error>(loaded_model) - }; - - let handle = graphics.future_queue.push(Box::pin(future)); - self.pending_model_swaps.push((*entity, handle)); - success!("Queued model swap for entity {:?} from '{}'", entity, uri); - self.signal = Signal::None; - Ok(()) - } - - Signal::LoadModel(entity, uri) => { - let graphics_clone = graphics.clone(); - let uri_clone = uri.clone(); - let future = async move { - let renderer = if is_legacy_internal_cube_uri(&uri_clone) { - let size = glam::DVec3::new(1.0, 1.0, 1.0); - let size_bits = [1.0f32.to_bits(), 1.0f32.to_bits(), 1.0f32.to_bits()]; - let mut loaded_model = ProcedurallyGeneratedObject::cuboid(size) - .build_model(graphics_clone.clone(), None, Some("Cuboid"), ASSET_REGISTRY.clone()); - - { - let model = loaded_model.make_mut(); - model.path = ResourceReference::from_reference(ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits })); - for material in &mut model.materials { - material.set_tint(graphics_clone.as_ref(), [1.0, 1.0, 1.0, 1.0]); - } - } - loaded_model.refresh_registry(); - - MeshRenderer::from_handle(loaded_model) - } else { - let path = resolve_editor_path(&uri_clone); - let mut model = dropbear_engine::model::Model::load( - graphics_clone.clone(), - &path, - Some(&uri_clone), - None, - ) - .await?; - - { - let model_mut = model.make_mut(); - for material in &mut model_mut.materials { - material.set_tint(graphics_clone.as_ref(), [1.0, 1.0, 1.0, 1.0]); - } - } - - model.refresh_registry(); - dropbear_engine::entity::MeshRenderer::from_handle(model) - }; - - Ok::<Box<dyn ComponentInsert>, anyhow::Error>(Box::new(InsertMeshRenderer { - renderer, - ensure_transform: true, - })) - }; - - let handle = graphics.future_queue.push(Box::pin(future)); - self.pending_components.push((*entity, handle)); - success!("Queued model load for entity {:?} from '{}'", entity, uri); - self.signal = Signal::None; - Ok(()) - } - - Signal::SetProceduralCuboid(entity, size) - | Signal::UpdateProceduralCuboid(entity, size) => { - let previous_customisation: Option< - Vec<(String, [f32; 4], Option<String>, TextureWrapMode, [f32; 2])>, - > = - self.world - .get::<&MeshRenderer>(*entity) - .ok() - .map(|renderer| { - renderer - .model() - .materials - .iter() - .map(|mat| { - ( - mat.name.clone(), - mat.tint, - mat.texture_tag.clone(), - mat.wrap_mode, - mat.uv_tiling, - ) - }) - .collect() - }); - - let label = self - .world - .get::<&eucalyptus_core::states::Label>(*entity) - .map(|l| l.to_string()) - .unwrap_or_else(|_| "Cuboid".to_string()); - - { - let mut cache_guard = MODEL_CACHE.lock(); - cache_guard.remove(&label); - } - - let size_bits = [size[0].to_bits(), size[1].to_bits(), size[2].to_bits()]; - let size_vec = glam::DVec3::new(size[0] as f64, size[1] as f64, size[2] as f64); - - let mut loaded_model = ProcedurallyGeneratedObject::cuboid(size_vec) - .build_model(graphics.clone(), None, Some(&label), ASSET_REGISTRY.clone()); - - { - let model = loaded_model.make_mut(); - model.path = ResourceReference::from_reference(ResourceReferenceType::ProcObj(ProcObj::Cuboid { size_bits })); - } - loaded_model.refresh_registry(); - - if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) { - renderer.set_handle(loaded_model); - - if let Some(previous) = previous_customisation { - let model = renderer.make_model_mut(); - for (mat_name, tint, texture_tag, wrap_mode, uv_tiling) in previous { - if let Some(material) = - model.materials.iter_mut().find(|m| m.name == mat_name) - { - material.wrap_mode = wrap_mode; - material.set_tint(graphics.as_ref(), tint); - material.set_uv_tiling(graphics.as_ref(), uv_tiling); - - if let Some(uri) = texture_tag { - if uri.to_ascii_lowercase().ends_with(".png") - || uri.to_ascii_lowercase().ends_with(".jpg") - || uri.to_ascii_lowercase().ends_with(".jpeg") - || uri.to_ascii_lowercase().ends_with(".tga") - || uri.to_ascii_lowercase().ends_with(".bmp") - { - let path = resolve_editor_path(&uri); - - if let Ok(bytes) = std::fs::read(&path) { - let diffuse = Texture::from_bytes_verbose_mipmapped( - graphics.clone(), - &bytes, - None, - None, - Some(Texture::sampler_from_wrap(wrap_mode)), - Some(mat_name.as_str()), - ); - let flat_normal = (*dropbear_engine::asset::ASSET_REGISTRY - .solid_texture_rgba8( - graphics.clone(), - [128, 128, 255, 255], - )) - .clone(); - - material.diffuse_texture = diffuse; - material.normal_texture = flat_normal; - material.bind_group = - dropbear_engine::model::Material::create_bind_group( - graphics.as_ref(), - &material.diffuse_texture, - &material.normal_texture, - &material.tint_buffer, - &material.name, - ); - material.texture_tag = Some(uri); - } - } else { - material.texture_tag = Some(uri); - } - } - } - } - - renderer.sync_asset_registry(); - } - } else { - let renderer = MeshRenderer::from_handle(loaded_model); - let _ = self.world.insert_one(*entity, renderer); - } - - self.signal = Signal::None; - Ok(()) - } - - Signal::SetMaterialTexture(entity, target_material, uri, wrap_mode) => { - let path = resolve_editor_path(uri); - - let bytes = match std::fs::read(&path) { - Ok(bytes) => bytes, - Err(err) => { - warn!("Failed to read texture '{}': {}", path.display(), err); - self.signal = Signal::None; - return Ok(()); - } - }; - - let diffuse = Texture::from_bytes_verbose_mipmapped( - graphics.clone(), - &bytes, - None, - None, - Some(Texture::sampler_from_wrap(wrap_mode.clone())), - Some(target_material), - ); - let flat_normal = (*dropbear_engine::asset::ASSET_REGISTRY - .solid_texture_rgba8(graphics.clone(), [128, 128, 255, 255])) - .clone(); - - if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) { - let model = renderer.make_model_mut(); - if let Some(material) = model - .materials - .iter_mut() - .find(|mat| mat.name == *target_material) - { - material.diffuse_texture = diffuse; - material.normal_texture = flat_normal; - material.bind_group = Material::create_bind_group( - graphics.as_ref(), - &material.diffuse_texture, - &material.normal_texture, - &material.tint_buffer, - &material.name, - ); - material.texture_tag = Some(uri.clone()); - material.wrap_mode = *wrap_mode; - } else { - warn!("Material '{}' not found on renderer", target_material); - } - - renderer.sync_asset_registry(); - } - - self.signal = Signal::None; - Ok(()) - } - - Signal::SetMaterialWrapMode(entity, target_material, wrap_mode) => { - if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) { - let model = renderer.make_model_mut(); - if let Some(material) = model - .materials - .iter_mut() - .find(|mat| mat.name == *target_material) - { - material.wrap_mode = *wrap_mode; - - if let Some(uri) = material.texture_tag.clone() { - let path = resolve_editor_path(&uri); - - if let Ok(bytes) = std::fs::read(&path) { - let diffuse = Texture::from_bytes_verbose_mipmapped( - graphics.clone(), - &bytes, - None, - None, - Some(Texture::sampler_from_wrap(wrap_mode.clone())), - Some(target_material), - ); - material.diffuse_texture = diffuse; - material.bind_group = Material::create_bind_group( - graphics.as_ref(), - &material.diffuse_texture, - &material.normal_texture, - &material.tint_buffer, - &material.name, - ); - } else { - warn!( - "Failed to read texture '{}' to apply wrap mode", - path.display() - ); - } - } - } else { - warn!("Material '{}' not found on renderer", target_material); - } - - renderer.sync_asset_registry(); - } - - self.signal = Signal::None; - Ok(()) - } - - Signal::SetMaterialUvTiling(entity, target_material, uv_tiling) => { - if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) { - let model = renderer.make_model_mut(); - if let Some(material) = model - .materials - .iter_mut() - .find(|mat| mat.name == *target_material) - { - material.set_uv_tiling(graphics.as_ref(), *uv_tiling); - } else { - warn!("Material '{}' not found on renderer", target_material); - } - - renderer.sync_asset_registry(); - } - - self.signal = Signal::None; - Ok(()) - } - - Signal::ClearMaterialTexture(entity, target_material) => { - let diffuse = - (*dropbear_engine::asset::ASSET_REGISTRY.grey_texture(graphics.clone())) - .clone(); - let flat_normal = (*dropbear_engine::asset::ASSET_REGISTRY - .solid_texture_rgba8(graphics.clone(), [128, 128, 255, 255])) - .clone(); - - if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) { - let model = renderer.make_model_mut(); - if let Some(material) = model - .materials - .iter_mut() - .find(|mat| mat.name == *target_material) - { - material.diffuse_texture = diffuse; - material.normal_texture = flat_normal; - material.bind_group = Material::create_bind_group( - graphics.as_ref(), - &material.diffuse_texture, - &material.normal_texture, - &material.tint_buffer, - &material.name, - ); - material.texture_tag = None; - } else { - warn!("Material '{}' not found on renderer", target_material); - } - - renderer.sync_asset_registry(); - } - - self.signal = Signal::None; - Ok(()) - } - - Signal::SetMaterialTint(entity, target_material, tint) => { - if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) { - let model = renderer.make_model_mut(); - if let Some(material) = model - .materials - .iter_mut() - .find(|mat| mat.name == *target_material) - { - material.set_tint(graphics.as_ref(), *tint); - } else { - warn!("Material '{}' not found on renderer", target_material); - } - } - - self.signal = Signal::None; - Ok(()) - } Signal::RequestNewWindow(window_data) => { use dropbear_engine::scene::SceneCommand; self.scene_command = SceneCommand::RequestWindow(window_data.clone()); @@ -2,12 +2,13 @@ use crate::editor::Editor; use dropbear_engine::entity::{EntityTransform, MeshRenderer}; use dropbear_engine::future::FutureQueue; use dropbear_engine::graphics::{SharedGraphicsContext}; -use dropbear_engine::model::LoadedModel; -use dropbear_traits::{ComponentInitContext, ComponentInsert, ComponentResources}; +use dropbear_engine::model::{Model}; pub(crate) use eucalyptus_core::spawn::{PendingSpawnController, PENDING_SPAWNS}; use eucalyptus_core::{fatal, success}; use hecs::Entity; use std::sync::Arc; +use dropbear_engine::asset::Handle; +use dropbear_engine::component::{ComponentInitContext, ComponentInsert, ComponentResources}; impl PendingSpawnController for Editor { fn check_up( @@ -126,11 +127,11 @@ impl PendingSpawnController for Editor { let mut completed_swaps = Vec::new(); for (index, (entity, handle)) in self.pending_model_swaps.iter().enumerate() { if let Some(result) = queue.exchange_owned(handle) { - match result.downcast::<anyhow::Result<LoadedModel>>() { + match result.downcast::<anyhow::Result<Handle<Model>>>() { Ok(r) => match Arc::try_unwrap(r) { Ok(Ok(loaded_model)) => { if let Ok(mut renderer) = self.world.get::<&mut MeshRenderer>(*entity) { - renderer.set_handle(loaded_model); + renderer.set_model(loaded_model) } else { let renderer = MeshRenderer::from_handle(loaded_model); let _ = self.world.insert_one(*entity, renderer); @@ -32,6 +32,7 @@ use eucalyptus_core::rapier3d::geometry::SharedShape; use eucalyptus_core::states::{Label, PROJECT}; use eucalyptus_core::states::SCENES; use eucalyptus_core::scene::loading::{IsSceneLoaded, SceneLoadResult, SCENE_LOADER}; +use eucalyptus_core::traits::ComponentResources; use crate::PlayMode; use eucalyptus_core::physics::collider::shader::create_wireframe_geometry; use kino_ui::widgets::{Anchor, Border, Fill}; @@ -334,6 +335,14 @@ impl Scene for PlayMode { } { + let mut resources = ComponentResources::new(); + resources.insert(graphics.clone()); + let resources = Arc::new(resources); + self.component_registry + .update_components(self.world.as_mut(), dt, &resources); + } + + { let mut query = self.world.query::<(&mut MeshRenderer, &Transform)>(); for (renderer, transform) in query.iter() { renderer.update(transform); @@ -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,6 +146,59 @@ 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 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 void* WorldPtr; + +typedef void* AssetRegistryPtr; + typedef struct f64Array { double* values; size_t length; @@ -210,6 +230,27 @@ typedef struct NAnimationArray { size_t capacity; } NAnimationArray; +typedef struct NCollider { + IndexNative index; + uint64_t entity_id; + uint32_t id; +} NCollider; + +typedef struct NColliderArray { + NCollider* values; + size_t length; + size_t capacity; +} NColliderArray; + +typedef void* CommandBufferPtr; + +typedef void* SceneLoaderPtr; + +typedef struct NVector2 { + double x; + double y; +} NVector2; + typedef struct u64Array { uint64_t* values; size_t length; @@ -220,8 +261,6 @@ typedef struct ConnectedGamepadIds { u64Array ids; } ConnectedGamepadIds; -typedef void* InputStatePtr; - typedef struct NVector4 { double x; double y; @@ -229,10 +268,41 @@ typedef struct NVector4 { double w; } NVector4; -typedef struct NVector2 { - double x; - double y; -} NVector2; +typedef struct i32Array { + int32_t* values; + size_t length; + size_t capacity; +} i32Array; + +typedef struct NModelVertex { + NVector3 position; + NVector3 normal; + NVector4 tangent; + NVector2 tex_coords0; + NVector2 tex_coords1; + NVector4 colour0; + i32Array joints0; + NVector4 weights0; +} NModelVertex; + +typedef struct NModelVertexArray { + NModelVertex* values; + size_t length; + size_t capacity; +} NModelVertexArray; + +typedef struct NMesh { + const char* name; + int32_t num_elements; + int32_t material_index; + NModelVertexArray vertices; +} NMesh; + +typedef struct NMeshArray { + NMesh* values; + size_t length; + size_t capacity; +} NMeshArray; typedef struct NMaterial { const char* name; @@ -258,48 +328,13 @@ typedef struct NMaterialArray { size_t capacity; } NMaterialArray; -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* GraphicsContextPtr; - -typedef struct IndexNative { - uint32_t index; - uint32_t generation; -} IndexNative; - -typedef struct IndexNativeArray { - IndexNative* values; - size_t length; - size_t capacity; -} IndexNativeArray; +typedef void* PhysicsStatePtr; -typedef struct CharacterCollisionArray { - uint64_t entity_id; - IndexNativeArray collisions; -} CharacterCollisionArray; +typedef struct NTransform { + NVector3 position; + NQuaternion rotation; + NVector3 scale; +} NTransform; typedef struct NNodeTransform { NVector3 translation; @@ -320,47 +355,6 @@ typedef struct NNodeArray { size_t capacity; } NNodeArray; -typedef struct NCollider { - IndexNative index; - uint64_t entity_id; - uint32_t id; -} NCollider; - -typedef struct AxisLock { - bool x; - bool y; - bool z; -} AxisLock; - -typedef void* PhysicsStatePtr; - -typedef void* SceneLoaderPtr; - -typedef struct NTransform { - NVector3 position; - NQuaternion rotation; - NVector3 scale; -} NTransform; - -typedef struct NAttenuation { - float constant; - float linear; - float quadratic; -} NAttenuation; - -typedef struct NColliderArray { - NCollider* values; - size_t length; - size_t capacity; -} NColliderArray; - -typedef struct NColour { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; -} NColour; - typedef struct NShapeCastHit { NCollider collider; double distance; @@ -371,63 +365,69 @@ typedef struct NShapeCastHit { NShapeCastStatus status; } NShapeCastHit; -typedef void* WorldPtr; +typedef struct NAttenuation { + float constant; + float linear; + float quadratic; +} NAttenuation; -typedef void* CommandBufferPtr; +typedef struct Progress { + size_t current; + size_t total; + const char* message; +} Progress; -typedef struct NModelVertex { - NVector3 position; - NVector3 normal; - NVector4 tangent; - NVector2 tex_coords0; - NVector2 tex_coords1; - NVector4 colour0; - i32Array joints0; - NVector4 weights0; -} NModelVertex; +typedef struct NRange { + float start; + float end; +} NRange; -typedef struct NModelVertexArray { - NModelVertex* values; +typedef struct f64ArrayArray { + double* values; size_t length; size_t capacity; -} NModelVertexArray; +} f64ArrayArray; -typedef struct NMesh { +typedef struct NSkin { const char* name; - int32_t num_elements; - int32_t material_index; - NModelVertexArray vertices; -} NMesh; + i32Array joints; + f64ArrayArray inverse_bind_matrices; + const int32_t* skeleton_root; +} NSkin; -typedef struct NMeshArray { - NMesh* values; +typedef struct NSkinArray { + NSkin* values; size_t length; size_t capacity; -} NMeshArray; +} NSkinArray; typedef struct RayHit { NCollider collider; double distance; } RayHit; -typedef struct NRange { - float start; - float end; -} NRange; - -typedef struct Progress { - size_t current; - size_t total; - const char* message; -} Progress; - -typedef void* AssetRegistryPtr; - 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; + 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); int32_t dropbear_gamepad_get_right_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,16 @@ 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); @@ -516,6 +526,8 @@ 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); @@ -555,6 +567,9 @@ 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); @@ -583,20 +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); #endif /* DROPBEAR_H */