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-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"
|
||||||
|
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue