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-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"]

View File

@ -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<RwLock<render::RenderState>>,
file_receiver: crossbeam_channel::Receiver<ui::FileEvent>,
objects: Vec<ui::ObjectWidget>,
}
@ -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");

View File

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

View File

@ -340,19 +340,27 @@ impl RenderState {
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?
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 {

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,
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 {