Update the gltf loader to support multiple meshes in one file

This commit is contained in:
reo 2025-12-31 22:34:05 +03:00
parent 71e991db77
commit 0f2209a5d5
3 changed files with 115 additions and 100 deletions

View file

@ -12,7 +12,7 @@ use raidillon_assets::model_manager::ModelID;
/// Glium platform asset manager implementation. /// Glium platform asset manager implementation.
pub struct GliumAssetManager { pub struct GliumAssetManager {
pub models: HashMap<ModelID, Model>, pub models: HashMap<ModelID, Vec<Model>>,
facade: Box<dyn Facade>, facade: Box<dyn Facade>,
} }
@ -28,8 +28,8 @@ impl GliumAssetManager {
impl ModelManager for GliumAssetManager { impl ModelManager for GliumAssetManager {
fn load_gltf(&mut self, id: ModelID, path: &Path) { fn load_gltf(&mut self, id: ModelID, path: &Path) {
let model = load_gltf(path, self.facade.as_ref()).unwrap(); let models = load_gltf(path, self.facade.as_ref()).unwrap();
self.models.insert(id, model); self.models.insert(id, models);
} }
fn unload_model(&mut self, id: ModelID) { fn unload_model(&mut self, id: ModelID) {
@ -48,6 +48,6 @@ impl ModelManager for GliumAssetManager {
// } // }
fn get_model(&self, id: &ModelID) -> Option<&dyn Any> { fn get_model(&self, id: &ModelID) -> Option<&dyn Any> {
self.models.get(id).map(|model| model as &dyn Any) self.models.get(id).map(|models| models as &dyn Any)
} }
} }

View file

@ -1,4 +1,4 @@
use anyhow::{Context, Result}; use anyhow::{bail, Context, Result};
use glium::{backend::Facade, IndexBuffer, VertexBuffer}; use glium::{backend::Facade, IndexBuffer, VertexBuffer};
use glium::index::PrimitiveType; use glium::index::PrimitiveType;
use std::{fmt::Debug, path::Path}; use std::{fmt::Debug, path::Path};
@ -8,31 +8,16 @@ use glium::uniforms::{SamplerWrapFunction, MinifySamplerFilter, MagnifySamplerFi
use gltf::image::Format as GltfFormat; use gltf::image::Format as GltfFormat;
use glam::Vec2; use glam::Vec2;
/// Load a glTF 2.0 file from disk and upload the first primitive to the GPU. /// Load a glTF 2.0 file from disk and upload all primitives to the GPU.
pub fn load_gltf<P>(path: P, facade: &dyn Facade) -> Result<Model> ///
/// Returns one [`Model`] per glTF primitive (across all meshes).
pub fn load_gltf<P>(path: P, facade: &dyn Facade) -> Result<Vec<Model>>
where where
P: AsRef<Path> + Debug, P: AsRef<Path> + Debug,
{ {
// -- parse the asset & bring buffer blobs into memory -- // -- parse the asset & bring buffer blobs into memory --
let (doc, buffers, images) = gltf::import(path.as_ref()).context("failed to import glTF file")?; let (doc, buffers, images) = gltf::import(path.as_ref()).context("failed to import glTF file")?;
// -- grab the very first mesh / primitive --
let mesh = doc.meshes().next().context("glTF has no meshes")?;
let primitive = mesh.primitives().next().context("mesh has no primitives")?;
// ---------- MATERIAL ----------
let mut mat = Material::default();
let mat_idx = primitive.material().index().context("primitive has no material")?;
let material = doc.materials().nth(mat_idx).unwrap();
let pbr = material.pbr_metallic_roughness();
// Factors --------------------------------------------------
mat.base_color_factor = pbr.base_color_factor();
mat.metal_factor = pbr.metallic_factor();
mat.roughness_factor = pbr.roughness_factor();
mat.emissive_factor = material.emissive_factor();
// Helper to update sampler settings from glTF sampler // Helper to update sampler settings from glTF sampler
fn update_sampler(mat: &mut Material, t: &gltf::texture::Texture<'_>) { fn update_sampler(mat: &mut Material, t: &gltf::texture::Texture<'_>) {
let sampler_info = t.sampler(); let sampler_info = t.sampler();
@ -64,6 +49,21 @@ where
} }
} }
let mut out: Vec<Model> = Vec::new();
for mesh in doc.meshes() {
for primitive in mesh.primitives() {
// ---------- MATERIAL ----------
let mut mat = Material::default();
let material = primitive.material();
let pbr = material.pbr_metallic_roughness();
// Factors --------------------------------------------------
mat.base_color_factor = pbr.base_color_factor();
mat.metal_factor = pbr.metallic_factor();
mat.roughness_factor = pbr.roughness_factor();
mat.emissive_factor = material.emissive_factor();
// Base-color texture (sRGB) // Base-color texture (sRGB)
if let Some(info) = pbr.base_color_texture() { if let Some(info) = pbr.base_color_texture() {
update_sampler(&mut mat, &info.texture()); update_sampler(&mut mat, &info.texture());
@ -79,27 +79,27 @@ where
} }
// Normal map (linear) // Normal map (linear)
if let Some(info) = primitive.material().normal_texture() { if let Some(info) = material.normal_texture() {
update_sampler(&mut mat, &info.texture()); update_sampler(&mut mat, &info.texture());
let view = info.texture().source().index(); let view = info.texture().source().index();
mat.normal = Some(glium_linear_texture(facade, &images[view])?); mat.normal = Some(glium_linear_texture(facade, &images[view])?);
} }
// Occlusion (linear) // Occlusion (linear)
if let Some(info) = primitive.material().occlusion_texture() { if let Some(info) = material.occlusion_texture() {
update_sampler(&mut mat, &info.texture()); update_sampler(&mut mat, &info.texture());
let view = info.texture().source().index(); let view = info.texture().source().index();
mat.occlusion = Some(glium_linear_texture(facade, &images[view])?); mat.occlusion = Some(glium_linear_texture(facade, &images[view])?);
} }
// Emissive (sRGB) // Emissive (sRGB)
if let Some(info) = primitive.material().emissive_texture() { if let Some(info) = material.emissive_texture() {
update_sampler(&mut mat, &info.texture()); update_sampler(&mut mat, &info.texture());
let view = info.texture().source().index(); let view = info.texture().source().index();
mat.emissive = Some(glium_srgb_texture(facade, &images[view])?); mat.emissive = Some(glium_srgb_texture(facade, &images[view])?);
} }
// KHR_texture_transform // KHR_texture_transform (base color only, for now)
if let Some(tex) = pbr.base_color_texture() { if let Some(tex) = pbr.base_color_texture() {
if let Some(xform) = tex.texture_transform() { if let Some(xform) = tex.texture_transform() {
mat.uv_offset = Vec2::new(xform.offset()[0], xform.offset()[1]); mat.uv_offset = Vec2::new(xform.offset()[0], xform.offset()[1]);
@ -112,16 +112,29 @@ where
let positions: Vec<[f32; 3]> = reader.read_positions().context("missing POSITION")?.collect(); let positions: Vec<[f32; 3]> = reader.read_positions().context("missing POSITION")?.collect();
let normals: Vec<[f32; 3]> = reader.read_normals().context("missing NORMAL")?.collect(); let normals: Vec<[f32; 3]> = reader.read_normals().context("missing NORMAL")?.collect();
let tex_coords: Vec<[f32; 2]> = reader.read_tex_coords(0).map(|tc| tc.into_f32().collect()).unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]); let tex_coords: Vec<[f32; 2]> = reader
.read_tex_coords(0)
.map(|tc| tc.into_f32().collect())
.unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
let indices: Vec<u32> = reader.read_indices().context("missing indices")?.into_u32().collect(); let indices: Vec<u32> = reader.read_indices().context("missing indices")?.into_u32().collect();
// Interleave // Interleave
let vertices: Vec<Vertex> = (0..positions.len()).map(|i| Vertex { position: positions[i], normal: normals[i], tex_coords: tex_coords[i] }).collect(); let vertices: Vec<Vertex> = (0..positions.len())
.map(|i| Vertex { position: positions[i], normal: normals[i], tex_coords: tex_coords[i] })
.collect();
let vbuf = VertexBuffer::immutable(facade, &vertices)?; let vbuf = VertexBuffer::immutable(facade, &vertices)?;
let ibuf = IndexBuffer ::immutable(facade, PrimitiveType::TrianglesList, &indices)?; let ibuf = IndexBuffer::immutable(facade, PrimitiveType::TrianglesList, &indices)?;
Ok(Model { mesh: Mesh { vbuf, ibuf }, material: mat }) out.push(Model { mesh: Mesh { vbuf, ibuf }, material: mat });
}
}
if out.is_empty() {
bail!("glTF has no mesh primitives");
}
Ok(out)
} }
/// Linear-space texture (RGBA8) from glTF image data. /// Linear-space texture (RGBA8) from glTF image data.

View file

@ -73,11 +73,12 @@ impl RenderingSystem for BasicMeshRenderingSystem {
_ => continue, _ => continue,
}; };
let model = match model.downcast_ref::<Model>() { let models = match model.downcast_ref::<Vec<Model>>() {
Some(model) => model, Some(models) => models,
None => continue, None => continue,
}; };
for model in models {
let mesh = &model.mesh; let mesh = &model.mesh;
let mat = &model.material; let mat = &model.material;
@ -110,4 +111,5 @@ impl RenderingSystem for BasicMeshRenderingSystem {
).unwrap(); ).unwrap();
} }
} }
}
} }