WIP scene hierarchy + glTF loading
This commit is contained in:
parent
c5eb2bff49
commit
3437d4d40f
|
@ -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"
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue