refactor: change mesh load pipeline
These changes allow mesh loading to conform even better to the file format, meaning that loading should be more natural and performant.
This commit is contained in:
1 parent
406b0f9174
commit
26dc75cd38
6 files changed
+154
-80
No files matched your search
+10
-12
@@ -1,5 +1,3 @@
|
|||||||
use crate::assets::loaders::mat_loader::MaterialLoader;
|
|
||||||
use crate::assets::loaders::mesh_loader::MeshLoader;
|
|
||||||
use crate::common::material::{Material, MaterialRegistry};
|
use crate::common::material::{Material, MaterialRegistry};
|
||||||
use crate::common::mesh::{MeshHandle, MeshRegistry};
|
use crate::common::mesh::{MeshHandle, MeshRegistry};
|
||||||
use crate::common::scene::Scene;
|
use crate::common::scene::Scene;
|
||||||
@@ -11,6 +9,7 @@ use std::path::Path;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vulkano::buffer::Subbuffer;
|
use vulkano::buffer::Subbuffer;
|
||||||
use vulkano::memory::allocator::MemoryAllocator;
|
use vulkano::memory::allocator::MemoryAllocator;
|
||||||
|
use crate::assets::loaders::model_loader::{ModelLoadOptions, ModelLoader};
|
||||||
|
|
||||||
/// A manager that controls all assets used for display. It handles loading all meshes, materials,
|
/// A manager that controls all assets used for display. It handles loading all meshes, materials,
|
||||||
/// and textures for now.
|
/// and textures for now.
|
||||||
@@ -27,10 +26,6 @@ pub struct AssetManager {
|
|||||||
material_registry: MaterialRegistry,
|
material_registry: MaterialRegistry,
|
||||||
/// The asset manager's [`TextureRegistry`].
|
/// The asset manager's [`TextureRegistry`].
|
||||||
texture_registry: TextureRegistry,
|
texture_registry: TextureRegistry,
|
||||||
|
|
||||||
// Loaders
|
|
||||||
mesh_loader: MeshLoader,
|
|
||||||
material_loader: MaterialLoader,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssetManager {
|
impl AssetManager {
|
||||||
@@ -43,8 +38,6 @@ impl AssetManager {
|
|||||||
mesh_registry,
|
mesh_registry,
|
||||||
material_registry,
|
material_registry,
|
||||||
texture_registry: TextureRegistry::new(),
|
texture_registry: TextureRegistry::new(),
|
||||||
mesh_loader: MeshLoader::new(),
|
|
||||||
material_loader: MaterialLoader::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,8 +52,12 @@ impl AssetManager {
|
|||||||
// `MeshRegistry::append_mesh`), matching the index materials are registered under below.
|
// `MeshRegistry::append_mesh`), matching the index materials are registered under below.
|
||||||
// Not every mesh format has a material loader yet (e.g. `.obj`), so a failure here just
|
// Not every mesh format has a material loader yet (e.g. `.obj`), so a failure here just
|
||||||
// means no materials get registered for this mesh rather than failing the whole load.
|
// means no materials get registered for this mesh rather than failing the whole load.
|
||||||
match self.material_loader.load_materials(&path) {
|
let path = path.as_ref();
|
||||||
Ok(materials) => {
|
let mut loader = ModelLoader::new();
|
||||||
|
let model_info = loader.load_model(path, ModelLoadOptions::default())?;
|
||||||
|
|
||||||
|
match model_info.materials {
|
||||||
|
Some(materials) => {
|
||||||
for (index, info) in materials.into_iter().enumerate() {
|
for (index, info) in materials.into_iter().enumerate() {
|
||||||
let material_id = format!("{mesh_id}_mat_{index}");
|
let material_id = format!("{mesh_id}_mat_{index}");
|
||||||
self.material_registry
|
self.material_registry
|
||||||
@@ -68,10 +65,11 @@ impl AssetManager {
|
|||||||
println!("Loaded material {material_id}");
|
println!("Loaded material {material_id}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => println!("No materials loaded for mesh \"{mesh_id}\": {err}"),
|
None => println!("No materials loaded for mesh \"{mesh_id}\""),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mesh = self.mesh_loader.load_mesh(&path)?;
|
let mesh = model_info.mesh
|
||||||
|
.expect("Mesh was not loaded");
|
||||||
self.mesh_registry.register_mesh(mesh_id.clone(), mesh);
|
self.mesh_registry.register_mesh(mesh_id.clone(), mesh);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,19 +1,73 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use gltf::buffer::Data;
|
use gltf::buffer::Data as BufferData;
|
||||||
|
use gltf::image::Data as ImageData;
|
||||||
use gltf::image::Source;
|
use gltf::image::Source;
|
||||||
use gltf::Texture;
|
use gltf::{Document, Texture};
|
||||||
use nalgebra::{Matrix4, Vector4};
|
use nalgebra::{Matrix4, Vector4};
|
||||||
use crate::assets::loaders::common::{MaterialLoadInfo, MaterialUniforms, MeshLoadInfo, Submesh, TextureLoadInfo};
|
use crate::assets::loaders::common::{MaterialLoadInfo, MaterialUniforms, MeshLoadInfo, Submesh, TextureLoadInfo};
|
||||||
|
use crate::assets::loaders::model_loader::{LoadModel, ModelLoadInfo, ModelLoadOptions};
|
||||||
|
|
||||||
pub struct GltfLoader;
|
pub struct GltfLoader;
|
||||||
|
|
||||||
impl GltfLoader {
|
impl GltfLoader {
|
||||||
/// Traverses and loads all information from a GLTF/GLB node.
|
/// Returns a stable identifier for a texture's image, meant to be resolved by a texture
|
||||||
pub fn traverse_node(
|
/// loader later. External images are identified by their URI; images embedded in the file
|
||||||
|
/// itself (e.g. in a `.glb`) have no URI, so they're identified by their image index instead.
|
||||||
|
fn texture_id(texture: &Texture) -> String {
|
||||||
|
let image = texture.source();
|
||||||
|
match image.source() {
|
||||||
|
Source::Uri { uri, .. } => uri.to_string(),
|
||||||
|
Source::View { .. } => format!("embedded_image_{}", image.index()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads a mesh from the given [`Document`] and vector of [`Data`], both specific to the glTF
|
||||||
|
/// file format.
|
||||||
|
fn load_mesh(&self, document: &Document, buffers: &Vec<BufferData>) -> Result<MeshLoadInfo> {
|
||||||
|
let mut vertices = Vec::new();
|
||||||
|
let mut normals = Vec::new();
|
||||||
|
let mut uvs = Vec::new();
|
||||||
|
let mut indices = Vec::<u32>::new();
|
||||||
|
let mut submeshes = Vec::new();
|
||||||
|
|
||||||
|
let scenes = document.scenes();
|
||||||
|
|
||||||
|
// We need to iterate through scenes and apply the transforms from each scene to their
|
||||||
|
// corresponding vertices.
|
||||||
|
for scene in scenes {
|
||||||
|
for node in scene.nodes() {
|
||||||
|
// Read through the nodes and get all vertex information.
|
||||||
|
// Even if there are multiple scenes, we're treating the whole file as a single mesh.
|
||||||
|
Self::mesh_traverse_node(
|
||||||
|
&node,
|
||||||
|
&Matrix4::identity(),
|
||||||
|
&buffers,
|
||||||
|
&mut vertices,
|
||||||
|
&mut normals,
|
||||||
|
&mut uvs,
|
||||||
|
&mut indices,
|
||||||
|
&mut submeshes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let info = MeshLoadInfo {
|
||||||
|
vertices,
|
||||||
|
indices,
|
||||||
|
normals,
|
||||||
|
uvs,
|
||||||
|
submeshes,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverses and loads all mesh information from a glTF/glB node.
|
||||||
|
pub fn mesh_traverse_node(
|
||||||
node: &gltf::Node,
|
node: &gltf::Node,
|
||||||
parent_transform: &Matrix4<f32>,
|
parent_transform: &Matrix4<f32>,
|
||||||
buffers: &Vec<Data>,
|
buffers: &Vec<BufferData>,
|
||||||
vertices: &mut Vec<[f32; 3]>,
|
vertices: &mut Vec<[f32; 3]>,
|
||||||
normals: &mut Vec<[f32; 3]>,
|
normals: &mut Vec<[f32; 3]>,
|
||||||
uvs: &mut Vec<[f32; 3]>,
|
uvs: &mut Vec<[f32; 3]>,
|
||||||
@@ -73,7 +127,7 @@ impl GltfLoader {
|
|||||||
|
|
||||||
// Process all child nodes as well
|
// Process all child nodes as well
|
||||||
for child in node.children() {
|
for child in node.children() {
|
||||||
Self::traverse_node(
|
Self::mesh_traverse_node(
|
||||||
&child,
|
&child,
|
||||||
&full_transform,
|
&full_transform,
|
||||||
&buffers,
|
&buffers,
|
||||||
@@ -86,62 +140,12 @@ impl GltfLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a stable identifier for a texture's image, meant to be resolved by a texture
|
/// Loads all materials from the given [`Document`] and [`Data`] buffers. These are glTF/glB
|
||||||
/// loader later. External images are identified by their URI; images embedded in the file
|
/// specific.
|
||||||
/// itself (e.g. in a `.glb`) have no URI, so they're identified by their image index instead.
|
///
|
||||||
fn texture_id(texture: &Texture) -> String {
|
/// Note that this does not load any textures that are used with the materials, so this must
|
||||||
let image = texture.source();
|
/// be called in tandem with [`GltfLoader::load_textures`].
|
||||||
match image.source() {
|
fn load_materials(&self, document: &Document, buffers: &Vec<BufferData>) -> anyhow::Result<Vec<MaterialLoadInfo>> {
|
||||||
Source::Uri { uri, .. } => uri.to_string(),
|
|
||||||
Source::View { .. } => format!("embedded_image_{}", image.index()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_mesh(&self, path: &Path) -> Result<MeshLoadInfo> {
|
|
||||||
let (document, buffers, _) = gltf::import(path)?;
|
|
||||||
|
|
||||||
let mut vertices = Vec::new();
|
|
||||||
let mut normals = Vec::new();
|
|
||||||
let mut uvs = Vec::new();
|
|
||||||
let mut indices = Vec::<u32>::new();
|
|
||||||
let mut submeshes = Vec::new();
|
|
||||||
|
|
||||||
let scenes = document.scenes();
|
|
||||||
|
|
||||||
// We need to iterate through scenes and apply the transforms from each scene to their
|
|
||||||
// corresponding vertices.
|
|
||||||
println!("Parsing {} scenes from {:?}", scenes.len(), path);
|
|
||||||
for scene in scenes {
|
|
||||||
for node in scene.nodes() {
|
|
||||||
// Read through the nodes and get all vertex information.
|
|
||||||
// Even if there are multiple scenes, we're treating the whole file as a single mesh.
|
|
||||||
Self::traverse_node(
|
|
||||||
&node,
|
|
||||||
&Matrix4::identity(),
|
|
||||||
&buffers,
|
|
||||||
&mut vertices,
|
|
||||||
&mut normals,
|
|
||||||
&mut uvs,
|
|
||||||
&mut indices,
|
|
||||||
&mut submeshes,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let info = MeshLoadInfo {
|
|
||||||
vertices,
|
|
||||||
indices,
|
|
||||||
normals,
|
|
||||||
uvs,
|
|
||||||
submeshes,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(info)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_materials(&self, path: &Path) -> anyhow::Result<Vec<MaterialLoadInfo>> {
|
|
||||||
let (document, _buffers, _images) = gltf::import(path)?;
|
|
||||||
|
|
||||||
// Collected in document order, so a material's position here matches the
|
// Collected in document order, so a material's position here matches the
|
||||||
// `primitive.material().index()` that mesh loading recorded for its submeshes.
|
// `primitive.material().index()` that mesh loading recorded for its submeshes.
|
||||||
Ok(document
|
Ok(document
|
||||||
@@ -180,7 +184,42 @@ impl GltfLoader {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_texture(&self, path: &Path) -> Result<TextureLoadInfo> {
|
/// Loads all textures from the given [`Document`] and [`Data`] buffers.
|
||||||
|
///
|
||||||
|
/// Note that loading this does not load any materials that may use the textures, so this must
|
||||||
|
/// be called in tandem with [`GltfLoader::load_materials`].
|
||||||
|
fn load_textures(&self, document: &Document, buffers: &Vec<BufferData>, images: &Vec<ImageData>) -> Result<Vec<TextureLoadInfo>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoadModel for GltfLoader {
|
||||||
|
fn load_model(&self, path: &Path, options: ModelLoadOptions) -> Result<ModelLoadInfo> {
|
||||||
|
let mut info = ModelLoadInfo::default();
|
||||||
|
|
||||||
|
// First try to parse the file at the given path and get the information from it, then
|
||||||
|
// continue with processing.
|
||||||
|
let (document, buffers, images) = gltf::import(path)?;
|
||||||
|
|
||||||
|
if options.load_mesh {
|
||||||
|
// Load the mesh.
|
||||||
|
let mesh_info = self.load_mesh(&document, &buffers)?;
|
||||||
|
info.mesh = Some(mesh_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.load_materials {
|
||||||
|
// Load the materials.
|
||||||
|
let material_info = self.load_materials(&document, &buffers)?;
|
||||||
|
info.materials = Some(material_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Textures aren't currently supported.
|
||||||
|
// if options.load_textures {
|
||||||
|
// // Load the textures.
|
||||||
|
// let texture_info = self.load_textures(&document, &buffers, &images)?;
|
||||||
|
// info.textures = Some(texture_info)
|
||||||
|
// }
|
||||||
|
|
||||||
|
Ok(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use thiserror::Error;
|
||||||
use crate::assets::loaders::common::{MaterialLoadInfo, MeshLoadInfo, TextureLoadInfo};
|
use crate::assets::loaders::common::{MaterialLoadInfo, MeshLoadInfo, TextureLoadInfo};
|
||||||
|
use crate::assets::loaders::gltf_loader::GltfLoader;
|
||||||
|
|
||||||
/// A trait for loading a model from a given path.
|
/// A trait for loading a model from a given path.
|
||||||
pub trait LoadModel {
|
pub trait LoadModel {
|
||||||
@@ -10,7 +12,7 @@ pub trait LoadModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The options applicable to loading a model.
|
/// The options applicable to loading a model.
|
||||||
#[derive(Default, Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct ModelLoadOptions {
|
pub struct ModelLoadOptions {
|
||||||
/// Whether to load the full mesh.
|
/// Whether to load the full mesh.
|
||||||
pub load_mesh: bool,
|
pub load_mesh: bool,
|
||||||
@@ -20,8 +22,27 @@ pub struct ModelLoadOptions {
|
|||||||
pub load_textures: bool,
|
pub load_textures: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ModelLoadOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
load_mesh: true,
|
||||||
|
load_materials: true,
|
||||||
|
load_textures: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, Clone)]
|
||||||
|
pub enum ModelLoadError {
|
||||||
|
#[error("Extension was not found")]
|
||||||
|
ExtensionNotFound,
|
||||||
|
#[error("Extension {0} is not supported")]
|
||||||
|
ExtensionNotSupported(String),
|
||||||
|
}
|
||||||
|
|
||||||
/// Contains the information about the mesh, materials, and textures that this model has loaded.
|
/// Contains the information about the mesh, materials, and textures that this model has loaded.
|
||||||
/// Each type of information will only be loaded if the caller indicates that they should be.
|
/// Each type of information will only be loaded if the caller indicates that they should be.
|
||||||
|
#[derive(Default)]
|
||||||
pub struct ModelLoadInfo {
|
pub struct ModelLoadInfo {
|
||||||
/// The loaded mesh, if applicable.
|
/// The loaded mesh, if applicable.
|
||||||
pub mesh: Option<MeshLoadInfo>,
|
pub mesh: Option<MeshLoadInfo>,
|
||||||
@@ -41,9 +62,25 @@ impl ModelLoader {
|
|||||||
/// Creates a new `ModelLoader` instance.
|
/// Creates a new `ModelLoader` instance.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut loaders = HashMap::new();
|
let mut loaders = HashMap::new();
|
||||||
|
loaders.insert("gltf".into(), Box::new(GltfLoader) as Box<dyn LoadModel>);
|
||||||
|
loaders.insert("glb".into(), Box::new(GltfLoader));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
loaders,
|
loaders,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads a model from the given path. The available options are indicated by the [`ModelLoadOptions`]
|
||||||
|
/// struct.
|
||||||
|
pub fn load_model<P: AsRef<Path>>(&mut self, path: P, options: ModelLoadOptions) -> Result<ModelLoadInfo> {
|
||||||
|
// Determine which loader type to use.
|
||||||
|
let path = path.as_ref();
|
||||||
|
let ext = path.extension()
|
||||||
|
.ok_or(ModelLoadError::ExtensionNotFound)?
|
||||||
|
.to_str().unwrap();
|
||||||
|
|
||||||
|
self.loaders.get(ext)
|
||||||
|
.ok_or(ModelLoadError::ExtensionNotSupported(ext.into()))?
|
||||||
|
.load_model(path, options)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::assets::loaders::mesh_loader::{LoadMesh, MeshLoadInfo, Submesh};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use wavefront::Obj;
|
use wavefront::Obj;
|
||||||
|
use crate::assets::loaders::common::{MeshLoadInfo, Submesh};
|
||||||
|
|
||||||
pub struct ObjLoader;
|
pub struct ObjLoader;
|
||||||
|
|
||||||
impl LoadMesh for ObjLoader {
|
impl ObjLoader {
|
||||||
fn load_mesh(&self, path: &Path) -> anyhow::Result<MeshLoadInfo> {
|
fn load_mesh(&self, path: &Path) -> anyhow::Result<MeshLoadInfo> {
|
||||||
let obj_info = Obj::from_file(path).expect("Couldn't read wavefront file");
|
let obj_info = Obj::from_file(path).expect("Couldn't read wavefront file");
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::assets::loaders::mat_loader::MaterialLoadInfo;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use crate::assets::loaders::common::MaterialLoadInfo;
|
||||||
|
|
||||||
pub struct MaterialRegistry {
|
pub struct MaterialRegistry {
|
||||||
materials: HashMap<String, Material>
|
materials: HashMap<String, Material>
|
||||||
|
|||||||
+1
-1
@@ -1,9 +1,9 @@
|
|||||||
use crate::assets::loaders::mesh_loader::MeshLoadInfo;
|
|
||||||
use crate::gpu::vertex3::Vertex3;
|
use crate::gpu::vertex3::Vertex3;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
|
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
|
||||||
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter};
|
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter};
|
||||||
|
use crate::assets::loaders::common::MeshLoadInfo;
|
||||||
|
|
||||||
/// This struct is slightly misleading - it doesn't actually contain the mesh information, but
|
/// This struct is slightly misleading - it doesn't actually contain the mesh information, but
|
||||||
/// the locations of the mesh's information in the overall application's vertex buffer, index buffer,
|
/// the locations of the mesh's information in the overall application's vertex buffer, index buffer,
|
||||||
|
|||||||
Reference in new issue
Block a user