diff --git a/editor/src/main.rs b/editor/src/main.rs index 30ff556..22dc94a 100644 --- a/editor/src/main.rs +++ b/editor/src/main.rs @@ -7,7 +7,6 @@ use egui_winit::winit::{ event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; -use legion::EntityStore; use parking_lot::RwLock; use std::sync::Arc; @@ -16,6 +15,7 @@ mod model; mod render; mod script; mod ui; +mod world; struct Application { window: winit::window::Window, @@ -29,9 +29,9 @@ struct Application { egui_rp: EguiRenderPass, ui: ui::UserInterface, viewport: ui::ViewportWidget, - render_state: Arc>, + world: Arc>, + render_state: render::RenderState, file_receiver: crossbeam_channel::Receiver, - objects: Vec, } impl Application { @@ -73,9 +73,18 @@ impl Application { let egui_state = egui_winit::State::new(4096, &window); let egui_ctx = egui::Context::default(); let mut egui_rp = egui_wgpu_backend::RenderPass::new(&device, config.format, 1); - let render_state = - render::RenderState::new(device.clone(), queue.clone(), config.format, &mut egui_rp); - let viewport_texture = render_state + + let mut world = world::World::new(); + + let render_state = render::RenderState::new( + &mut world, + device.clone(), + queue.clone(), + config.format, + &mut egui_rp, + ); + + let viewport_texture = world .resources .get::() .unwrap() @@ -94,11 +103,11 @@ impl Application { egui_state, egui_ctx, egui_rp, + world: Arc::new(RwLock::new(world)), ui: ui::UserInterface::new(file_sender), viewport: ui::ViewportWidget::new(viewport_texture), - render_state: Arc::new(RwLock::new(render_state)), + render_state, file_receiver, - objects: vec![], } } @@ -115,14 +124,13 @@ impl Application { ui::ImportKind::Gltf => import::load_gltf(path), }; - let mut render_state = self.render_state.write(); - let loaded = render_state.load_model(&model); + let mut world = self.world.write(); + let loaded = world.load_model(&model); let transform = glam::Mat4::IDENTITY; - let widgets = render_state.spawn_model(&loaded, &transform); - self.objects.extend(widgets.into_iter()); - }, + world.spawn_model(&loaded, &transform); + } Ok(ui::FileEvent::LoadScript(path)) => { - script::Script::new(&path, self.render_state.to_owned()); + script::Script::new(&path, self.world.to_owned()); } Err(crossbeam_channel::TryRecvError::Empty) => break, Err(crossbeam_channel::TryRecvError::Disconnected) => { @@ -130,22 +138,9 @@ impl Application { } } } - - let mut render_state = self.render_state.write(); - for object in self.objects.iter_mut() { - object.flush_dirty(|object| { - let transform = object.transform.to_mat4(); - for entity in object.entities.iter() { - render_state - .world - .entry_mut(*entity) - .unwrap() - .get_component_mut::() - .unwrap() - .transform = transform; - } - }); - } + + let mut world = self.world.write(); + world.flush_dirty_objects(); } pub fn on_resize(&mut self, new_size: PhysicalSize) { @@ -168,15 +163,22 @@ impl Application { puffin::profile_scope!("Draw egui"); let raw_input = self.egui_state.take_egui_input(&self.window); self.egui_ctx.run(raw_input, |ctx| { - self.ui.run(ctx, &mut self.viewport, &mut self.objects) + let mut world = self.world.write(); + + let mut resources = ui::UiResources { + world: &mut world, + viewport: &mut self.viewport, + }; + + self.ui.run(ctx, &mut resources); }) }; { puffin::profile_scope!("Main render"); - let mut render_state = self.render_state.write(); - render_state.update_viewport(&mut self.egui_rp, &mut self.viewport); - render_state.render(); + let mut world = self.world.write(); + world.update_viewport(&mut self.egui_rp, &mut self.viewport); + world.execute(&mut self.render_state.render_schedule); } puffin::profile_scope!("Render egui"); diff --git a/editor/src/render.rs b/editor/src/render.rs index 90feda3..31d9183 100644 --- a/editor/src/render.rs +++ b/editor/src/render.rs @@ -1,6 +1,7 @@ use crate::model; -use crate::ui::{ObjectWidget, ViewportWidget}; +use crate::ui::ViewportWidget; use crate::wgpu; +use crate::world::World; use cyborg::camera::Camera; use cyborg::storage::mesh::MeshHandle; use cyborg::viewport::{Viewport, ViewportInfo, ViewportViews}; @@ -154,13 +155,12 @@ impl cyborg::legion::RenderCallbacks for ViewportStore { } pub struct RenderState { - pub world: legion::World, - pub resources: legion::Resources, pub render_schedule: legion::Schedule, } impl RenderState { pub fn new( + world: &mut World, device: Arc, queue: Arc, output_format: wgpu::TextureFormat, @@ -169,8 +169,8 @@ impl RenderState { use cyborg::scene::{DebugDrawList, DebugVertex}; use cyborg::shader::{ShaderStore, ShaderWatcher}; - let mut world = legion::World::default(); - let mut resources = legion::Resources::default(); + let resources = &mut world.resources; + let viewport_store = ViewportStore::new(device.clone(), queue.clone(), output_format, render_pass); @@ -200,7 +200,7 @@ impl RenderState { resources.insert(mesh_shaders); let mut render_schedule = legion::Schedule::builder(); - cyborg::legion::build_renderer(viewport_store, &mut resources, &mut render_schedule); + cyborg::legion::build_renderer(viewport_store, resources, &mut render_schedule); let render_schedule = render_schedule.build(); // make debug draw grid @@ -242,13 +242,11 @@ impl RenderState { }, )); - Self { - world, - resources, - render_schedule, - } + Self { render_schedule } } +} +impl World { pub fn update_viewport( &mut self, egui_rp: &mut egui_wgpu_backend::RenderPass, @@ -277,11 +275,6 @@ impl RenderState { } } - pub fn render(&mut self) { - self.render_schedule - .execute(&mut self.world, &mut self.resources); - } - pub fn load_model(&mut self, model: &model::Model) -> model::LoadedModel { model::LoadedModel { name: model.name.clone(), @@ -339,49 +332,4 @@ impl RenderState { mesh_pass.get_mesh_pool().load(mesh).unwrap() } - - pub fn spawn_model( - &mut self, - model: &model::LoadedModel, - transform: &glam::Mat4, - ) -> Vec { - // TODO use model name? - let mut objects = Vec::new(); - for object in model.objects.iter() { - objects.push(self.spawn_object(object, transform)); - } - - objects - } - - pub fn spawn_object( - &mut self, - object: &model::LoadedObject, - root_transform: &glam::Mat4, - ) -> ObjectWidget { - let transform = cyborg::scene::Transform { - transform: *root_transform * object.transform.to_mat4(), - }; - - let mut entities = Vec::new(); - for mesh in object.meshes.iter() { - let mesh = cyborg::scene::Mesh { mesh: *mesh }; - let entity = self.world.push((mesh, transform.clone())); - entities.push(entity); - } - - let mut children = Vec::new(); - for child in object.children.iter() { - children.push(self.spawn_object(child, root_transform)); - } - - ObjectWidget { - name: object.name.clone(), - transform: object.transform.clone(), - entities, - children, - dirty: false, - children_dirty: false, - } - } } diff --git a/editor/src/script.rs b/editor/src/script.rs index 6d1cc62..5c6ccd1 100644 --- a/editor/src/script.rs +++ b/editor/src/script.rs @@ -1,5 +1,5 @@ use crate::model::LoadedModel; -use crate::render::RenderState; +use crate::world::World; use mlua::{Lua, LuaSerdeExt, Result, Value}; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; @@ -10,7 +10,7 @@ impl mlua::UserData for LoadedModel {} pub struct ScriptData { root_path: PathBuf, - render_state: Arc>, + world: Arc>, } #[derive(Copy, Clone, Debug, Deserialize, Serialize)] @@ -31,7 +31,7 @@ impl Transform { fn load_gltf(lua: &Lua, filename: String) -> Result { let data = lua.app_data_ref::().unwrap(); let model = crate::import::load_gltf(data.root_path.join(filename).into()); - let loaded = data.render_state.write().load_model(&model); + let loaded = data.world.write().load_model(&model); Ok(loaded) } @@ -39,7 +39,7 @@ fn spawn_model(lua: &Lua, model: (LoadedModel, Value)) -> Result<()> { let data = lua.app_data_ref::().unwrap(); let transform: Transform = lua.from_value(model.1)?; let transform = transform.to_mat4(); - data.render_state.write().spawn_model(&model.0, &transform); + data.world.write().spawn_model(&model.0, &transform); Ok(()) } @@ -48,12 +48,12 @@ pub struct Script { } impl Script { - pub fn new(file: &Path, render_state: Arc>) -> Self { + pub fn new(file: &Path, world: Arc>) -> Self { let lua = Lua::new(); let data = ScriptData { root_path: file.parent().unwrap().into(), - render_state, + world, }; lua.set_app_data(data); diff --git a/editor/src/ui.rs b/editor/src/ui.rs index ec5baae..4600209 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -1,8 +1,11 @@ -use crate::winit; use crossbeam_channel::Sender; use cyborg::camera::Flycam; +use legion::Entity; use std::path::PathBuf; +use crate::winit; +use crate::world::{Object, World}; + #[derive(Clone, Debug)] pub enum FileEvent { Save, @@ -23,6 +26,11 @@ pub enum Workspace { NodeEditor, } +pub struct UiResources<'a> { + pub world: &'a mut World, + pub viewport: &'a mut ViewportWidget, +} + pub struct UserInterface { file_sender: Sender, developer_mode: bool, @@ -52,12 +60,7 @@ impl UserInterface { self.quit } - pub fn run( - &mut self, - ctx: &egui::Context, - viewport: &mut ViewportWidget, - objects: &mut [ObjectWidget], - ) { + pub fn run(&mut self, ctx: &egui::Context, resources: &mut UiResources) { egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::menu::bar(ui, |ui| self.ui_menu_bar(ui)); }); @@ -98,7 +101,7 @@ impl UserInterface { } match self.workspace { - Workspace::Scene => self.ui_scene(ctx, viewport, objects), + Workspace::Scene => self.ui_scene(ctx, resources), Workspace::NodeEditor => { egui::CentralPanel::default().show(ctx, |ui| self.ui_node_editor(ui)); } @@ -194,28 +197,65 @@ impl UserInterface { ui.selectable_value(&mut self.workspace, Workspace::NodeEditor, "Node Editor"); } - pub fn ui_scene( - &mut self, - ctx: &egui::Context, - viewport: &mut ViewportWidget, - objects: &mut [ObjectWidget], - ) { + pub fn ui_scene(&mut self, ctx: &egui::Context, resources: &mut UiResources) { egui::SidePanel::left("objects_panel") .resizable(true) - .show(ctx, |ui| { - egui::ScrollArea::vertical().show(ui, |ui| { - for (index, object) in objects.iter_mut().enumerate() { - object.ui(index, ui); - } - }); - }); + .show(ctx, |ui| self.ui_objects(ui, resources)); egui::CentralPanel::default().show(ctx, |ui| { - viewport.show(ui); + resources.viewport.show(ui); ui.heading("Viewport"); }); } + pub fn ui_objects(&mut self, ui: &mut egui::Ui, resources: &mut UiResources) { + egui::ScrollArea::vertical().show(ui, |ui| { + let roots = resources.world.root_objects.to_owned(); + for object in roots.into_iter() { + self.ui_object(ui, resources, object); + } + }); + } + + pub fn ui_object( + &mut self, + ui: &mut egui::Ui, + resources: &mut UiResources, + id: Entity, + ) -> bool { + let object: &mut Object = resources.world.get_component_mut(id); + let name = object + .name + .as_ref() + .unwrap_or(&"".into()) + .to_owned(); + + let children = object.children.to_owned(); + let mut any_dirty = false; + + egui::CollapsingHeader::new(name) + .id_source(format!("child_{:?}", id)) + .default_open(true) + .show(ui, |ui| { + let object: &mut Object = resources.world.get_component_mut(id); + object.ui(ui); + + for child in children.into_iter() { + if self.ui_object(ui, resources, child) { + any_dirty = true; + } + } + }); + + let object: &mut Object = resources.world.get_component_mut(id); + + if any_dirty { + object.children_dirty = true; + } + + object.dirty || any_dirty + } + pub fn ui_node_editor(&mut self, ui: &mut egui::Ui) { ui.label("Node editor goes here!"); } @@ -359,116 +399,3 @@ impl ViewportWidget { } } } - -pub struct ObjectWidget { - pub name: Option, - pub transform: crate::model::Transform, - pub entities: Vec, - pub children: Vec, - pub dirty: bool, - pub children_dirty: bool, -} - -impl ObjectWidget { - pub fn ui(&mut self, index: usize, ui: &mut egui::Ui) { - egui::CollapsingHeader::new(self.name.as_ref().unwrap_or(&"".into())) - .id_source(format!("child_{}", index)) - .default_open(true) - .show(ui, |ui| { - self.ui_self(ui); - - for (index, child) in self.children.iter_mut().enumerate() { - child.ui(index, ui); - if child.dirty || child.children_dirty { - self.children_dirty = true; - } - } - }); - } - - pub fn ui_self(&mut self, ui: &mut egui::Ui) { - egui::Grid::new("root_object") - .num_columns(4) - .striped(true) - .show(ui, |ui| { - ui.label("Position: "); - self.ui_position(ui); - ui.end_row(); - - ui.label("Rotation: "); - self.ui_rotation(ui); - ui.end_row(); - - ui.label("Scale: "); - self.ui_scale(ui); - ui.end_row(); - }); - } - - pub fn ui_position(&mut self, ui: &mut egui::Ui) { - let speed = 0.1 * self.transform.scale; - if Self::ui_vec3(ui, speed, &mut self.transform.position) { - self.dirty = true; - } - } - - pub fn ui_rotation(&mut self, ui: &mut egui::Ui) { - let axes = &mut self.transform.rotation; - - if ui.drag_angle(&mut axes.x).changed() { - self.dirty = true; - } - - if ui.drag_angle(&mut axes.y).changed() { - self.dirty = true; - } - - if ui.drag_angle(&mut axes.z).changed() { - self.dirty = true; - } - } - - pub fn ui_vec3(ui: &mut egui::Ui, speed: f32, vec3: &mut glam::Vec3) -> bool { - let x_drag = egui::DragValue::new(&mut vec3.x).speed(speed); - let dirty = ui.add(x_drag).changed(); - - let dirty = dirty || { - // For some reason, Rust complains if this isn't in a block - let y_drag = egui::DragValue::new(&mut vec3.y).speed(speed); - ui.add(y_drag).changed() - }; - - let z_drag = egui::DragValue::new(&mut vec3.z).speed(speed); - let dirty = dirty || ui.add(z_drag).changed(); - - dirty - } - - pub fn ui_scale(&mut self, ui: &mut egui::Ui) { - let scale_speed = self.transform.scale * 0.01; - let drag = egui::DragValue::new(&mut self.transform.scale) - .clamp_range(0.0..=f32::INFINITY) - .speed(scale_speed); - if ui.add(drag).changed() { - self.dirty = true; - } - } - - pub fn flush_dirty(&mut self, mut f: impl FnMut(&mut Self)) { - let mut stack = vec![self]; - while let Some(parent) = stack.pop() { - if parent.dirty { - parent.dirty = false; - f(parent); - } - - if parent.children_dirty { - parent.children_dirty = false; - - for child in parent.children.iter_mut() { - stack.push(child); - } - } - } - } -} diff --git a/editor/src/world.rs b/editor/src/world.rs new file mode 100644 index 0000000..ce4c61f --- /dev/null +++ b/editor/src/world.rs @@ -0,0 +1,203 @@ +use legion::Entity; + +use crate::model; + +use std::ops::{Deref, DerefMut}; + +pub struct World { + pub world: legion::World, + pub resources: legion::Resources, + pub root_objects: Vec, +} + +impl Deref for World { + type Target = legion::World; + + fn deref(&self) -> &legion::World { + &self.world + } +} + +impl DerefMut for World { + fn deref_mut(&mut self) -> &mut legion::World { + &mut self.world + } +} + +impl World { + pub fn new() -> Self { + Self { + world: Default::default(), + resources: Default::default(), + root_objects: vec![], + } + } + + pub fn execute(&mut self, schedule: &mut legion::Schedule) { + schedule.execute(&mut self.world, &mut self.resources); + } + + pub fn get_component_mut(&mut self, entity: Entity) -> &mut T { + self.entry(entity).unwrap().into_component_mut().unwrap() + } + + pub fn spawn_model(&mut self, model: &model::LoadedModel, transform: &glam::Mat4) -> Entity { + let mut children = Vec::new(); + for object in model.objects.iter() { + children.push(self.spawn_object(object, transform)); + } + + let root = self.world.push((Object { + name: model.name.to_owned(), + transform: model::Transform::default(), + entities: Default::default(), + children, + dirty: false, + children_dirty: false, + },)); + + self.root_objects.push(root); + root + } + + pub fn spawn_object( + &mut self, + object: &model::LoadedObject, + root_transform: &glam::Mat4, + ) -> Entity { + let transform = cyborg::scene::Transform { + transform: *root_transform * object.transform.to_mat4(), + }; + + let mut entities = Vec::new(); + for mesh in object.meshes.iter() { + let mesh = cyborg::scene::Mesh { mesh: *mesh }; + let entity = self.world.push((mesh, transform.clone())); + entities.push(entity); + } + + let mut children = Vec::new(); + for child in object.children.iter() { + children.push(self.spawn_object(child, root_transform)); + } + + self.world.push((Object { + name: object.name.clone(), + transform: object.transform.clone(), + entities, + children, + dirty: false, + children_dirty: false, + },)) + } + + pub fn flush_dirty_objects(&mut self) { + let mut stack = self.root_objects.to_owned(); + while let Some(parent) = stack.pop() { + let parent: &mut Object = self + .world + .entry(parent) + .unwrap() + .into_component_mut() + .unwrap(); + + if parent.children_dirty { + parent.children_dirty = false; + stack.extend_from_slice(&parent.children); + } + + if parent.dirty { + parent.dirty = false; + + let transform = parent.transform.to_mat4(); + for entity in parent.entities.to_owned().into_iter() { + self.world + .entry(entity) + .unwrap() + .get_component_mut::() + .unwrap() + .transform = transform; + } + } + } + } +} + +pub struct Object { + pub name: Option, + pub transform: model::Transform, + pub entities: Vec, + pub children: Vec, + pub dirty: bool, + pub children_dirty: bool, +} + +impl Object { + pub fn ui(&mut self, ui: &mut egui::Ui) { + egui::Grid::new("root_object") + .num_columns(4) + .striped(true) + .show(ui, |ui| { + ui.label("Position: "); + self.ui_position(ui); + ui.end_row(); + + ui.label("Rotation: "); + self.ui_rotation(ui); + ui.end_row(); + + ui.label("Scale: "); + self.ui_scale(ui); + ui.end_row(); + }); + } + + pub fn ui_position(&mut self, ui: &mut egui::Ui) { + let speed = 0.1 * self.transform.scale; + if Self::ui_vec3(ui, speed, &mut self.transform.position) { + self.dirty = true; + } + } + + pub fn ui_rotation(&mut self, ui: &mut egui::Ui) { + let axes = &mut self.transform.rotation; + + if ui.drag_angle(&mut axes.x).changed() { + self.dirty = true; + } + + if ui.drag_angle(&mut axes.y).changed() { + self.dirty = true; + } + + if ui.drag_angle(&mut axes.z).changed() { + self.dirty = true; + } + } + + pub fn ui_vec3(ui: &mut egui::Ui, speed: f32, vec3: &mut glam::Vec3) -> bool { + let x_drag = egui::DragValue::new(&mut vec3.x).speed(speed); + let dirty = ui.add(x_drag).changed(); + + let dirty = dirty || { + // For some reason, Rust complains if this isn't in a block + let y_drag = egui::DragValue::new(&mut vec3.y).speed(speed); + ui.add(y_drag).changed() + }; + + let z_drag = egui::DragValue::new(&mut vec3.z).speed(speed); + let dirty = dirty || ui.add(z_drag).changed(); + + dirty + } + + pub fn ui_scale(&mut self, ui: &mut egui::Ui) { + let scale_speed = self.transform.scale * 0.01; + let drag = egui::DragValue::new(&mut self.transform.scale) + .clamp_range(0.0..=f32::INFINITY) + .speed(scale_speed); + if ui.add(drag).changed() { + self.dirty = true; + } + } +}