Lua scripting
Decouple model loading from spawning Make model structs cloneable
This commit is contained in:
parent
5f367c3108
commit
6c2ed8b6b8
|
@ -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"]
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue