Lua scripting

Decouple model loading from spawning
Make model structs cloneable
This commit is contained in:
mars 2022-06-30 00:22:24 -06:00
parent 5f367c3108
commit 6c2ed8b6b8
6 changed files with 134 additions and 14 deletions

View File

@ -10,11 +10,20 @@ cyborg = { path = "../", features = ["legion"] }
egui = "0.17.0" egui = "0.17.0"
egui-winit = "0.17.0" egui-winit = "0.17.0"
egui_wgpu_backend = "0.17.0" egui_wgpu_backend = "0.17.0"
glam = "0.20" glam = { version = "0.20", features = ["serde"] }
gltf = { version = "1.0", features = ["utils"] } gltf = { version = "1.0", features = ["utils"] }
legion = "^0.4" legion = "^0.4"
parking_lot = "^0.11"
pollster = "0.2" pollster = "0.2"
puffin = "^0.13" puffin = "^0.13"
puffin_egui = "0.14.0" puffin_egui = "0.14.0"
rfd = "^0.8" rfd = "^0.8"
stl = "0.2.1" stl = "0.2.1"
[dependencies.mlua]
version = "^0.8"
features = ["vendored", "luajit52", "serialize"]
[dependencies.serde]
version = "1"
features = ["derive"]

View File

@ -8,11 +8,13 @@ use egui_winit::winit::{
window::WindowBuilder, window::WindowBuilder,
}; };
use legion::EntityStore; use legion::EntityStore;
use parking_lot::RwLock;
use std::sync::Arc; use std::sync::Arc;
mod import; mod import;
mod model; mod model;
mod render; mod render;
mod script;
mod ui; mod ui;
struct Application { struct Application {
@ -27,7 +29,7 @@ struct Application {
egui_rp: EguiRenderPass, egui_rp: EguiRenderPass,
ui: ui::UserInterface, ui: ui::UserInterface,
viewport: ui::ViewportWidget, viewport: ui::ViewportWidget,
render_state: render::RenderState, render_state: Arc<RwLock<render::RenderState>>,
file_receiver: crossbeam_channel::Receiver<ui::FileEvent>, file_receiver: crossbeam_channel::Receiver<ui::FileEvent>,
objects: Vec<ui::ObjectWidget>, objects: Vec<ui::ObjectWidget>,
} }
@ -94,7 +96,7 @@ impl Application {
egui_rp, egui_rp,
ui: ui::UserInterface::new(file_sender), ui: ui::UserInterface::new(file_sender),
viewport: ui::ViewportWidget::new(viewport_texture), viewport: ui::ViewportWidget::new(viewport_texture),
render_state, render_state: Arc::new(RwLock::new(render_state)),
file_receiver, file_receiver,
objects: vec![], objects: vec![],
} }
@ -113,10 +115,15 @@ impl Application {
ui::ImportKind::Gltf => import::load_gltf(path), ui::ImportKind::Gltf => import::load_gltf(path),
}; };
let loaded = self.render_state.load_model(&model); let mut render_state = self.render_state.write();
let widgets = self.render_state.spawn_model(&loaded); 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()); 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::Empty) => break,
Err(crossbeam_channel::TryRecvError::Disconnected) => { Err(crossbeam_channel::TryRecvError::Disconnected) => {
panic!("File event sender hung up!"); 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() { for object in self.objects.iter_mut() {
object.flush_dirty(|object| { object.flush_dirty(|object| {
let transform = object.transform.to_mat4(); let transform = object.transform.to_mat4();
for entity in object.entities.iter() { for entity in object.entities.iter() {
self.render_state render_state
.world .world
.entry_mut(*entity) .entry_mut(*entity)
.unwrap() .unwrap()
@ -166,9 +174,9 @@ impl Application {
{ {
puffin::profile_scope!("Main render"); puffin::profile_scope!("Main render");
self.render_state let mut render_state = self.render_state.write();
.update_viewport(&mut self.egui_rp, &mut self.viewport); render_state.update_viewport(&mut self.egui_rp, &mut self.viewport);
self.render_state.render(); render_state.render();
} }
puffin::profile_scope!("Render egui"); puffin::profile_scope!("Render egui");

View File

@ -1,10 +1,12 @@
use cyborg::storage::mesh::MeshHandle; use cyborg::storage::mesh::MeshHandle;
#[derive(Clone, Debug)]
pub struct Model { pub struct Model {
pub name: Option<String>, pub name: Option<String>,
pub objects: Vec<Object>, pub objects: Vec<Object>,
} }
#[derive(Clone, Debug)]
pub struct Object { pub struct Object {
pub name: Option<String>, pub name: Option<String>,
pub transform: Transform, pub transform: Transform,
@ -12,20 +14,24 @@ pub struct Object {
pub children: Vec<Object>, pub children: Vec<Object>,
} }
#[derive(Clone, Debug)]
pub struct Mesh { pub struct Mesh {
pub vertices: Vec<BasicVertex>, pub vertices: Vec<BasicVertex>,
pub indices: Vec<u32>, pub indices: Vec<u32>,
} }
#[derive(Clone, Debug)]
pub struct BasicVertex { pub struct BasicVertex {
pub position: glam::Vec3, pub position: glam::Vec3,
} }
#[derive(Clone, Debug)]
pub struct LoadedModel { pub struct LoadedModel {
pub name: Option<String>, pub name: Option<String>,
pub objects: Vec<LoadedObject>, pub objects: Vec<LoadedObject>,
} }
#[derive(Clone, Debug)]
pub struct LoadedObject { pub struct LoadedObject {
pub name: Option<String>, pub name: Option<String>,
pub transform: Transform, pub transform: Transform,

View File

@ -340,19 +340,27 @@ impl RenderState {
mesh_pass.get_mesh_pool().load(mesh).unwrap() mesh_pass.get_mesh_pool().load(mesh).unwrap()
} }
pub fn spawn_model(&mut self, model: &model::LoadedModel) -> Vec<ObjectWidget> { pub fn spawn_model(
&mut self,
model: &model::LoadedModel,
transform: &glam::Mat4,
) -> Vec<ObjectWidget> {
// TODO use model name? // TODO use model name?
let mut objects = Vec::new(); let mut objects = Vec::new();
for object in model.objects.iter() { for object in model.objects.iter() {
objects.push(self.spawn_object(object)); objects.push(self.spawn_object(object, transform));
} }
objects 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 { let transform = cyborg::scene::Transform {
transform: object.transform.to_mat4(), transform: *root_transform * object.transform.to_mat4(),
}; };
let mut entities = Vec::new(); let mut entities = Vec::new();
@ -364,7 +372,7 @@ impl RenderState {
let mut children = Vec::new(); let mut children = Vec::new();
for child in object.children.iter() { for child in object.children.iter() {
children.push(self.spawn_object(child)); children.push(self.spawn_object(child, root_transform));
} }
ObjectWidget { ObjectWidget {

72
editor/src/script.rs Normal file
View File

@ -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<RwLock<RenderState>>,
}
#[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<LoadedModel> {
let data = lua.app_data_ref::<ScriptData>().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::<ScriptData>().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<RwLock<RenderState>>) -> 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::<mlua::MultiValue>().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(())
}
}

View File

@ -8,6 +8,7 @@ pub enum FileEvent {
Save, Save,
SaveAs(PathBuf), SaveAs(PathBuf),
Import(ImportKind, PathBuf), Import(ImportKind, PathBuf),
LoadScript(PathBuf),
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[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() { if ui.button("Open...").clicked() {
println!("Opening!"); println!("Opening!");
ui.close_menu(); 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 { pub struct ViewportWidget {