resource: improve gltf loader to show scene hierarchy and node local transform
This commit is contained in:
parent
ad40621f7c
commit
556b603f83
|
@ -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"
|
|
@ -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();
|
||||
|
||||
if let Some(mesh) = node.mesh() {
|
||||
for prim in mesh.primitives() {
|
||||
let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice()));
|
||||
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();
|
||||
|
||||
for prim in mesh.primitives() {
|
||||
let reader = prim.reader(|buf| Some(ctx.buffers[buf.index()].as_slice()));
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
for child in node.children() {
|
||||
let mut child_meshes = ModelLoader::process_node(buffers, materials, child);
|
||||
meshes.append(&mut child_meshes);
|
||||
let handle = ResHandle::with_data("", new_mesh);
|
||||
ctx.resource_manager.store_uuid(handle.clone());
|
||||
node.mesh = Some(handle);
|
||||
}
|
||||
|
||||
meshes
|
||||
for child in gnode.children() {
|
||||
let mut cmesh = ModelLoader::process_node(ctx, materials, child);
|
||||
node.children.push(cmesh);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -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)]
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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::*;
|
|
@ -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>,
|
||||
}
|
|
@ -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::*;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
pub mod image;
|
||||
pub mod model;
|
||||
|
||||
use std::{io, sync::Arc, path::Path, ffi::OsStr};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue