From 8eb18bd5d89d3e07550a7506a5bd75b66806c3f2 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Mon, 4 Mar 2024 11:33:35 -0500 Subject: [PATCH] resource: improve gltf loader to show scene hierarchy and node local transform --- lyra-resource/Cargo.toml | 4 +- .../src/{loader/model.rs => gltf/loader.rs} | 100 +++++++++++------- lyra-resource/src/{ => gltf}/material.rs | 3 +- lyra-resource/src/{model.rs => gltf/mesh.rs} | 43 ++------ lyra-resource/src/gltf/mod.rs | 11 ++ lyra-resource/src/gltf/scene.rs | 21 ++++ lyra-resource/src/lib.rs | 6 +- lyra-resource/src/loader/mod.rs | 1 - lyra-resource/src/resource_manager.rs | 27 ++++- 9 files changed, 130 insertions(+), 86 deletions(-) rename lyra-resource/src/{loader/model.rs => gltf/loader.rs} (71%) rename lyra-resource/src/{ => gltf}/material.rs (99%) rename lyra-resource/src/{model.rs => gltf/mesh.rs} (78%) create mode 100644 lyra-resource/src/gltf/mod.rs create mode 100644 lyra-resource/src/gltf/scene.rs diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index 753caff..a79817c 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lyra-ecs = { path = "../lyra-ecs" } +lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } +lyra-math = { path = "../lyra-math" } anyhow = "1.0.75" base64 = "0.21.4" crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] } @@ -23,3 +24,4 @@ percent-encoding = "2.3.0" thiserror = "1.0.48" tracing = "0.1.37" uuid = { version = "1.4.1", features = ["v4"] } +instant = "0.1" \ No newline at end of file diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/gltf/loader.rs similarity index 71% rename from lyra-resource/src/loader/model.rs rename to lyra-resource/src/gltf/loader.rs index b5ed36d..2b26645 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/gltf/loader.rs @@ -1,8 +1,12 @@ use std::{sync::Arc, path::PathBuf}; +use glam::{Quat, Vec3}; +use instant::Instant; +use lyra_math::Transform; use thiserror::Error; -use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, ResHandle, Material, MeshIndices, ResourceManager, util}; +use crate::{gltf::GltfScene, util, LoaderError, ResHandle, ResourceLoader, ResourceManager}; +use super::{GltfNode, Material, Mesh, MeshIndices, MeshVertexAttribute, VertexAttributeData}; use tracing::debug; @@ -60,16 +64,23 @@ impl ModelLoader { } } */ - fn process_node(buffers: &Vec>, materials: &Vec, node: gltf::Node<'_>) -> Vec { - let mut meshes = vec![]; - //node.transform() + fn process_node(ctx: &mut GltfLoadContext, materials: &Vec, gnode: gltf::Node<'_>) -> GltfNode { + let mut node = GltfNode::default(); + + node.transform = { + let gt = gnode.transform(); + let (pos, rot, scale) = gt.decomposed(); + + Transform::new(Vec3::from(pos), Quat::from_array(rot), Vec3::from(scale)) + }; + node.name = gnode.name().map(str::to_string); + + if let Some(mesh) = gnode.mesh() { + let mut new_mesh = Mesh::default(); - if let Some(mesh) = node.mesh() { for prim in mesh.primitives() { - let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice())); + let reader = prim.reader(|buf| Some(ctx.buffers[buf.index()].as_slice())); - let mut new_mesh = Mesh::default(); - // read the positions if let Some(pos) = reader.read_positions() { if prim.mode() != gltf::mesh::Mode::Triangles { @@ -111,18 +122,20 @@ impl ModelLoader { } let mat = materials.get(prim.material().index().unwrap()).unwrap(); - new_mesh.set_material(mat.clone()); - - meshes.push(new_mesh); + new_mesh.material = Some(mat.clone()); } + + let handle = ResHandle::with_data("", new_mesh); + ctx.resource_manager.store_uuid(handle.clone()); + node.mesh = Some(handle); } - for child in node.children() { - let mut child_meshes = ModelLoader::process_node(buffers, materials, child); - meshes.append(&mut child_meshes); + for child in gnode.children() { + let mut cmesh = ModelLoader::process_node(ctx, materials, child); + node.children.push(cmesh); } - meshes + node } } @@ -147,18 +160,6 @@ impl ResourceLoader for ModelLoader { parent_path.pop(); let parent_path = parent_path.display().to_string(); - /* let (document, buffers, images) = gltf::import(&path)?; - let buffers: Vec> = buffers.into_iter().map(|b| b.0).collect(); - - let scene = document.scenes().next().unwrap(); - - let materials: Vec = document.materials() - .map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect(); - - let meshes: Vec = scene.nodes() - .map(|node| self.process_node(&buffers, &materials, node)) - .flatten().collect(); */ - let gltf = gltf::Gltf::open(path)?; let mut use_bin = false; @@ -183,15 +184,27 @@ impl ResourceLoader for ModelLoader { buffers: &buffers, }; + let start_inst = Instant::now(); let materials: Vec = gltf.materials() - .map(|mat| Material::from_gltf(&mut context, mat)).collect(); - - let meshes: Vec = scene.nodes() - .flat_map(|node| ModelLoader::process_node(&buffers, &materials, node)) + .map(|mat| Material::from_gltf(&mut context, mat)) .collect(); - debug!("Loaded {} meshes, and {} materials from '{}'", meshes.len(), materials.len(), path); + let mat_time = Instant::now() - start_inst; - Ok(Arc::new(ResHandle::with_data(path, Model::new(meshes)))) + let start_inst = Instant::now(); + let nodes: Vec = scene.nodes() + .map(|node| ModelLoader::process_node(&mut context, &materials, node)) + .collect(); + let node_time = Instant::now() - start_inst; + + debug!("Loaded {} nodes (in {}s), and {} materials (in {}s) from '{}'", nodes.len(), + node_time.as_secs_f32(), materials.len(), mat_time.as_secs_f32(), path); + + let scene = GltfScene { + nodes, + materials, + }; + + Ok(Arc::new(ResHandle::with_data(path, scene))) } #[allow(unused_variables)] @@ -217,16 +230,23 @@ mod tests { let mut manager = ResourceManager::new(); let loader = ModelLoader::default(); - let model = loader.load(&mut manager, &path).unwrap(); - let model = Arc::downcast::>(model.as_arc_any()).unwrap(); - let model = model.data_ref(); - assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh - let mesh = &model.meshes[0]; + let scene = loader.load(&mut manager, &path).unwrap(); + let scene = Arc::downcast::>(scene.as_arc_any()).unwrap(); + let scene = scene.data_ref(); + + assert_eq!(scene.nodes.len(), 1); + + let mnode = &scene.nodes[0]; + assert!(mnode.mesh.is_some()); + assert_eq!(mnode.transform, Transform::from_xyz(0.0, 0.0, 0.0)); + assert_eq!(mnode.children.len(), 0); + + let mesh = mnode.mesh.as_ref().unwrap(); + let mesh = mesh.data_ref(); assert!(mesh.position().unwrap().len() > 0); assert!(mesh.normals().unwrap().len() > 0); assert!(mesh.tex_coords().unwrap().len() > 0); assert!(mesh.indices.clone().unwrap().len() > 0); - assert!(mesh.material().base_color_texture.is_some()); - let _mesh_mat = mesh.material(); // inner panic if material was not loaded + assert!(mesh.material.as_ref().unwrap().base_color_texture.is_some()); } } \ No newline at end of file diff --git a/lyra-resource/src/material.rs b/lyra-resource/src/gltf/material.rs similarity index 99% rename from lyra-resource/src/material.rs rename to lyra-resource/src/gltf/material.rs index 1c3c357..649086d 100644 --- a/lyra-resource/src/material.rs +++ b/lyra-resource/src/gltf/material.rs @@ -1,6 +1,7 @@ use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}}; -use crate::{Texture, ResHandle, util, loader::model::GltfLoadContext}; +use crate::{Texture, ResHandle, util}; +use super::loader::GltfLoadContext; /// PBR metallic roughness #[derive(Clone, Debug, Default)] diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/gltf/mesh.rs similarity index 78% rename from lyra-resource/src/model.rs rename to lyra-resource/src/gltf/mesh.rs index acc8c2e..7121e9c 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/gltf/mesh.rs @@ -1,8 +1,11 @@ use std::collections::HashMap; -use crate::Material; +use lyra_math::Transform; + use crate::lyra_engine; +use super::Material; + #[repr(C)] #[derive(Clone, Debug, PartialEq)] pub enum MeshIndices { @@ -82,23 +85,11 @@ pub enum MeshVertexAttribute { Other(String), } -#[derive(Clone, lyra_ecs::Component)] +#[derive(Clone, Default, lyra_ecs::Component)] pub struct Mesh { - pub uuid: uuid::Uuid, pub attributes: HashMap, pub indices: Option, - material: Option, -} - -impl Default for Mesh { - fn default() -> Self { - Self { - uuid: uuid::Uuid::new_v4(), - attributes: Default::default(), - indices: Default::default(), - material: Default::default() - } - } + pub material: Option, } impl Mesh { @@ -127,26 +118,4 @@ impl Mesh { self.attributes.get(&MeshVertexAttribute::TexCoords) .map(|p| p.as_vec2()) } - - pub fn material(&self) -> Material { - self.material.clone().expect("This mesh is missing a material!") - } - - pub fn set_material(&mut self, val: Material) { - self.material = Some(val); - } -} - -#[derive(Clone, Default)] -pub struct Model { - pub meshes: Vec, - //pub material -} - -impl Model { - pub fn new(meshes: Vec) -> Self { - Self { - meshes, - } - } } \ No newline at end of file diff --git a/lyra-resource/src/gltf/mod.rs b/lyra-resource/src/gltf/mod.rs new file mode 100644 index 0000000..18772fc --- /dev/null +++ b/lyra-resource/src/gltf/mod.rs @@ -0,0 +1,11 @@ +pub mod loader; +pub use loader::*; + +pub mod material; +pub use material::*; + +pub mod mesh; +pub use mesh::*; + +pub mod scene; +pub use scene::*; \ No newline at end of file diff --git a/lyra-resource/src/gltf/scene.rs b/lyra-resource/src/gltf/scene.rs new file mode 100644 index 0000000..cd64df7 --- /dev/null +++ b/lyra-resource/src/gltf/scene.rs @@ -0,0 +1,21 @@ +use std::collections::HashMap; + +use lyra_ecs::Component; +use lyra_math::Transform; + +use super::{Material, Mesh}; +use crate::{lyra_engine, ResHandle}; + +#[derive(Clone, Default)] +pub struct GltfNode { + pub name: Option, + pub mesh: Option>, + pub transform: Transform, + pub children: Vec, +} + +#[derive(Clone)] +pub struct GltfScene { + pub nodes: Vec, + pub materials: Vec, +} \ No newline at end of file diff --git a/lyra-resource/src/lib.rs b/lyra-resource/src/lib.rs index 0427c2f..196e75d 100644 --- a/lyra-resource/src/lib.rs +++ b/lyra-resource/src/lib.rs @@ -10,11 +10,7 @@ pub use texture::*; pub mod loader; pub use loader::*; -pub mod model; -pub use model::*; - -pub mod material; -pub use material::*; +pub mod gltf; pub mod world_ext; pub use world_ext::*; diff --git a/lyra-resource/src/loader/mod.rs b/lyra-resource/src/loader/mod.rs index a4e9e64..5e2f0f6 100644 --- a/lyra-resource/src/loader/mod.rs +++ b/lyra-resource/src/loader/mod.rs @@ -1,5 +1,4 @@ pub mod image; -pub mod model; use std::{io, sync::Arc, path::Path, ffi::OsStr}; diff --git a/lyra-resource/src/resource_manager.rs b/lyra-resource/src/resource_manager.rs index dc24d33..970c629 100644 --- a/lyra-resource/src/resource_manager.rs +++ b/lyra-resource/src/resource_manager.rs @@ -6,7 +6,7 @@ use notify_debouncer_full::{DebouncedEvent, FileIdMap}; use thiserror::Error; use uuid::Uuid; -use crate::{loader::{image::ImageLoader, model::ModelLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState}; +use crate::{gltf::ModelLoader, loader::{image::ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState}; /// A trait for type erased storage of a resource. /// Implemented for [`ResHandle`] @@ -56,6 +56,7 @@ pub struct ResourceWatcher { pub struct ResourceManager { resources: HashMap>, + uuid_resources: HashMap>, loaders: Vec>, watchers: HashMap, } @@ -70,6 +71,7 @@ impl ResourceManager { pub fn new() -> Self { Self { resources: HashMap::new(), + uuid_resources: HashMap::new(), loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ], watchers: HashMap::new(), } @@ -148,6 +150,29 @@ impl ResourceManager { } } + /// Store a resource using its uuid. + /// + /// The resource cannot be requested with [`ResourceManager::request`], it can only be + /// retrieved with [`ResourceManager::request_uuid`]. + pub fn store_uuid(&mut self, res: ResHandle) { + self.uuid_resources.insert(res.uuid(), Arc::new(res)); + } + + /// Request a resource via its uuid. + /// + /// Returns `None` if the resource was not found. The resource must of had been + /// stored with [`ResourceManager::request`] to return `Some`. + pub fn request_uuid(&mut self, uuid: &Uuid) -> Option> { + match self.uuid_resources.get(uuid) { + Some(res) => { + let res = res.clone().as_arc_any(); + let res: Arc> = res.downcast::>().expect("Failure to downcast resource"); + Some(ResHandle::::clone(&res)) + }, + None => None, + } + } + /// Store bytes in the manager. If there is already an entry with the same identifier it will be updated. /// /// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic.