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-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"]
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
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 {
|
||||
|
|
Loading…
Reference in New Issue