diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 8161f45..1e9af72 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -10,11 +10,20 @@ cyborg = { path = "../", features = ["legion"] } egui = "0.17.0" egui-winit = "0.17.0" egui_wgpu_backend = "0.17.0" -glam = "0.20" +glam = { version = "0.20", features = ["serde"] } gltf = { version = "1.0", features = ["utils"] } legion = "^0.4" +parking_lot = "^0.11" pollster = "0.2" puffin = "^0.13" puffin_egui = "0.14.0" rfd = "^0.8" stl = "0.2.1" + +[dependencies.mlua] +version = "^0.8" +features = ["vendored", "luajit52", "serialize"] + +[dependencies.serde] +version = "1" +features = ["derive"] diff --git a/editor/src/main.rs b/editor/src/main.rs index 7077c08..d960b8e 100644 --- a/editor/src/main.rs +++ b/editor/src/main.rs @@ -8,11 +8,13 @@ use egui_winit::winit::{ window::WindowBuilder, }; use legion::EntityStore; +use parking_lot::RwLock; use std::sync::Arc; mod import; mod model; mod render; +mod script; mod ui; struct Application { @@ -27,7 +29,7 @@ struct Application { egui_rp: EguiRenderPass, ui: ui::UserInterface, viewport: ui::ViewportWidget, - render_state: render::RenderState, + render_state: Arc>, file_receiver: crossbeam_channel::Receiver, objects: Vec, } @@ -94,7 +96,7 @@ impl Application { egui_rp, ui: ui::UserInterface::new(file_sender), viewport: ui::ViewportWidget::new(viewport_texture), - render_state, + render_state: Arc::new(RwLock::new(render_state)), file_receiver, objects: vec![], } @@ -113,10 +115,15 @@ impl Application { ui::ImportKind::Gltf => import::load_gltf(path), }; - let loaded = self.render_state.load_model(&model); - let widgets = self.render_state.spawn_model(&loaded); + let mut render_state = self.render_state.write(); + let loaded = render_state.load_model(&model); + let transform = glam::Mat4::IDENTITY; + let widgets = render_state.spawn_model(&loaded, &transform); self.objects.extend(widgets.into_iter()); }, + Ok(ui::FileEvent::LoadScript(path)) => { + script::Script::new(&path, self.render_state.to_owned()); + } Err(crossbeam_channel::TryRecvError::Empty) => break, Err(crossbeam_channel::TryRecvError::Disconnected) => { panic!("File event sender hung up!"); @@ -124,11 +131,12 @@ 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() { - self.render_state + render_state .world .entry_mut(*entity) .unwrap() @@ -166,9 +174,9 @@ impl Application { { puffin::profile_scope!("Main render"); - self.render_state - .update_viewport(&mut self.egui_rp, &mut self.viewport); - self.render_state.render(); + let mut render_state = self.render_state.write(); + render_state.update_viewport(&mut self.egui_rp, &mut self.viewport); + render_state.render(); } puffin::profile_scope!("Render egui"); diff --git a/editor/src/model.rs b/editor/src/model.rs index ddfc357..1c2992b 100644 --- a/editor/src/model.rs +++ b/editor/src/model.rs @@ -1,10 +1,12 @@ use cyborg::storage::mesh::MeshHandle; +#[derive(Clone, Debug)] pub struct Model { pub name: Option, pub objects: Vec, } +#[derive(Clone, Debug)] pub struct Object { pub name: Option, pub transform: Transform, @@ -12,20 +14,24 @@ pub struct Object { pub children: Vec, } +#[derive(Clone, Debug)] pub struct Mesh { pub vertices: Vec, pub indices: Vec, } +#[derive(Clone, Debug)] pub struct BasicVertex { pub position: glam::Vec3, } +#[derive(Clone, Debug)] pub struct LoadedModel { pub name: Option, pub objects: Vec, } +#[derive(Clone, Debug)] pub struct LoadedObject { pub name: Option, pub transform: Transform, diff --git a/editor/src/render.rs b/editor/src/render.rs index 3111b7a..96c0823 100644 --- a/editor/src/render.rs +++ b/editor/src/render.rs @@ -340,19 +340,27 @@ impl RenderState { mesh_pass.get_mesh_pool().load(mesh).unwrap() } - pub fn spawn_model(&mut self, model: &model::LoadedModel) -> Vec { + 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)); + objects.push(self.spawn_object(object, transform)); } objects } - pub fn spawn_object(&mut self, object: &model::LoadedObject) -> ObjectWidget { + pub fn spawn_object( + &mut self, + object: &model::LoadedObject, + root_transform: &glam::Mat4, + ) -> ObjectWidget { let transform = cyborg::scene::Transform { - transform: object.transform.to_mat4(), + transform: *root_transform * object.transform.to_mat4(), }; let mut entities = Vec::new(); @@ -364,7 +372,7 @@ impl RenderState { let mut children = Vec::new(); for child in object.children.iter() { - children.push(self.spawn_object(child)); + children.push(self.spawn_object(child, root_transform)); } ObjectWidget { diff --git a/editor/src/script.rs b/editor/src/script.rs new file mode 100644 index 0000000..4c3881f --- /dev/null +++ b/editor/src/script.rs @@ -0,0 +1,72 @@ +use crate::model::LoadedModel; +use crate::render::RenderState; +use mlua::{Lua, LuaSerdeExt, Result, Value}; +use parking_lot::RwLock; +use serde::{Deserialize, Serialize}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +impl mlua::UserData for LoadedModel {} + +pub struct ScriptData { + root_path: PathBuf, + render_state: Arc>, +} + +#[derive(Copy, Clone, Debug, Deserialize, Serialize)] +pub struct Transform { + pub position: glam::Vec3, + pub orientation: glam::Quat, +} + +impl Transform { + pub fn to_mat4(&self) -> glam::Mat4 { + glam::Mat4::from_rotation_translation(self.orientation, self.position) + } +} + +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); + Ok(loaded) +} + +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); + Ok(()) +} + +pub struct Script { + lua: Lua, +} + +impl Script { + pub fn new(file: &Path, render_state: Arc>) -> Self { + let lua = Lua::new(); + + let data = ScriptData { + root_path: file.parent().unwrap().into(), + render_state, + }; + + lua.set_app_data(data); + + Self::load_cyborg_lib(&lua).unwrap(); + let src = std::fs::read_to_string(file).unwrap(); + lua.load(&src).eval::().unwrap(); + + Self { lua } + } + + pub fn load_cyborg_lib(lua: &Lua) -> Result<()> { + let lib = lua.create_table()?; + lib.set("load_gltf", lua.create_function(load_gltf)?)?; + lib.set("spawn_model", lua.create_function(spawn_model)?)?; + lua.globals().set("cyborg", lib)?; + Ok(()) + } +} diff --git a/editor/src/ui.rs b/editor/src/ui.rs index 56aaf3f..c8827e3 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -8,6 +8,7 @@ pub enum FileEvent { Save, SaveAs(PathBuf), Import(ImportKind, PathBuf), + LoadScript(PathBuf), } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -140,6 +141,11 @@ impl UserInterface { } }); + if ui.button("Load script...").clicked() { + ui.close_menu(); + self.on_load_script(); + } + if ui.button("Open...").clicked() { println!("Opening!"); ui.close_menu(); @@ -236,6 +242,17 @@ impl UserInterface { } }); } + + pub fn on_load_script(&self) { + let file_sender = self.file_sender.to_owned(); + std::thread::spawn(move || { + let dialog = rfd::FileDialog::new().add_filter("Lua script", &["lua"]); + if let Some(path) = dialog.pick_file() { + let event = FileEvent::LoadScript(path.into()); + file_sender.send(event).unwrap(); + } + }); + } } pub struct ViewportWidget {