Compare commits

..

4 Commits

Author SHA1 Message Date
mars e9700e65fe Upgrade glTF loader + three-axis model scales 2022-10-22 12:11:28 -06:00
mars e238fa244d Clean up direct World entry usage 2022-10-22 11:17:40 -06:00
mars 1834b77522 Refactor editor world state 2022-10-22 11:11:08 -06:00
mars 4bfd6977c3 Add stub 3D gizmo 2022-09-17 11:28:10 -06:00
10 changed files with 406 additions and 284 deletions

View File

@ -7,7 +7,7 @@
> A repulsive reaper of all of human consciousness -- but mostly SIGGRAPH presentations.
> H.R Giger's irreconcilable reality... if H.R. Giger was a computer programmer.
Cyborg is an experimental, GPU-driven rendering engine using Rust and wgpu.
Cyborg is a free-form rendering engine that we mash whatever we feel like into.
It's a test bed for all sorts of modern rendering technology. Our goal is to take
techniques and features from modern game engines and reimplement them on our own, for
the sake of education, performance, and reusability. We wanna *make shit work.*
@ -16,15 +16,16 @@ We also want to give artists a playground for generating all sorts of unique
3D visuals -- custom shaders, procedurally generated meshes and textures,
and a focus on *interesting* visuals.
Realism is a non-goal!
Make shrooms obsolete.
Go wild.
Modularity and reusability are important. We want to be able to unplug certain
Modularity and reusability are important. We wanna be able to unplug certain
parts of the rendering pipeline, upgrade them, fix them, document them, commit
them, tag them, etc., then stick them back in in a different place to end up
with a different resulting image.
Cyborg is licensed under the GPL-3.0. Yes, this does mean that any software using
Cyborg will be required to make its source code public. But it also means that
Cyborg is licensed under the GPL-3.0. Yes, this does mean that Bevy games using
Cyborg will be required to make their source code public. But it also means that
new visuals added to Cyborg will become available to all other developers and
artists to use. Hopefully, this will expand the collective knowledge of how different
rendering effects work, and decrease the pressure on graphics programmers to
@ -36,3 +37,10 @@ gathered from countless programmers and individual projects.
> Resistance is futile.
>
> -- The Borg
Cyborg is based on [Bevy](https://bevyengine.org/), a "refreshingly simple"
game engine written in Rust. Bevy provides a lot of useful utilities like
ECS-based data parallelization and a modular plugin system. Cyborg is integrated
into the rest of the Bevy ecosystem, and can also work as a replacement Bevy's
provided rendering engine, giving Bevy games GPU-powered, procedurally-generated
content and access to modern rendering optimizations.

View File

@ -8,7 +8,8 @@ bytemuck = "^1.0"
crossbeam-channel = "^0.5"
cyborg = { path = "../", features = ["legion"] }
egui = "0.18"
egui-winit = "0.18.0"
egui-gizmo = "0.7"
egui-winit = "0.18"
egui_wgpu_backend = "0.18.0"
glam = { version = "0.20", features = ["serde"] }
gltf = { version = "1.0", features = ["utils"] }

View File

@ -36,33 +36,36 @@ pub fn load_stl(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 mut file = std::fs::File::open(path).unwrap();
let model = Gltf::from_reader(&mut file).unwrap();
let (document, buffers, _images) = gltf::import(path).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 model: &'a gltf::Gltf,
pub struct GltfLoader {
pub name: Option<String>,
pub document: gltf::Document,
pub buffers: Vec<Vec<u8>>,
}
impl<'a> GltfLoader<'a> {
pub fn load(name: Option<String>, model: &'a gltf::Gltf) -> model::Model {
let buffers = Self::load_buffers(model);
let mut loader = Self { model, buffers };
impl GltfLoader {
pub fn load(mut self) -> model::Model {
let mut model = model::Model {
name,
name: self.name.clone(),
objects: vec![],
};
for scene in loader.model.scenes() {
for scene in self.document.scenes() {
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
}
pub fn load_node(&mut self, node: gltf::Node) -> model::Object {
let transform = model::Transform::default();
pub fn load_node(&self, node: gltf::Node, parent_scale: glam::Vec3) -> model::Object {
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 {
name: node.name().map(str::to_string),
@ -99,13 +114,13 @@ impl<'a> GltfLoader<'a> {
}
for child in node.children() {
object.children.push(self.load_node(child));
object.children.push(self.load_node(child, scale));
}
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};
if primitive.mode() != gltf::mesh::Mode::Triangles {
panic!("glTF primitive must be triangle list");

View File

@ -7,7 +7,6 @@ use egui_winit::winit::{
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
use legion::EntityStore;
use parking_lot::RwLock;
use std::sync::Arc;
@ -16,6 +15,7 @@ mod model;
mod render;
mod script;
mod ui;
mod world;
struct Application {
window: winit::window::Window,
@ -29,9 +29,9 @@ struct Application {
egui_rp: EguiRenderPass,
ui: ui::UserInterface,
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>,
objects: Vec<ui::ObjectWidget>,
}
impl Application {
@ -73,9 +73,18 @@ impl Application {
let egui_state = egui_winit::State::new(4096, &window);
let egui_ctx = egui::Context::default();
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 viewport_texture = render_state
let mut world = world::World::new();
let render_state = render::RenderState::new(
&mut world,
device.clone(),
queue.clone(),
config.format,
&mut egui_rp,
);
let viewport_texture = world
.resources
.get::<render::ViewportStore>()
.unwrap()
@ -94,11 +103,11 @@ impl Application {
egui_state,
egui_ctx,
egui_rp,
world: Arc::new(RwLock::new(world)),
ui: ui::UserInterface::new(file_sender),
viewport: ui::ViewportWidget::new(viewport_texture),
render_state: Arc::new(RwLock::new(render_state)),
render_state,
file_receiver,
objects: vec![],
}
}
@ -115,14 +124,13 @@ impl Application {
ui::ImportKind::Gltf => import::load_gltf(path),
};
let mut render_state = self.render_state.write();
let loaded = render_state.load_model(&model);
let mut world = self.world.write();
let loaded = world.load_model(&model);
let transform = glam::Mat4::IDENTITY;
let widgets = render_state.spawn_model(&loaded, &transform);
self.objects.extend(widgets.into_iter());
},
world.spawn_model(&loaded, &transform);
}
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::Disconnected) => {
@ -130,22 +138,9 @@ 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() {
render_state
.world
.entry_mut(*entity)
.unwrap()
.get_component_mut::<cyborg::scene::Transform>()
.unwrap()
.transform = transform;
}
});
}
let mut world = self.world.write();
world.flush_dirty_objects();
}
pub fn on_resize(&mut self, new_size: PhysicalSize<u32>) {
@ -168,15 +163,22 @@ impl Application {
puffin::profile_scope!("Draw egui");
let raw_input = self.egui_state.take_egui_input(&self.window);
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");
let mut render_state = self.render_state.write();
render_state.update_viewport(&mut self.egui_rp, &mut self.viewport);
render_state.render();
let mut world = self.world.write();
world.update_viewport(&mut self.egui_rp, &mut self.viewport);
world.execute(&mut self.render_state.render_schedule);
}
puffin::profile_scope!("Render egui");

View File

@ -43,7 +43,7 @@ pub struct LoadedObject {
pub struct Transform {
pub position: glam::Vec3,
pub rotation: glam::Vec3, // TODO support glam::Quat too
pub scale: f32,
pub scale: glam::Vec3,
}
impl Transform {
@ -55,7 +55,7 @@ impl Transform {
self.rotation[1],
self.rotation[2],
);
let scale = glam::Vec3::splat(self.scale);
let scale = self.scale;
glam::Mat4::from_scale_rotation_translation(scale, rotation, translation)
}
}
@ -65,7 +65,7 @@ impl Default for Transform {
Self {
position: glam::Vec3::ZERO,
rotation: glam::Vec3::ZERO,
scale: 1.0,
scale: glam::Vec3::ONE,
}
}
}

View File

@ -1,6 +1,7 @@
use crate::model;
use crate::ui::{ObjectWidget, ViewportWidget};
use crate::ui::ViewportWidget;
use crate::wgpu;
use crate::world::World;
use cyborg::camera::Camera;
use cyborg::storage::mesh::MeshHandle;
use cyborg::viewport::{Viewport, ViewportInfo, ViewportViews};
@ -154,13 +155,12 @@ impl cyborg::legion::RenderCallbacks for ViewportStore {
}
pub struct RenderState {
pub world: legion::World,
pub resources: legion::Resources,
pub render_schedule: legion::Schedule,
}
impl RenderState {
pub fn new(
world: &mut World,
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
output_format: wgpu::TextureFormat,
@ -169,8 +169,8 @@ impl RenderState {
use cyborg::scene::{DebugDrawList, DebugVertex};
use cyborg::shader::{ShaderStore, ShaderWatcher};
let mut world = legion::World::default();
let mut resources = legion::Resources::default();
let resources = &mut world.resources;
let viewport_store =
ViewportStore::new(device.clone(), queue.clone(), output_format, render_pass);
@ -200,7 +200,7 @@ impl RenderState {
resources.insert(mesh_shaders);
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();
// make debug draw grid
@ -242,13 +242,11 @@ impl RenderState {
},
));
Self {
world,
resources,
render_schedule,
}
Self { render_schedule }
}
}
impl World {
pub fn update_viewport(
&mut self,
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 {
model::LoadedModel {
name: model.name.clone(),
@ -339,49 +332,4 @@ impl RenderState {
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,
}
}
}

View File

@ -1,5 +1,5 @@
use crate::model::LoadedModel;
use crate::render::RenderState;
use crate::world::World;
use mlua::{Lua, LuaSerdeExt, Result, Value};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
@ -10,7 +10,7 @@ impl mlua::UserData for LoadedModel {}
pub struct ScriptData {
root_path: PathBuf,
render_state: Arc<RwLock<RenderState>>,
world: Arc<RwLock<World>>,
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
@ -31,7 +31,7 @@ impl Transform {
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);
let loaded = data.world.write().load_model(&model);
Ok(loaded)
}
@ -39,7 +39,7 @@ 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);
data.world.write().spawn_model(&model.0, &transform);
Ok(())
}
@ -48,12 +48,12 @@ pub struct 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 data = ScriptData {
root_path: file.parent().unwrap().into(),
render_state,
world,
};
lua.set_app_data(data);

View File

@ -1,8 +1,11 @@
use crate::winit;
use crossbeam_channel::Sender;
use cyborg::camera::Flycam;
use legion::Entity;
use std::path::PathBuf;
use crate::winit;
use crate::world::{Object, World};
#[derive(Clone, Debug)]
pub enum FileEvent {
Save,
@ -23,6 +26,11 @@ pub enum Workspace {
NodeEditor,
}
pub struct UiResources<'a> {
pub world: &'a mut World,
pub viewport: &'a mut ViewportWidget,
}
pub struct UserInterface {
file_sender: Sender<FileEvent>,
developer_mode: bool,
@ -52,12 +60,7 @@ impl UserInterface {
self.quit
}
pub fn run(
&mut self,
ctx: &egui::Context,
viewport: &mut ViewportWidget,
objects: &mut [ObjectWidget],
) {
pub fn run(&mut self, ctx: &egui::Context, resources: &mut UiResources) {
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
egui::menu::bar(ui, |ui| self.ui_menu_bar(ui));
});
@ -98,7 +101,7 @@ impl UserInterface {
}
match self.workspace {
Workspace::Scene => self.ui_scene(ctx, viewport, objects),
Workspace::Scene => self.ui_scene(ctx, resources),
Workspace::NodeEditor => {
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");
}
pub fn ui_scene(
&mut self,
ctx: &egui::Context,
viewport: &mut ViewportWidget,
objects: &mut [ObjectWidget],
) {
pub fn ui_scene(&mut self, ctx: &egui::Context, resources: &mut UiResources) {
egui::SidePanel::left("objects_panel")
.resizable(true)
.show(ctx, |ui| {
egui::ScrollArea::vertical().show(ui, |ui| {
for (index, object) in objects.iter_mut().enumerate() {
object.ui(index, ui);
}
});
});
.show(ctx, |ui| self.ui_objects(ui, resources));
egui::CentralPanel::default().show(ctx, |ui| {
ui.add(viewport);
resources.viewport.show(ui);
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) {
ui.label("Node editor goes here!");
}
@ -260,6 +300,7 @@ pub struct ViewportWidget {
pub flycam: Flycam,
pub width: u32,
pub height: u32,
pub dragging: bool,
}
impl ViewportWidget {
@ -269,21 +310,39 @@ impl ViewportWidget {
flycam: Flycam::new(0.002, 10.0, 0.25),
width: 640,
height: 480,
dragging: false,
}
}
}
impl egui::Widget for &mut ViewportWidget {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
fn should_drag(
&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);
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.height = rect.height().round() as u32;
self.flycam.resize(self.width, self.height);
self.flycam.update();
use egui::{pos2, Color32, Mesh, Rect, Shape};
let mut mesh = Mesh::with_texture(self.texture);
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);
ui.painter().add(Shape::mesh(mesh));
if response.dragged() {
let delta = response.drag_delta();
let gizmo = egui_gizmo::Gizmo::new("My gizmo")
.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);
for event in ui.input().events.iter() {
for event in input.events.iter() {
match event {
egui::Event::Key { key, pressed, .. } => {
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.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);
}
}
}
}
}

191
editor/src/world.rs Normal file
View File

@ -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;
}
}
}

View File

@ -233,16 +233,21 @@ impl Flycam {
pub fn get_eye(&self) -> [f32; 4] {
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)
let rotation = Mat4::from_quat(self.get_orientation().inverse());
let translation = Mat4::from_translation(-self.position);
let view = rotation * translation;
// perspective projection
let proj = Mat4::perspective_rh_gl(self.fovy, self.aspect, self.znear, self.zfar);
rotation * translation
}
pub fn get_projection(&self) -> glam::Mat4 {
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;
vp.to_cols_array_2d()
}