Compare commits
4 Commits
main
...
egui_gizmo
Author | SHA1 | Date |
---|---|---|
mars | e9700e65fe | |
mars | e238fa244d | |
mars | 1834b77522 | |
mars | 4bfd6977c3 |
|
@ -8,7 +8,8 @@ bytemuck = "^1.0"
|
||||||
crossbeam-channel = "^0.5"
|
crossbeam-channel = "^0.5"
|
||||||
cyborg = { path = "../", features = ["legion"] }
|
cyborg = { path = "../", features = ["legion"] }
|
||||||
egui = "0.18"
|
egui = "0.18"
|
||||||
egui-winit = "0.18.0"
|
egui-gizmo = "0.7"
|
||||||
|
egui-winit = "0.18"
|
||||||
egui_wgpu_backend = "0.18.0"
|
egui_wgpu_backend = "0.18.0"
|
||||||
glam = { version = "0.20", features = ["serde"] }
|
glam = { version = "0.20", features = ["serde"] }
|
||||||
gltf = { version = "1.0", features = ["utils"] }
|
gltf = { version = "1.0", features = ["utils"] }
|
||||||
|
|
|
@ -36,33 +36,36 @@ pub fn load_stl(path: std::path::PathBuf) -> model::Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_gltf(path: std::path::PathBuf) -> model::Model {
|
pub fn load_gltf(path: std::path::PathBuf) -> model::Model {
|
||||||
use gltf::*;
|
|
||||||
|
|
||||||
let name = path.file_name().map(|v| v.to_string_lossy().to_string());
|
let name = path.file_name().map(|v| v.to_string_lossy().to_string());
|
||||||
let mut file = std::fs::File::open(path).unwrap();
|
let (document, buffers, _images) = gltf::import(path).unwrap();
|
||||||
let model = Gltf::from_reader(&mut file).unwrap();
|
|
||||||
|
|
||||||
GltfLoader::load(name, &model)
|
let buffers = buffers.into_iter().map(|v| v.to_vec()).collect();
|
||||||
|
|
||||||
|
let loader = GltfLoader {
|
||||||
|
name,
|
||||||
|
document,
|
||||||
|
buffers,
|
||||||
|
};
|
||||||
|
|
||||||
|
loader.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GltfLoader<'a> {
|
pub struct GltfLoader {
|
||||||
pub model: &'a gltf::Gltf,
|
pub name: Option<String>,
|
||||||
|
pub document: gltf::Document,
|
||||||
pub buffers: Vec<Vec<u8>>,
|
pub buffers: Vec<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GltfLoader<'a> {
|
impl GltfLoader {
|
||||||
pub fn load(name: Option<String>, model: &'a gltf::Gltf) -> model::Model {
|
pub fn load(mut self) -> model::Model {
|
||||||
let buffers = Self::load_buffers(model);
|
|
||||||
let mut loader = Self { model, buffers };
|
|
||||||
|
|
||||||
let mut model = model::Model {
|
let mut model = model::Model {
|
||||||
name,
|
name: self.name.clone(),
|
||||||
objects: vec![],
|
objects: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
for scene in loader.model.scenes() {
|
for scene in self.document.scenes() {
|
||||||
for node in scene.nodes() {
|
for node in scene.nodes() {
|
||||||
model.objects.push(loader.load_node(node));
|
model.objects.push(self.load_node(node, glam::Vec3::ONE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,8 +85,20 @@ impl<'a> GltfLoader<'a> {
|
||||||
buffer_data
|
buffer_data
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_node(&mut self, node: gltf::Node) -> model::Object {
|
pub fn load_node(&self, node: gltf::Node, parent_scale: glam::Vec3) -> model::Object {
|
||||||
let transform = model::Transform::default();
|
let (translation, orientation, scale) = node.transform().decomposed();
|
||||||
|
|
||||||
|
let orientation = glam::Quat::from_array(orientation);
|
||||||
|
let rotation = orientation.to_euler(glam::EulerRot::XYZ);
|
||||||
|
|
||||||
|
let scale: glam::Vec3 = scale.into();
|
||||||
|
let scale = scale * parent_scale;
|
||||||
|
|
||||||
|
let transform = model::Transform {
|
||||||
|
position: translation.into(),
|
||||||
|
rotation: rotation.into(),
|
||||||
|
scale,
|
||||||
|
};
|
||||||
|
|
||||||
let mut object = model::Object {
|
let mut object = model::Object {
|
||||||
name: node.name().map(str::to_string),
|
name: node.name().map(str::to_string),
|
||||||
|
@ -99,13 +114,13 @@ impl<'a> GltfLoader<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for child in node.children() {
|
for child in node.children() {
|
||||||
object.children.push(self.load_node(child));
|
object.children.push(self.load_node(child, scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
object
|
object
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_primitive_mesh(&mut self, primitive: gltf::Primitive) -> model::Mesh {
|
pub fn load_primitive_mesh(&self, primitive: gltf::Primitive) -> model::Mesh {
|
||||||
use gltf::mesh::util::{ReadIndices, ReadTexCoords};
|
use gltf::mesh::util::{ReadIndices, ReadTexCoords};
|
||||||
if primitive.mode() != gltf::mesh::Mode::Triangles {
|
if primitive.mode() != gltf::mesh::Mode::Triangles {
|
||||||
panic!("glTF primitive must be triangle list");
|
panic!("glTF primitive must be triangle list");
|
||||||
|
|
|
@ -7,7 +7,6 @@ use egui_winit::winit::{
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::{ControlFlow, EventLoop},
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
use legion::EntityStore;
|
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -16,6 +15,7 @@ mod model;
|
||||||
mod render;
|
mod render;
|
||||||
mod script;
|
mod script;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
mod world;
|
||||||
|
|
||||||
struct Application {
|
struct Application {
|
||||||
window: winit::window::Window,
|
window: winit::window::Window,
|
||||||
|
@ -29,9 +29,9 @@ struct Application {
|
||||||
egui_rp: EguiRenderPass,
|
egui_rp: EguiRenderPass,
|
||||||
ui: ui::UserInterface,
|
ui: ui::UserInterface,
|
||||||
viewport: ui::ViewportWidget,
|
viewport: ui::ViewportWidget,
|
||||||
render_state: Arc<RwLock<render::RenderState>>,
|
world: Arc<RwLock<world::World>>,
|
||||||
|
render_state: render::RenderState,
|
||||||
file_receiver: crossbeam_channel::Receiver<ui::FileEvent>,
|
file_receiver: crossbeam_channel::Receiver<ui::FileEvent>,
|
||||||
objects: Vec<ui::ObjectWidget>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application {
|
impl Application {
|
||||||
|
@ -73,9 +73,18 @@ impl Application {
|
||||||
let egui_state = egui_winit::State::new(4096, &window);
|
let egui_state = egui_winit::State::new(4096, &window);
|
||||||
let egui_ctx = egui::Context::default();
|
let egui_ctx = egui::Context::default();
|
||||||
let mut egui_rp = egui_wgpu_backend::RenderPass::new(&device, config.format, 1);
|
let mut egui_rp = egui_wgpu_backend::RenderPass::new(&device, config.format, 1);
|
||||||
let render_state =
|
|
||||||
render::RenderState::new(device.clone(), queue.clone(), config.format, &mut egui_rp);
|
let mut world = world::World::new();
|
||||||
let viewport_texture = render_state
|
|
||||||
|
let render_state = render::RenderState::new(
|
||||||
|
&mut world,
|
||||||
|
device.clone(),
|
||||||
|
queue.clone(),
|
||||||
|
config.format,
|
||||||
|
&mut egui_rp,
|
||||||
|
);
|
||||||
|
|
||||||
|
let viewport_texture = world
|
||||||
.resources
|
.resources
|
||||||
.get::<render::ViewportStore>()
|
.get::<render::ViewportStore>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -94,11 +103,11 @@ impl Application {
|
||||||
egui_state,
|
egui_state,
|
||||||
egui_ctx,
|
egui_ctx,
|
||||||
egui_rp,
|
egui_rp,
|
||||||
|
world: Arc::new(RwLock::new(world)),
|
||||||
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: Arc::new(RwLock::new(render_state)),
|
render_state,
|
||||||
file_receiver,
|
file_receiver,
|
||||||
objects: vec![],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,14 +124,13 @@ impl Application {
|
||||||
ui::ImportKind::Gltf => import::load_gltf(path),
|
ui::ImportKind::Gltf => import::load_gltf(path),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut render_state = self.render_state.write();
|
let mut world = self.world.write();
|
||||||
let loaded = render_state.load_model(&model);
|
let loaded = world.load_model(&model);
|
||||||
let transform = glam::Mat4::IDENTITY;
|
let transform = glam::Mat4::IDENTITY;
|
||||||
let widgets = render_state.spawn_model(&loaded, &transform);
|
world.spawn_model(&loaded, &transform);
|
||||||
self.objects.extend(widgets.into_iter());
|
}
|
||||||
},
|
|
||||||
Ok(ui::FileEvent::LoadScript(path)) => {
|
Ok(ui::FileEvent::LoadScript(path)) => {
|
||||||
script::Script::new(&path, self.render_state.to_owned());
|
script::Script::new(&path, self.world.to_owned());
|
||||||
}
|
}
|
||||||
Err(crossbeam_channel::TryRecvError::Empty) => break,
|
Err(crossbeam_channel::TryRecvError::Empty) => break,
|
||||||
Err(crossbeam_channel::TryRecvError::Disconnected) => {
|
Err(crossbeam_channel::TryRecvError::Disconnected) => {
|
||||||
|
@ -131,21 +139,8 @@ impl Application {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut render_state = self.render_state.write();
|
let mut world = self.world.write();
|
||||||
for object in self.objects.iter_mut() {
|
world.flush_dirty_objects();
|
||||||
object.flush_dirty(|object| {
|
|
||||||
let transform = object.transform.to_mat4();
|
|
||||||
for entity in object.entities.iter() {
|
|
||||||
render_state
|
|
||||||
.world
|
|
||||||
.entry_mut(*entity)
|
|
||||||
.unwrap()
|
|
||||||
.get_component_mut::<cyborg::scene::Transform>()
|
|
||||||
.unwrap()
|
|
||||||
.transform = transform;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_resize(&mut self, new_size: PhysicalSize<u32>) {
|
pub fn on_resize(&mut self, new_size: PhysicalSize<u32>) {
|
||||||
|
@ -168,15 +163,22 @@ impl Application {
|
||||||
puffin::profile_scope!("Draw egui");
|
puffin::profile_scope!("Draw egui");
|
||||||
let raw_input = self.egui_state.take_egui_input(&self.window);
|
let raw_input = self.egui_state.take_egui_input(&self.window);
|
||||||
self.egui_ctx.run(raw_input, |ctx| {
|
self.egui_ctx.run(raw_input, |ctx| {
|
||||||
self.ui.run(ctx, &mut self.viewport, &mut self.objects)
|
let mut world = self.world.write();
|
||||||
|
|
||||||
|
let mut resources = ui::UiResources {
|
||||||
|
world: &mut world,
|
||||||
|
viewport: &mut self.viewport,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ui.run(ctx, &mut resources);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
puffin::profile_scope!("Main render");
|
puffin::profile_scope!("Main render");
|
||||||
let mut render_state = self.render_state.write();
|
let mut world = self.world.write();
|
||||||
render_state.update_viewport(&mut self.egui_rp, &mut self.viewport);
|
world.update_viewport(&mut self.egui_rp, &mut self.viewport);
|
||||||
render_state.render();
|
world.execute(&mut self.render_state.render_schedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
puffin::profile_scope!("Render egui");
|
puffin::profile_scope!("Render egui");
|
||||||
|
|
|
@ -43,7 +43,7 @@ pub struct LoadedObject {
|
||||||
pub struct Transform {
|
pub struct Transform {
|
||||||
pub position: glam::Vec3,
|
pub position: glam::Vec3,
|
||||||
pub rotation: glam::Vec3, // TODO support glam::Quat too
|
pub rotation: glam::Vec3, // TODO support glam::Quat too
|
||||||
pub scale: f32,
|
pub scale: glam::Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transform {
|
impl Transform {
|
||||||
|
@ -55,7 +55,7 @@ impl Transform {
|
||||||
self.rotation[1],
|
self.rotation[1],
|
||||||
self.rotation[2],
|
self.rotation[2],
|
||||||
);
|
);
|
||||||
let scale = glam::Vec3::splat(self.scale);
|
let scale = self.scale;
|
||||||
glam::Mat4::from_scale_rotation_translation(scale, rotation, translation)
|
glam::Mat4::from_scale_rotation_translation(scale, rotation, translation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ impl Default for Transform {
|
||||||
Self {
|
Self {
|
||||||
position: glam::Vec3::ZERO,
|
position: glam::Vec3::ZERO,
|
||||||
rotation: glam::Vec3::ZERO,
|
rotation: glam::Vec3::ZERO,
|
||||||
scale: 1.0,
|
scale: glam::Vec3::ONE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::model;
|
use crate::model;
|
||||||
use crate::ui::{ObjectWidget, ViewportWidget};
|
use crate::ui::ViewportWidget;
|
||||||
use crate::wgpu;
|
use crate::wgpu;
|
||||||
|
use crate::world::World;
|
||||||
use cyborg::camera::Camera;
|
use cyborg::camera::Camera;
|
||||||
use cyborg::storage::mesh::MeshHandle;
|
use cyborg::storage::mesh::MeshHandle;
|
||||||
use cyborg::viewport::{Viewport, ViewportInfo, ViewportViews};
|
use cyborg::viewport::{Viewport, ViewportInfo, ViewportViews};
|
||||||
|
@ -154,13 +155,12 @@ impl cyborg::legion::RenderCallbacks for ViewportStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RenderState {
|
pub struct RenderState {
|
||||||
pub world: legion::World,
|
|
||||||
pub resources: legion::Resources,
|
|
||||||
pub render_schedule: legion::Schedule,
|
pub render_schedule: legion::Schedule,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderState {
|
impl RenderState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
world: &mut World,
|
||||||
device: Arc<wgpu::Device>,
|
device: Arc<wgpu::Device>,
|
||||||
queue: Arc<wgpu::Queue>,
|
queue: Arc<wgpu::Queue>,
|
||||||
output_format: wgpu::TextureFormat,
|
output_format: wgpu::TextureFormat,
|
||||||
|
@ -169,8 +169,8 @@ impl RenderState {
|
||||||
use cyborg::scene::{DebugDrawList, DebugVertex};
|
use cyborg::scene::{DebugDrawList, DebugVertex};
|
||||||
use cyborg::shader::{ShaderStore, ShaderWatcher};
|
use cyborg::shader::{ShaderStore, ShaderWatcher};
|
||||||
|
|
||||||
let mut world = legion::World::default();
|
let resources = &mut world.resources;
|
||||||
let mut resources = legion::Resources::default();
|
|
||||||
let viewport_store =
|
let viewport_store =
|
||||||
ViewportStore::new(device.clone(), queue.clone(), output_format, render_pass);
|
ViewportStore::new(device.clone(), queue.clone(), output_format, render_pass);
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ impl RenderState {
|
||||||
resources.insert(mesh_shaders);
|
resources.insert(mesh_shaders);
|
||||||
|
|
||||||
let mut render_schedule = legion::Schedule::builder();
|
let mut render_schedule = legion::Schedule::builder();
|
||||||
cyborg::legion::build_renderer(viewport_store, &mut resources, &mut render_schedule);
|
cyborg::legion::build_renderer(viewport_store, resources, &mut render_schedule);
|
||||||
let render_schedule = render_schedule.build();
|
let render_schedule = render_schedule.build();
|
||||||
|
|
||||||
// make debug draw grid
|
// make debug draw grid
|
||||||
|
@ -242,13 +242,11 @@ impl RenderState {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
Self {
|
Self { render_schedule }
|
||||||
world,
|
|
||||||
resources,
|
|
||||||
render_schedule,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
pub fn update_viewport(
|
pub fn update_viewport(
|
||||||
&mut self,
|
&mut self,
|
||||||
egui_rp: &mut egui_wgpu_backend::RenderPass,
|
egui_rp: &mut egui_wgpu_backend::RenderPass,
|
||||||
|
@ -277,11 +275,6 @@ impl RenderState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self) {
|
|
||||||
self.render_schedule
|
|
||||||
.execute(&mut self.world, &mut self.resources);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_model(&mut self, model: &model::Model) -> model::LoadedModel {
|
pub fn load_model(&mut self, model: &model::Model) -> model::LoadedModel {
|
||||||
model::LoadedModel {
|
model::LoadedModel {
|
||||||
name: model.name.clone(),
|
name: model.name.clone(),
|
||||||
|
@ -339,49 +332,4 @@ 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,
|
|
||||||
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, transform));
|
|
||||||
}
|
|
||||||
|
|
||||||
objects
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_object(
|
|
||||||
&mut self,
|
|
||||||
object: &model::LoadedObject,
|
|
||||||
root_transform: &glam::Mat4,
|
|
||||||
) -> ObjectWidget {
|
|
||||||
let transform = cyborg::scene::Transform {
|
|
||||||
transform: *root_transform * object.transform.to_mat4(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut entities = Vec::new();
|
|
||||||
for mesh in object.meshes.iter() {
|
|
||||||
let mesh = cyborg::scene::Mesh { mesh: *mesh };
|
|
||||||
let entity = self.world.push((mesh, transform.clone()));
|
|
||||||
entities.push(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut children = Vec::new();
|
|
||||||
for child in object.children.iter() {
|
|
||||||
children.push(self.spawn_object(child, root_transform));
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectWidget {
|
|
||||||
name: object.name.clone(),
|
|
||||||
transform: object.transform.clone(),
|
|
||||||
entities,
|
|
||||||
children,
|
|
||||||
dirty: false,
|
|
||||||
children_dirty: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::model::LoadedModel;
|
use crate::model::LoadedModel;
|
||||||
use crate::render::RenderState;
|
use crate::world::World;
|
||||||
use mlua::{Lua, LuaSerdeExt, Result, Value};
|
use mlua::{Lua, LuaSerdeExt, Result, Value};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -10,7 +10,7 @@ impl mlua::UserData for LoadedModel {}
|
||||||
|
|
||||||
pub struct ScriptData {
|
pub struct ScriptData {
|
||||||
root_path: PathBuf,
|
root_path: PathBuf,
|
||||||
render_state: Arc<RwLock<RenderState>>,
|
world: Arc<RwLock<World>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
|
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
|
||||||
|
@ -31,7 +31,7 @@ impl Transform {
|
||||||
fn load_gltf(lua: &Lua, filename: String) -> Result<LoadedModel> {
|
fn load_gltf(lua: &Lua, filename: String) -> Result<LoadedModel> {
|
||||||
let data = lua.app_data_ref::<ScriptData>().unwrap();
|
let data = lua.app_data_ref::<ScriptData>().unwrap();
|
||||||
let model = crate::import::load_gltf(data.root_path.join(filename).into());
|
let model = crate::import::load_gltf(data.root_path.join(filename).into());
|
||||||
let loaded = data.render_state.write().load_model(&model);
|
let loaded = data.world.write().load_model(&model);
|
||||||
Ok(loaded)
|
Ok(loaded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ fn spawn_model(lua: &Lua, model: (LoadedModel, Value)) -> Result<()> {
|
||||||
let data = lua.app_data_ref::<ScriptData>().unwrap();
|
let data = lua.app_data_ref::<ScriptData>().unwrap();
|
||||||
let transform: Transform = lua.from_value(model.1)?;
|
let transform: Transform = lua.from_value(model.1)?;
|
||||||
let transform = transform.to_mat4();
|
let transform = transform.to_mat4();
|
||||||
data.render_state.write().spawn_model(&model.0, &transform);
|
data.world.write().spawn_model(&model.0, &transform);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,12 +48,12 @@ pub struct Script {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Script {
|
impl Script {
|
||||||
pub fn new(file: &Path, render_state: Arc<RwLock<RenderState>>) -> Self {
|
pub fn new(file: &Path, world: Arc<RwLock<World>>) -> Self {
|
||||||
let lua = Lua::new();
|
let lua = Lua::new();
|
||||||
|
|
||||||
let data = ScriptData {
|
let data = ScriptData {
|
||||||
root_path: file.parent().unwrap().into(),
|
root_path: file.parent().unwrap().into(),
|
||||||
render_state,
|
world,
|
||||||
};
|
};
|
||||||
|
|
||||||
lua.set_app_data(data);
|
lua.set_app_data(data);
|
||||||
|
|
252
editor/src/ui.rs
252
editor/src/ui.rs
|
@ -1,8 +1,11 @@
|
||||||
use crate::winit;
|
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
use cyborg::camera::Flycam;
|
use cyborg::camera::Flycam;
|
||||||
|
use legion::Entity;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::winit;
|
||||||
|
use crate::world::{Object, World};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum FileEvent {
|
pub enum FileEvent {
|
||||||
Save,
|
Save,
|
||||||
|
@ -23,6 +26,11 @@ pub enum Workspace {
|
||||||
NodeEditor,
|
NodeEditor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct UiResources<'a> {
|
||||||
|
pub world: &'a mut World,
|
||||||
|
pub viewport: &'a mut ViewportWidget,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct UserInterface {
|
pub struct UserInterface {
|
||||||
file_sender: Sender<FileEvent>,
|
file_sender: Sender<FileEvent>,
|
||||||
developer_mode: bool,
|
developer_mode: bool,
|
||||||
|
@ -52,12 +60,7 @@ impl UserInterface {
|
||||||
self.quit
|
self.quit
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(&mut self, ctx: &egui::Context, resources: &mut UiResources) {
|
||||||
&mut self,
|
|
||||||
ctx: &egui::Context,
|
|
||||||
viewport: &mut ViewportWidget,
|
|
||||||
objects: &mut [ObjectWidget],
|
|
||||||
) {
|
|
||||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||||
egui::menu::bar(ui, |ui| self.ui_menu_bar(ui));
|
egui::menu::bar(ui, |ui| self.ui_menu_bar(ui));
|
||||||
});
|
});
|
||||||
|
@ -98,7 +101,7 @@ impl UserInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.workspace {
|
match self.workspace {
|
||||||
Workspace::Scene => self.ui_scene(ctx, viewport, objects),
|
Workspace::Scene => self.ui_scene(ctx, resources),
|
||||||
Workspace::NodeEditor => {
|
Workspace::NodeEditor => {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| self.ui_node_editor(ui));
|
egui::CentralPanel::default().show(ctx, |ui| self.ui_node_editor(ui));
|
||||||
}
|
}
|
||||||
|
@ -194,28 +197,65 @@ impl UserInterface {
|
||||||
ui.selectable_value(&mut self.workspace, Workspace::NodeEditor, "Node Editor");
|
ui.selectable_value(&mut self.workspace, Workspace::NodeEditor, "Node Editor");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ui_scene(
|
pub fn ui_scene(&mut self, ctx: &egui::Context, resources: &mut UiResources) {
|
||||||
&mut self,
|
|
||||||
ctx: &egui::Context,
|
|
||||||
viewport: &mut ViewportWidget,
|
|
||||||
objects: &mut [ObjectWidget],
|
|
||||||
) {
|
|
||||||
egui::SidePanel::left("objects_panel")
|
egui::SidePanel::left("objects_panel")
|
||||||
.resizable(true)
|
.resizable(true)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| self.ui_objects(ui, resources));
|
||||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
|
||||||
for (index, object) in objects.iter_mut().enumerate() {
|
|
||||||
object.ui(index, ui);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.add(viewport);
|
resources.viewport.show(ui);
|
||||||
ui.heading("Viewport");
|
ui.heading("Viewport");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ui_objects(&mut self, ui: &mut egui::Ui, resources: &mut UiResources) {
|
||||||
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||||
|
let roots = resources.world.root_objects.to_owned();
|
||||||
|
for object in roots.into_iter() {
|
||||||
|
self.ui_object(ui, resources, object);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui_object(
|
||||||
|
&mut self,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
resources: &mut UiResources,
|
||||||
|
id: Entity,
|
||||||
|
) -> bool {
|
||||||
|
let object: &mut Object = resources.world.get_component_mut(id);
|
||||||
|
let name = object
|
||||||
|
.name
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&"<unnamed>".into())
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let children = object.children.to_owned();
|
||||||
|
let mut any_dirty = false;
|
||||||
|
|
||||||
|
egui::CollapsingHeader::new(name)
|
||||||
|
.id_source(format!("child_{:?}", id))
|
||||||
|
.default_open(true)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
let object: &mut Object = resources.world.get_component_mut(id);
|
||||||
|
object.ui(ui);
|
||||||
|
|
||||||
|
for child in children.into_iter() {
|
||||||
|
if self.ui_object(ui, resources, child) {
|
||||||
|
any_dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let object: &mut Object = resources.world.get_component_mut(id);
|
||||||
|
|
||||||
|
if any_dirty {
|
||||||
|
object.children_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
object.dirty || any_dirty
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ui_node_editor(&mut self, ui: &mut egui::Ui) {
|
pub fn ui_node_editor(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.label("Node editor goes here!");
|
ui.label("Node editor goes here!");
|
||||||
}
|
}
|
||||||
|
@ -260,6 +300,7 @@ pub struct ViewportWidget {
|
||||||
pub flycam: Flycam,
|
pub flycam: Flycam,
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
|
pub dragging: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewportWidget {
|
impl ViewportWidget {
|
||||||
|
@ -269,21 +310,39 @@ impl ViewportWidget {
|
||||||
flycam: Flycam::new(0.002, 10.0, 0.25),
|
flycam: Flycam::new(0.002, 10.0, 0.25),
|
||||||
width: 640,
|
width: 640,
|
||||||
height: 480,
|
height: 480,
|
||||||
}
|
dragging: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl egui::Widget for &mut ViewportWidget {
|
fn should_drag(
|
||||||
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
&self,
|
||||||
|
viewport: egui::Rect,
|
||||||
|
gizmo_active: bool,
|
||||||
|
pointer: &egui::PointerState,
|
||||||
|
) -> bool {
|
||||||
|
if gizmo_active {
|
||||||
|
false
|
||||||
|
} else if !pointer.primary_down() {
|
||||||
|
false
|
||||||
|
} else if self.dragging {
|
||||||
|
true
|
||||||
|
} else if let Some(pos) = pointer.interact_pos() {
|
||||||
|
viewport.contains(pos)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.style_mut().spacing.window_margin = egui::style::Margin::same(0.0);
|
ui.style_mut().spacing.window_margin = egui::style::Margin::same(0.0);
|
||||||
let rect = ui.max_rect();
|
let rect = ui.max_rect();
|
||||||
let id = egui::Id::new("viewport_widget");
|
|
||||||
let sense = egui::Sense::click_and_drag();
|
|
||||||
let response = ui.interact(rect, id, sense);
|
|
||||||
|
|
||||||
self.width = rect.width().round() as u32;
|
self.width = rect.width().round() as u32;
|
||||||
self.height = rect.height().round() as u32;
|
self.height = rect.height().round() as u32;
|
||||||
|
|
||||||
|
self.flycam.resize(self.width, self.height);
|
||||||
|
self.flycam.update();
|
||||||
|
|
||||||
use egui::{pos2, Color32, Mesh, Rect, Shape};
|
use egui::{pos2, Color32, Mesh, Rect, Shape};
|
||||||
let mut mesh = Mesh::with_texture(self.texture);
|
let mut mesh = Mesh::with_texture(self.texture);
|
||||||
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
|
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
|
||||||
|
@ -291,11 +350,22 @@ impl egui::Widget for &mut ViewportWidget {
|
||||||
mesh.add_rect_with_uv(rect, uv, tint);
|
mesh.add_rect_with_uv(rect, uv, tint);
|
||||||
ui.painter().add(Shape::mesh(mesh));
|
ui.painter().add(Shape::mesh(mesh));
|
||||||
|
|
||||||
if response.dragged() {
|
let gizmo = egui_gizmo::Gizmo::new("My gizmo")
|
||||||
let delta = response.drag_delta();
|
.viewport(rect)
|
||||||
|
.view_matrix(self.flycam.get_view().to_cols_array_2d())
|
||||||
|
.projection_matrix(self.flycam.get_projection().to_cols_array_2d());
|
||||||
|
|
||||||
|
let gizmo = gizmo.interact(ui);
|
||||||
|
|
||||||
|
let input = ui.input();
|
||||||
|
|
||||||
|
self.dragging = self.should_drag(rect, gizmo.is_some(), &input.pointer);
|
||||||
|
|
||||||
|
if self.dragging {
|
||||||
|
let delta = input.pointer.delta();
|
||||||
self.flycam.process_mouse(delta.x as f64, delta.y as f64);
|
self.flycam.process_mouse(delta.x as f64, delta.y as f64);
|
||||||
|
|
||||||
for event in ui.input().events.iter() {
|
for event in input.events.iter() {
|
||||||
match event {
|
match event {
|
||||||
egui::Event::Key { key, pressed, .. } => {
|
egui::Event::Key { key, pressed, .. } => {
|
||||||
use winit::event::{ElementState, VirtualKeyCode};
|
use winit::event::{ElementState, VirtualKeyCode};
|
||||||
|
@ -324,126 +394,8 @@ impl egui::Widget for &mut ViewportWidget {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if response.drag_released() {
|
} else {
|
||||||
self.flycam.defocus();
|
self.flycam.defocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.flycam.resize(self.width, self.height);
|
|
||||||
self.flycam.update();
|
|
||||||
|
|
||||||
response
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ObjectWidget {
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub transform: crate::model::Transform,
|
|
||||||
pub entities: Vec<legion::Entity>,
|
|
||||||
pub children: Vec<ObjectWidget>,
|
|
||||||
pub dirty: bool,
|
|
||||||
pub children_dirty: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectWidget {
|
|
||||||
pub fn ui(&mut self, index: usize, ui: &mut egui::Ui) {
|
|
||||||
egui::CollapsingHeader::new(self.name.as_ref().unwrap_or(&"<unnamed>".into()))
|
|
||||||
.id_source(format!("child_{}", index))
|
|
||||||
.default_open(true)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
self.ui_self(ui);
|
|
||||||
|
|
||||||
for (index, child) in self.children.iter_mut().enumerate() {
|
|
||||||
child.ui(index, ui);
|
|
||||||
if child.dirty || child.children_dirty {
|
|
||||||
self.children_dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ui_self(&mut self, ui: &mut egui::Ui) {
|
|
||||||
egui::Grid::new("root_object")
|
|
||||||
.num_columns(4)
|
|
||||||
.striped(true)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.label("Position: ");
|
|
||||||
self.ui_position(ui);
|
|
||||||
ui.end_row();
|
|
||||||
|
|
||||||
ui.label("Rotation: ");
|
|
||||||
self.ui_rotation(ui);
|
|
||||||
ui.end_row();
|
|
||||||
|
|
||||||
ui.label("Scale: ");
|
|
||||||
self.ui_scale(ui);
|
|
||||||
ui.end_row();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ui_position(&mut self, ui: &mut egui::Ui) {
|
|
||||||
let speed = 0.1 * self.transform.scale;
|
|
||||||
if Self::ui_vec3(ui, speed, &mut self.transform.position) {
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ui_rotation(&mut self, ui: &mut egui::Ui) {
|
|
||||||
let axes = &mut self.transform.rotation;
|
|
||||||
|
|
||||||
if ui.drag_angle(&mut axes.x).changed() {
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.drag_angle(&mut axes.y).changed() {
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.drag_angle(&mut axes.z).changed() {
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ui_vec3(ui: &mut egui::Ui, speed: f32, vec3: &mut glam::Vec3) -> bool {
|
|
||||||
let x_drag = egui::DragValue::new(&mut vec3.x).speed(speed);
|
|
||||||
let dirty = ui.add(x_drag).changed();
|
|
||||||
|
|
||||||
let dirty = dirty || {
|
|
||||||
// For some reason, Rust complains if this isn't in a block
|
|
||||||
let y_drag = egui::DragValue::new(&mut vec3.y).speed(speed);
|
|
||||||
ui.add(y_drag).changed()
|
|
||||||
};
|
|
||||||
|
|
||||||
let z_drag = egui::DragValue::new(&mut vec3.z).speed(speed);
|
|
||||||
let dirty = dirty || ui.add(z_drag).changed();
|
|
||||||
|
|
||||||
dirty
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ui_scale(&mut self, ui: &mut egui::Ui) {
|
|
||||||
let scale_speed = self.transform.scale * 0.01;
|
|
||||||
let drag = egui::DragValue::new(&mut self.transform.scale)
|
|
||||||
.clamp_range(0.0..=f32::INFINITY)
|
|
||||||
.speed(scale_speed);
|
|
||||||
if ui.add(drag).changed() {
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flush_dirty(&mut self, mut f: impl FnMut(&mut Self)) {
|
|
||||||
let mut stack = vec![self];
|
|
||||||
while let Some(parent) = stack.pop() {
|
|
||||||
if parent.dirty {
|
|
||||||
parent.dirty = false;
|
|
||||||
f(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if parent.children_dirty {
|
|
||||||
parent.children_dirty = false;
|
|
||||||
|
|
||||||
for child in parent.children.iter_mut() {
|
|
||||||
stack.push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
use legion::Entity;
|
||||||
|
|
||||||
|
use crate::model;
|
||||||
|
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
pub struct World {
|
||||||
|
pub world: legion::World,
|
||||||
|
pub resources: legion::Resources,
|
||||||
|
pub root_objects: Vec<Entity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for World {
|
||||||
|
type Target = legion::World;
|
||||||
|
|
||||||
|
fn deref(&self) -> &legion::World {
|
||||||
|
&self.world
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for World {
|
||||||
|
fn deref_mut(&mut self) -> &mut legion::World {
|
||||||
|
&mut self.world
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
world: Default::default(),
|
||||||
|
resources: Default::default(),
|
||||||
|
root_objects: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(&mut self, schedule: &mut legion::Schedule) {
|
||||||
|
schedule.execute(&mut self.world, &mut self.resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_component_mut<T: 'static + Send + Sync>(&mut self, entity: Entity) -> &mut T {
|
||||||
|
self.entry(entity).unwrap().into_component_mut().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_model(&mut self, model: &model::LoadedModel, transform: &glam::Mat4) -> Entity {
|
||||||
|
let mut children = Vec::new();
|
||||||
|
for object in model.objects.iter() {
|
||||||
|
children.push(self.spawn_object(object, transform));
|
||||||
|
}
|
||||||
|
|
||||||
|
let root = self.world.push((Object {
|
||||||
|
name: model.name.to_owned(),
|
||||||
|
transform: model::Transform::default(),
|
||||||
|
entities: Default::default(),
|
||||||
|
children,
|
||||||
|
dirty: false,
|
||||||
|
children_dirty: false,
|
||||||
|
},));
|
||||||
|
|
||||||
|
self.root_objects.push(root);
|
||||||
|
root
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_object(
|
||||||
|
&mut self,
|
||||||
|
object: &model::LoadedObject,
|
||||||
|
root_transform: &glam::Mat4,
|
||||||
|
) -> Entity {
|
||||||
|
let transform = cyborg::scene::Transform {
|
||||||
|
transform: *root_transform * object.transform.to_mat4(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut entities = Vec::new();
|
||||||
|
for mesh in object.meshes.iter() {
|
||||||
|
let mesh = cyborg::scene::Mesh { mesh: *mesh };
|
||||||
|
let entity = self.world.push((mesh, transform.clone()));
|
||||||
|
entities.push(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut children = Vec::new();
|
||||||
|
for child in object.children.iter() {
|
||||||
|
children.push(self.spawn_object(child, root_transform));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.world.push((Object {
|
||||||
|
name: object.name.clone(),
|
||||||
|
transform: object.transform.clone(),
|
||||||
|
entities,
|
||||||
|
children,
|
||||||
|
dirty: false,
|
||||||
|
children_dirty: false,
|
||||||
|
},))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flush_dirty_objects(&mut self) {
|
||||||
|
let mut stack = self.root_objects.to_owned();
|
||||||
|
while let Some(parent) = stack.pop() {
|
||||||
|
let parent: &mut Object = self.get_component_mut(parent);
|
||||||
|
|
||||||
|
if parent.children_dirty {
|
||||||
|
parent.children_dirty = false;
|
||||||
|
stack.extend_from_slice(&parent.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent.dirty {
|
||||||
|
parent.dirty = false;
|
||||||
|
|
||||||
|
let mat4 = parent.transform.to_mat4();
|
||||||
|
for entity in parent.entities.to_owned().into_iter() {
|
||||||
|
let transform: &mut cyborg::scene::Transform = self.get_component_mut(entity);
|
||||||
|
transform.transform = mat4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Object {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub transform: model::Transform,
|
||||||
|
pub entities: Vec<Entity>,
|
||||||
|
pub children: Vec<Entity>,
|
||||||
|
pub dirty: bool,
|
||||||
|
pub children_dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object {
|
||||||
|
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
egui::Grid::new("root_object")
|
||||||
|
.num_columns(4)
|
||||||
|
.striped(true)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.label("Position: ");
|
||||||
|
self.ui_position(ui);
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Rotation: ");
|
||||||
|
self.ui_rotation(ui);
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Scale: ");
|
||||||
|
self.ui_scale(ui);
|
||||||
|
ui.end_row();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui_position(&mut self, ui: &mut egui::Ui) {
|
||||||
|
let speed = 0.1 * self.transform.scale.min_element();
|
||||||
|
if Self::ui_vec3(ui, speed, &mut self.transform.position) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui_rotation(&mut self, ui: &mut egui::Ui) {
|
||||||
|
let axes = &mut self.transform.rotation;
|
||||||
|
|
||||||
|
if ui.drag_angle(&mut axes.x).changed() {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.drag_angle(&mut axes.y).changed() {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.drag_angle(&mut axes.z).changed() {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui_vec3(ui: &mut egui::Ui, speed: f32, vec3: &mut glam::Vec3) -> bool {
|
||||||
|
let x_drag = egui::DragValue::new(&mut vec3.x).speed(speed);
|
||||||
|
let dirty = ui.add(x_drag).changed();
|
||||||
|
|
||||||
|
let dirty = dirty || {
|
||||||
|
// For some reason, Rust complains if this isn't in a block
|
||||||
|
let y_drag = egui::DragValue::new(&mut vec3.y).speed(speed);
|
||||||
|
ui.add(y_drag).changed()
|
||||||
|
};
|
||||||
|
|
||||||
|
let z_drag = egui::DragValue::new(&mut vec3.z).speed(speed);
|
||||||
|
let dirty = dirty || ui.add(z_drag).changed();
|
||||||
|
|
||||||
|
dirty
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui_scale(&mut self, ui: &mut egui::Ui) {
|
||||||
|
let speed = self.transform.scale.min_element() * 0.01;
|
||||||
|
if Self::ui_vec3(ui, speed, &mut self.transform.scale) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -234,15 +234,20 @@ impl Flycam {
|
||||||
self.position.extend(0.0).to_array()
|
self.position.extend(0.0).to_array()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_vp(&self) -> [[f32; 4]; 4] {
|
pub fn get_view(&self) -> glam::Mat4 {
|
||||||
// view matrix is inverted camera pose (world space to camera space)
|
// view matrix is inverted camera pose (world space to camera space)
|
||||||
let rotation = Mat4::from_quat(self.get_orientation().inverse());
|
let rotation = Mat4::from_quat(self.get_orientation().inverse());
|
||||||
let translation = Mat4::from_translation(-self.position);
|
let translation = Mat4::from_translation(-self.position);
|
||||||
let view = rotation * translation;
|
rotation * translation
|
||||||
|
}
|
||||||
|
|
||||||
// perspective projection
|
pub fn get_projection(&self) -> glam::Mat4 {
|
||||||
let proj = Mat4::perspective_rh_gl(self.fovy, self.aspect, self.znear, self.zfar);
|
Mat4::perspective_rh_gl(self.fovy, self.aspect, self.znear, self.zfar)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_vp(&self) -> [[f32; 4]; 4] {
|
||||||
|
let view = self.get_view();
|
||||||
|
let proj = self.get_projection();
|
||||||
let vp = proj * view;
|
let vp = proj * view;
|
||||||
vp.to_cols_array_2d()
|
vp.to_cols_array_2d()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue