WIP scene hierarchy + glTF loading

This commit is contained in:
mars 2022-05-20 21:18:01 -06:00
parent c5eb2bff49
commit 3437d4d40f
6 changed files with 162 additions and 36 deletions

View File

@ -11,6 +11,7 @@ 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 = "0.20"
gltf = { version = "1.0", features = ["utils"] }
legion = "^0.4" legion = "^0.4"
pollster = "0.2" pollster = "0.2"
puffin = "^0.13" puffin = "^0.13"

View File

@ -28,11 +28,115 @@ pub fn load_stl(path: std::path::PathBuf) -> model::Model {
name: name.clone(), name: name.clone(),
objects: vec![model::Object { objects: vec![model::Object {
name, name,
mesh: model::Mesh { transform: Default::default(),
transform: Default::default(), mesh: Some(model::Mesh { vertices, indices }),
data: model::MeshData { vertices, indices },
},
children: vec![], children: vec![],
}], }],
} }
} }
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();
GltfLoader::load(name, &model)
}
pub struct GltfLoader<'a> {
pub model: &'a gltf::Gltf,
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 };
let mut model = model::Model {
name,
objects: vec![],
};
for scene in loader.model.scenes() {
for node in scene.nodes() {
model.objects.push(loader.load_node(node));
}
}
model
}
pub fn load_buffers(model: &gltf::Gltf) -> Vec<Vec<u8>> {
let mut buffer_data = Vec::<Vec<u8>>::new();
for buffer in model.buffers() {
match buffer.source() {
gltf::buffer::Source::Bin => {
buffer_data.push(model.blob.as_deref().unwrap().into());
}
_ => panic!("URI buffer sources are unsupported"),
}
}
buffer_data
}
pub fn load_node(&mut self, node: gltf::Node) -> model::Object {
let transform = model::Transform::default();
let mut object = model::Object {
name: node.name().map(str::to_string),
transform,
mesh: None,
children: vec![],
};
if let Some(mesh) = node.mesh() {
assert_eq!(1, mesh.primitives().len());
let primitive = mesh.primitives().next().unwrap();
object.mesh = Some(self.load_primitive_mesh(primitive));
}
for child in node.children() {
object.children.push(self.load_node(child));
}
object
}
pub fn load_primitive_mesh(&mut 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");
}
let reader = primitive.reader(|buffer| Some(&self.buffers[buffer.index()]));
let positions = reader.read_positions().unwrap();
let mut normals = reader.read_normals().unwrap();
let tex_coords = reader.read_tex_coords(0).unwrap();
let mut tex_coords = if let ReadTexCoords::F32(tex_coords) = tex_coords {
tex_coords
} else {
panic!("only f32 texture coordinates are supported")
};
let mut vertices = Vec::new();
for position in positions {
let normal = normals.next().unwrap();
let tex_coords = tex_coords.next().unwrap();
vertices.push(model::BasicVertex {
position: position.into(),
});
}
let indices = match reader.read_indices().unwrap() {
ReadIndices::U32(indices) => indices.collect(),
ReadIndices::U16(indices) => indices.map(|i| i as u32).collect(),
ReadIndices::U8(indices) => indices.map(|i| i as u32).collect(),
};
model::Mesh { vertices, indices }
}
}

View File

@ -107,12 +107,14 @@ impl Application {
match self.file_receiver.try_recv() { match self.file_receiver.try_recv() {
Ok(ui::FileEvent::Save) => println!("Saving!"), Ok(ui::FileEvent::Save) => println!("Saving!"),
Ok(ui::FileEvent::SaveAs(path)) => println!("Saving as: {:?}", path), Ok(ui::FileEvent::SaveAs(path)) => println!("Saving as: {:?}", path),
Ok(ui::FileEvent::Import(kind, path)) => match kind { Ok(ui::FileEvent::Import(kind, path)) => {
ui::ImportKind::Stl => { let model = match kind {
let model = import::load_stl(path); ui::ImportKind::Stl => import::load_stl(path),
let widgets = self.render_state.load_model(&model); ui::ImportKind::Gltf => import::load_gltf(path),
self.objects.extend(widgets.into_iter()); };
}
let widgets = self.render_state.load_model(&model);
self.objects.extend(widgets.into_iter());
}, },
Err(crossbeam_channel::TryRecvError::Empty) => break, Err(crossbeam_channel::TryRecvError::Empty) => break,
Err(crossbeam_channel::TryRecvError::Disconnected) => { Err(crossbeam_channel::TryRecvError::Disconnected) => {

View File

@ -5,16 +5,12 @@ pub struct Model {
pub struct Object { pub struct Object {
pub name: Option<String>, pub name: Option<String>,
pub mesh: Mesh, pub transform: Transform,
pub mesh: Option<Mesh>,
pub children: Vec<Object>, pub children: Vec<Object>,
} }
pub struct Mesh { pub struct Mesh {
pub transform: Transform,
pub data: MeshData,
}
pub struct MeshData {
pub vertices: Vec<BasicVertex>, pub vertices: Vec<BasicVertex>,
pub indices: Vec<u32>, pub indices: Vec<u32>,
} }

View File

@ -272,14 +272,19 @@ impl RenderState {
} }
pub fn load_object(&mut self, object: &model::Object) -> ObjectWidget { pub fn load_object(&mut self, object: &model::Object) -> ObjectWidget {
let mesh = self.load_mesh_data(&object.mesh.data); let mesh = object.mesh.as_ref().map(|mesh| cyborg::scene::Mesh {
mesh: self.load_mesh(mesh),
});
let entity = self.world.push(( let transform = cyborg::scene::Transform {
cyborg::scene::Mesh { mesh }, transform: object.transform.to_mat4(),
cyborg::scene::Transform { };
transform: object.mesh.transform.to_mat4(),
}, let entity = if let Some(mesh) = mesh {
)); self.world.push((mesh, transform))
} else {
self.world.push((transform, ()))
};
let mut children = Vec::new(); let mut children = Vec::new();
for child in object.children.iter() { for child in object.children.iter() {
@ -288,7 +293,7 @@ impl RenderState {
ObjectWidget { ObjectWidget {
name: object.name.clone(), name: object.name.clone(),
transform: object.mesh.transform.clone(), transform: object.transform.clone(),
entity, entity,
children, children,
dirty: false, dirty: false,
@ -296,7 +301,7 @@ impl RenderState {
} }
} }
pub fn load_mesh_data(&mut self, data: &model::MeshData) -> MeshHandle { pub fn load_mesh(&mut self, data: &model::Mesh) -> MeshHandle {
use cyborg::pass::{ use cyborg::pass::{
mesh::{MeshPass, Vertex}, mesh::{MeshPass, Vertex},
RenderPassBox, RenderPassBox,

View File

@ -13,6 +13,7 @@ pub enum FileEvent {
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ImportKind { pub enum ImportKind {
Stl, Stl,
Gltf,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -130,18 +131,12 @@ impl UserInterface {
ui.menu_button("Import...", |ui| { ui.menu_button("Import...", |ui| {
if ui.button("STL").clicked() { if ui.button("STL").clicked() {
ui.close_menu(); ui.close_menu();
self.on_import(ImportKind::Stl);
}
let import_kind = ImportKind::Stl; if ui.button("glTF").clicked() {
let file_sender = self.file_sender.to_owned(); ui.close_menu();
std::thread::spawn(move || { self.on_import(ImportKind::Gltf);
let dialog = rfd::FileDialog::new().add_filter("STL", &["stl"]);
if let Some(paths) = dialog.pick_files() {
for path in paths.iter() {
let event = FileEvent::Import(import_kind, path.into());
file_sender.send(event).unwrap();
}
}
});
} }
}); });
@ -216,6 +211,29 @@ impl UserInterface {
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!");
} }
pub fn on_import(&self, kind: ImportKind) {
let kind_name = match kind {
ImportKind::Stl => "STL",
ImportKind::Gltf => "glTF",
};
let extensions: &[&str] = match kind {
ImportKind::Stl => &["stl"],
ImportKind::Gltf => &["gltf", "glb"],
};
let file_sender = self.file_sender.to_owned();
std::thread::spawn(move || {
let dialog = rfd::FileDialog::new().add_filter(kind_name, extensions);
if let Some(paths) = dialog.pick_files() {
for path in paths.iter() {
let event = FileEvent::Import(kind, path.into());
file_sender.send(event).unwrap();
}
}
});
}
} }
pub struct ViewportWidget { pub struct ViewportWidget {