diff --git a/editor/src/import.rs b/editor/src/import.rs new file mode 100644 index 0000000..93b0a2a --- /dev/null +++ b/editor/src/import.rs @@ -0,0 +1,38 @@ +use crate::model; + +pub fn load_stl(path: std::path::PathBuf) -> model::Model { + let name = path.file_name().map(|v| v.to_string_lossy().to_string()); + + let mut file = std::fs::File::open(path).unwrap(); + let stl = stl::read_stl(&mut file).unwrap(); + + let mut vertices = Vec::new(); + let mut indices = Vec::new(); + + for tri in stl.triangles.iter() { + indices.push(vertices.len() as u32); + vertices.push(model::BasicVertex { + position: tri.v1.into(), + }); + indices.push(vertices.len() as u32); + vertices.push(model::BasicVertex { + position: tri.v2.into(), + }); + indices.push(vertices.len() as u32); + vertices.push(model::BasicVertex { + position: tri.v3.into(), + }); + } + + model::Model { + name: name.clone(), + objects: vec![model::Object { + name, + mesh: model::Mesh { + transform: Default::default(), + data: model::MeshData { vertices, indices }, + }, + children: vec![], + }], + } +} diff --git a/editor/src/main.rs b/editor/src/main.rs index 498e1b6..0efdc3b 100644 --- a/editor/src/main.rs +++ b/editor/src/main.rs @@ -10,6 +10,8 @@ use egui_winit::winit::{ use legion::EntityStore; use std::sync::Arc; +mod import; +mod model; mod render; mod ui; @@ -107,7 +109,9 @@ impl Application { Ok(ui::FileEvent::SaveAs(path)) => println!("Saving as: {:?}", path), Ok(ui::FileEvent::Import(kind, path)) => match kind { ui::ImportKind::Stl => { - self.load_stl(path); + let model = import::load_stl(path); + let widgets = self.render_state.load_model(&model); + self.objects.extend(widgets.into_iter()); } }, Err(crossbeam_channel::TryRecvError::Empty) => break, @@ -119,16 +123,7 @@ impl Application { for object in self.objects.iter_mut() { object.flush_dirty(|object| { - let translation = glam::Vec3::from_slice(&object.position); - let rotation = glam::Quat::from_euler( - glam::EulerRot::XYZ, - object.rotation[0], - object.rotation[1], - object.rotation[2], - ); - let scale = glam::Vec3::splat(object.scale); - let transform = - glam::Mat4::from_scale_rotation_translation(scale, rotation, translation); + let transform = object.transform.to_mat4(); self.render_state .world .entry_mut(object.entity) @@ -140,89 +135,6 @@ impl Application { } } - pub fn load_stl(&mut self, path: std::path::PathBuf) { - let root_name = path - .file_name() - .map(|v| v.to_string_lossy().to_string()) - .unwrap_or("".to_string()); - - use cyborg::pass::{ - mesh::{Index, MeshPass, Vertex}, - RenderPassBox, - }; - use cyborg::storage::mesh::{AttrBuffer, MeshBuffer}; - - eprintln!("loading {:?}", path); - - let mut file = std::fs::File::open(path).unwrap(); - let stl = stl::read_stl(&mut file).unwrap(); - - let mut vertices = Vec::new(); - let mut indices = Vec::new(); - - for tri in stl.triangles.iter() { - let tan_frame = 0; // TODO make from normal - indices.push(vertices.len() as Index); - vertices.push(Vertex { - position: tri.v1, - tan_frame, - }); - indices.push(vertices.len() as Index); - vertices.push(Vertex { - position: tri.v2, - tan_frame, - }); - indices.push(vertices.len() as Index); - vertices.push(Vertex { - position: tri.v3, - tan_frame, - }); - } - - let mesh_pass = self - .render_state - .resources - .get::>() - .unwrap(); - let attributes = mesh_pass.get_attributes(); - - let vertices = AttrBuffer { - id: attributes.vertex, - count: vertices.len(), - data: bytemuck::cast_slice(&vertices).to_vec(), - }; - - let indices = AttrBuffer { - id: attributes.index, - count: indices.len(), - data: bytemuck::cast_slice(&indices).to_vec(), - }; - - let mut mesh = MeshBuffer::default(); - mesh.attributes.push(vertices); - mesh.attributes.push(indices); - - let entity = self.render_state.world.push(( - cyborg::scene::Mesh { - mesh: mesh_pass.get_mesh_pool().load(mesh).unwrap(), - }, - cyborg::scene::Transform { - transform: Default::default(), - }, - )); - - let object = ui::ObjectWidget { - name: root_name, - position: [0.0; 3], - rotation: [0.0; 3], - scale: 1.0, - entity, - dirty: false, - }; - - self.objects.push(object); - } - pub fn on_resize(&mut self, new_size: PhysicalSize) { if new_size.width > 0 && new_size.height > 0 { self.size = new_size; diff --git a/editor/src/model.rs b/editor/src/model.rs new file mode 100644 index 0000000..46ee0e8 --- /dev/null +++ b/editor/src/model.rs @@ -0,0 +1,55 @@ +pub struct Model { + pub name: Option, + pub objects: Vec, +} + +pub struct Object { + pub name: Option, + pub mesh: Mesh, + pub children: Vec, +} + +pub struct Mesh { + pub transform: Transform, + pub data: MeshData, +} + +pub struct MeshData { + pub vertices: Vec, + pub indices: Vec, +} + +pub struct BasicVertex { + pub position: glam::Vec3, +} + +#[derive(Copy, Clone, Debug)] +pub struct Transform { + pub position: glam::Vec3, + pub rotation: glam::Vec3, // TODO support glam::Quat too + pub scale: f32, +} + +impl Transform { + pub fn to_mat4(&self) -> glam::Mat4 { + let translation = self.position; + let rotation = glam::Quat::from_euler( + glam::EulerRot::XYZ, + self.rotation[0], + self.rotation[1], + self.rotation[2], + ); + let scale = glam::Vec3::splat(self.scale); + glam::Mat4::from_scale_rotation_translation(scale, rotation, translation) + } +} + +impl Default for Transform { + fn default() -> Self { + Self { + position: glam::Vec3::ZERO, + rotation: glam::Vec3::ZERO, + scale: 1.0, + } + } +} diff --git a/editor/src/render.rs b/editor/src/render.rs index 070dc23..bd61531 100644 --- a/editor/src/render.rs +++ b/editor/src/render.rs @@ -1,6 +1,8 @@ -use crate::ui::ViewportWidget; +use crate::model; +use crate::ui::{ObjectWidget, ViewportWidget}; use crate::wgpu; use cyborg::camera::Camera; +use cyborg::storage::mesh::MeshHandle; use cyborg::viewport::{Viewport, ViewportInfo, ViewportViews}; use std::sync::Arc; @@ -258,4 +260,77 @@ impl RenderState { self.render_schedule .execute(&mut self.world, &mut self.resources); } + + pub fn load_model(&mut self, model: &model::Model) -> Vec { + // TODO use model name? + let mut objects = Vec::new(); + for object in model.objects.iter() { + objects.push(self.load_object(object)); + } + + objects + } + + pub fn load_object(&mut self, object: &model::Object) -> ObjectWidget { + let mesh = self.load_mesh_data(&object.mesh.data); + + let entity = self.world.push(( + cyborg::scene::Mesh { mesh }, + cyborg::scene::Transform { + transform: object.mesh.transform.to_mat4(), + }, + )); + + let mut children = Vec::new(); + for child in object.children.iter() { + children.push(self.load_object(child)); + } + + ObjectWidget { + name: object.name.clone(), + transform: object.mesh.transform.clone(), + entity, + children, + dirty: false, + children_dirty: false, + } + } + + pub fn load_mesh_data(&mut self, data: &model::MeshData) -> MeshHandle { + use cyborg::pass::{ + mesh::{MeshPass, Vertex}, + RenderPassBox, + }; + use cyborg::storage::mesh::{AttrBuffer, MeshBuffer}; + + let mesh_pass = self.resources.get::>().unwrap(); + let attributes = mesh_pass.get_attributes(); + + let vertices: Vec<_> = data + .vertices + .iter() + .map(|v| Vertex { + position: v.position.to_array(), + tan_frame: 0, // TODO encode tangents + }) + .collect(); + + let vertices = AttrBuffer { + id: attributes.vertex, + count: vertices.len(), + data: bytemuck::cast_slice(&vertices).to_vec(), + }; + + let indices = AttrBuffer { + id: attributes.index, + count: data.indices.len(), + data: bytemuck::cast_slice(&data.indices).to_vec(), + }; + + let mut mesh = MeshBuffer::default(); + mesh.attributes.push(vertices); + mesh.attributes.push(indices); + + mesh_pass.get_mesh_pool().load(mesh).unwrap() + } } diff --git a/editor/src/ui.rs b/editor/src/ui.rs index 678c68e..fe01817 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -202,8 +202,8 @@ impl UserInterface { egui::SidePanel::left("objects_panel") .resizable(true) .show(ctx, |ui| { - for object in objects.iter_mut() { - object.ui(ui); + for (index, object) in objects.iter_mut().enumerate() { + object.ui(index, ui); } }); @@ -299,20 +299,28 @@ impl egui::Widget for &mut ViewportWidget { } pub struct ObjectWidget { - pub name: String, - pub position: [f32; 3], - pub rotation: [f32; 3], - pub scale: f32, + pub name: Option, + pub transform: crate::model::Transform, pub entity: legion::Entity, + pub children: Vec, pub dirty: bool, + pub children_dirty: bool, } impl ObjectWidget { - pub fn ui(&mut self, ui: &mut egui::Ui) { - egui::CollapsingHeader::new(&self.name) + 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 { + self.children_dirty = true; + } + } }); } @@ -336,26 +344,47 @@ impl ObjectWidget { } pub fn ui_position(&mut self, ui: &mut egui::Ui) { - let speed = 0.1 * self.scale; - for axis in self.position.iter_mut() { - let drag = egui::DragValue::new(axis).speed(speed); - if ui.add(drag).changed() { - self.dirty = true; - } + 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) { - for axis in self.rotation.iter_mut() { - if ui.drag_angle(axis).changed() { - self.dirty = true; - } + 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.scale * 0.01; - let drag = egui::DragValue::new(&mut self.scale) + 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() { @@ -364,9 +393,20 @@ impl ObjectWidget { } pub fn flush_dirty(&mut self, mut f: impl FnMut(&mut Self)) { - if self.dirty { - f(self); - self.dirty = false; + let mut stack = vec![self]; + if 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); + } + } } } }