resource: improve gltf loader to show scene hierarchy and node local transform

This commit is contained in:
SeanOMik 2024-03-04 11:33:35 -05:00 committed by SeanOMik
parent ad40621f7c
commit 556b603f83
9 changed files with 130 additions and 86 deletions

View File

@ -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"

View File

@ -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<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> {
let mut meshes = vec![];
//node.transform()
fn process_node(ctx: &mut GltfLoadContext, materials: &Vec<Material>, 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<Vec<u8>> = buffers.into_iter().map(|b| b.0).collect();
let scene = document.scenes().next().unwrap();
let materials: Vec<Material> = document.materials()
.map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect();
let meshes: Vec<Mesh> = 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<Material> = gltf.materials()
.map(|mat| Material::from_gltf(&mut context, mat)).collect();
let meshes: Vec<Mesh> = 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<GltfNode> = 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::<ResHandle<Model>>(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::<ResHandle<GltfScene>>(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());
}
}

View File

@ -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)]

View File

@ -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<MeshVertexAttribute, VertexAttributeData>,
pub indices: Option<MeshIndices>,
material: Option<Material>,
}
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<Material>,
}
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<Mesh>,
//pub material
}
impl Model {
pub fn new(meshes: Vec<Mesh>) -> Self {
Self {
meshes,
}
}
}

View File

@ -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::*;

View File

@ -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<String>,
pub mesh: Option<ResHandle<Mesh>>,
pub transform: Transform,
pub children: Vec<GltfNode>,
}
#[derive(Clone)]
pub struct GltfScene {
pub nodes: Vec<GltfNode>,
pub materials: Vec<Material>,
}

View File

@ -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::*;

View File

@ -1,5 +1,4 @@
pub mod image;
pub mod model;
use std::{io, sync::Arc, path::Path, ffi::OsStr};

View File

@ -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<T>`]
@ -56,6 +56,7 @@ pub struct ResourceWatcher {
pub struct ResourceManager {
resources: HashMap<String, Arc<dyn ResourceStorage>>,
uuid_resources: HashMap<Uuid, Arc<dyn ResourceStorage>>,
loaders: Vec<Arc<dyn ResourceLoader>>,
watchers: HashMap<String, ResourceWatcher>,
}
@ -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<T: Send + Sync + 'static>(&mut self, res: ResHandle<T>) {
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<T: Send + Sync + 'static>(&mut self, uuid: &Uuid) -> Option<ResHandle<T>> {
match self.uuid_resources.get(uuid) {
Some(res) => {
let res = res.clone().as_arc_any();
let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
Some(ResHandle::<T>::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.