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

View File

@ -28,11 +28,115 @@ pub fn load_stl(path: std::path::PathBuf) -> model::Model {
name: name.clone(),
objects: vec![model::Object {
name,
mesh: model::Mesh {
transform: Default::default(),
data: model::MeshData { vertices, indices },
},
transform: Default::default(),
mesh: Some(model::Mesh { vertices, indices }),
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() {
Ok(ui::FileEvent::Save) => println!("Saving!"),
Ok(ui::FileEvent::SaveAs(path)) => println!("Saving as: {:?}", path),
Ok(ui::FileEvent::Import(kind, path)) => match kind {
ui::ImportKind::Stl => {
let model = import::load_stl(path);
let widgets = self.render_state.load_model(&model);
self.objects.extend(widgets.into_iter());
}
Ok(ui::FileEvent::Import(kind, path)) => {
let model = match kind {
ui::ImportKind::Stl => import::load_stl(path),
ui::ImportKind::Gltf => import::load_gltf(path),
};
let widgets = self.render_state.load_model(&model);
self.objects.extend(widgets.into_iter());
},
Err(crossbeam_channel::TryRecvError::Empty) => break,
Err(crossbeam_channel::TryRecvError::Disconnected) => {

View File

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

View File

@ -272,14 +272,19 @@ impl RenderState {
}
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((
cyborg::scene::Mesh { mesh },
cyborg::scene::Transform {
transform: object.mesh.transform.to_mat4(),
},
));
let transform = cyborg::scene::Transform {
transform: object.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();
for child in object.children.iter() {
@ -288,7 +293,7 @@ impl RenderState {
ObjectWidget {
name: object.name.clone(),
transform: object.mesh.transform.clone(),
transform: object.transform.clone(),
entity,
children,
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::{
mesh::{MeshPass, Vertex},
RenderPassBox,

View File

@ -13,6 +13,7 @@ pub enum FileEvent {
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ImportKind {
Stl,
Gltf,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -130,18 +131,12 @@ impl UserInterface {
ui.menu_button("Import...", |ui| {
if ui.button("STL").clicked() {
ui.close_menu();
self.on_import(ImportKind::Stl);
}
let import_kind = ImportKind::Stl;
let file_sender = self.file_sender.to_owned();
std::thread::spawn(move || {
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();
}
}
});
if ui.button("glTF").clicked() {
ui.close_menu();
self.on_import(ImportKind::Gltf);
}
});
@ -216,6 +211,29 @@ impl UserInterface {
pub fn ui_node_editor(&mut self, ui: &mut egui::Ui) {
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 {