From e76ca1ec5048247a0e417d226c9094553f23052d Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 21 Sep 2023 23:11:09 -0400 Subject: [PATCH] Write a very experimental gltf loader --- lyra-resource/Cargo.toml | 3 + lyra-resource/src/loader/model.rs | 101 +++++++++++---- lyra-resource/src/model.rs | 25 +++- lyra-resource/test_files/gltf/test-blobs.glb | Bin 0 -> 1936 bytes .../test_files/gltf/test-embedded.gltf | 121 ++++++++++++++++++ lyra-resource/test_files/gltf/test-sep.bin | Bin 0 -> 840 bytes lyra-resource/test_files/gltf/test-sep.gltf | 121 ++++++++++++++++++ 7 files changed, 342 insertions(+), 29 deletions(-) create mode 100644 lyra-resource/test_files/gltf/test-blobs.glb create mode 100644 lyra-resource/test_files/gltf/test-embedded.gltf create mode 100644 lyra-resource/test_files/gltf/test-sep.bin create mode 100644 lyra-resource/test_files/gltf/test-sep.gltf diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index 6166616..43b0d9c 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -7,7 +7,10 @@ edition = "2021" [dependencies] anyhow = "1.0.75" +base64 = "0.21.4" +glam = "0.24.1" gltf = "1.3.0" image = "0.24.7" +percent-encoding = "2.3.0" thiserror = "1.0.48" uuid = { version = "1.4.1", features = ["v4"] } diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index 9b2bc92..56eb5d8 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -1,4 +1,9 @@ -use crate::{ResourceLoader, LoaderError, Mesh}; +use std::sync::Arc; + +use base64::Engine; +use glam::Vec3; + +use crate::{ResourceLoader, LoaderError, Mesh, Vertex, Model}; impl From for LoaderError { fn from(value: gltf::Error) -> Self { @@ -10,40 +15,56 @@ impl From for LoaderError { pub struct ModelLoader; impl ModelLoader { - fn process_node(&self, views: Vec>, node: gltf::Node<'_>) -> Vec { + fn parse_uri(uri: &str) -> Option> { + let uri = uri.strip_prefix("data")?; + let (mime, data) = uri.split_once(",")?; + + let (_mime, is_base64) = match mime.strip_suffix(";base64") { + Some(mime) => (mime, true), + None => (mime, false), + }; + + if is_base64 { + base64::engine::general_purpose::STANDARD.decode(data).ok() + } else { + Some(data.as_bytes().to_vec()) + } + } + + fn process_node(&self, buffers: &Vec>, node: gltf::Node<'_>) -> Vec { let mut meshes = vec![]; if let Some(mesh) = node.mesh() { for prim in mesh.primitives() { let pos_accessor = prim.get(&gltf::Semantic::Positions).unwrap(); - let mut new_mesh = Mesh::default(); - - assert_eq!(pos_accessor.dimensions(), gltf::accessor::Dimensions::Vec3); // TODO: dont do this - - let view = pos_accessor.view().unwrap(); // TODO: handle sparse Accessor - let stride = view.stride().unwrap_or(0); // if stride is None, its tightly packed - let offset = view.offset(); - let buffer = view.buffer(); - let buf_len = buffer.length(); - - let view = views.iter().next().unwrap(); - //gltf.views() + //assert_eq!(pos_accessor.dimensions(), gltf::accessor::Dimensions::Vec3); // TODO: dont do this - //vertices.data_type() == gltf:: - //new_mesh.vertices = vertices.; + let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice())); + let pos: Vec = reader.read_positions() + .unwrap() + .map(|pos| Vec3::new(pos[0], pos[1], pos[2])) + .collect(); - if let Some(indicies) = prim.indices() { + let indices: Option> = reader.read_indices() + .map(|i| match i { + gltf::mesh::util::ReadIndices::U8(i) => i.map(|i| i as u32).collect(), + gltf::mesh::util::ReadIndices::U16(i) => i.map(|i| i as u32).collect(), + gltf::mesh::util::ReadIndices::U32(i) => i.collect(), + }); - } + let mut new_mesh = Mesh::default(); + new_mesh.vertices = pos.into_iter().map(|p| Vertex::new(p, glam::Vec2::new(0.0, 0.0))).collect(); + new_mesh.indices = indices; + + meshes.push(new_mesh); } - //meshes.push(mesh); } for child in node.children() { - let mut child_meshes = self.process_node(node); + let mut child_meshes = self.process_node(buffers, child); meshes.append(&mut child_meshes); } - todo!() + meshes } } @@ -62,14 +83,44 @@ impl ResourceLoader for ModelLoader { let gltf = gltf::Gltf::open(path)?; - let buffers: Vec> = gltf.buffers().collect(); + //let buffers: Vec> = gltf.buffers().collect(); + + let buffers: Vec> = gltf.buffers().map(|b| match b.source() { + gltf::buffer::Source::Bin => gltf.blob.as_deref().map(|v| v.to_vec()), + gltf::buffer::Source::Uri(uri) => ModelLoader::parse_uri(uri), + }).flatten().collect(); // TODO: Read in multiple scenes let scene = gltf.scenes().next().unwrap(); - for node in scene.nodes() { - } + let meshes: Vec = scene.nodes() + .map(|node| self.process_node(&buffers, node)) + .flatten().collect(); - todo!() + Ok(Arc::new(Model::new(meshes))) + } +} + +#[cfg(test)] +mod tests { + use crate::ResourceLoader; + use super::*; + + fn test_file_path(path: &str) -> String { + let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + format!("{manifest}/test_files/gltf/{path}") + } + + #[test] + fn test_loading() { + let path = test_file_path("test-embedded.gltf"); + + let loader = ModelLoader::default(); + let model = loader.load(&path).unwrap(); + let model = Arc::downcast::(model.as_arc_any()).unwrap(); + let mesh = &model.meshes[0]; + assert!(mesh.vertices.len() > 0); + assert!(mesh.indices.clone().unwrap().len() > 0); } } \ No newline at end of file diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index 1f8d40d..8684b61 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -2,17 +2,34 @@ #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct Vertex { - pub position: [f32; 3], - pub tex_coords: [f32; 2] + pub position: glam::Vec3, + pub tex_coords: glam::Vec2 +} + +impl Vertex { + pub fn new(position: glam::Vec3, tex_coords: glam::Vec2) -> Self { + Self { + position, + tex_coords, + } + } } #[derive(Clone, Default)] pub struct Mesh { pub vertices: Vec, - pub indices: Option>, + pub indices: Option>, } pub struct Model { - pub mesh: Mesh, + 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/test_files/gltf/test-blobs.glb b/lyra-resource/test_files/gltf/test-blobs.glb new file mode 100644 index 0000000000000000000000000000000000000000..8f2d6fbe11ab6838d18ca2ca9921d34e589cb353 GIT binary patch literal 1936 zcmb7ES#R1v5S|=uk~Te(Uax4MU1}RbfQQnACMs=$D?m}CC~AyZh$XP2wMiQxB>#l| zqx7eBX6+3QVDyN!?SNPPzAtudM>ct+XN$UAhEX#0l3CIoqA{qf zB!Pm$riIFNrOR%NWeeDwZ2D>%vO6qPxC1wI&l95^hQe(RLpqMwPpesL)vUUlN^9q^ zYFUl1zjRhMG*-77`&)agY~s{iuj4utlA8<*W5#m8wu7d?7xDDk!)}+01DF4nppHK{ z^F8i`t&20fqYc9>Xw3149zF&$PiTMerVI@~)Uk_Kee}`L(B(Zc*_F?2awUavo2)R%ev_QOj! zo|e^__ziNVfyn+Q-!F>hZ V!4sH=W!#tHIjq481YAQ}g#R-Q*3tj~ literal 0 HcmV?d00001 diff --git a/lyra-resource/test_files/gltf/test-embedded.gltf b/lyra-resource/test_files/gltf/test-embedded.gltf new file mode 100644 index 0000000..5d42356 --- /dev/null +++ b/lyra-resource/test_files/gltf/test-embedded.gltf @@ -0,0 +1,121 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v3.6.5", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Cube" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"Cube", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "TEXCOORD_0":1, + "NORMAL":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":24, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":24, + "type":"VEC2" + }, + { + "bufferView":2, + "componentType":5126, + "count":24, + "type":"VEC3" + }, + { + "bufferView":3, + "componentType":5123, + "count":36, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":288, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":192, + "byteOffset":288, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":480, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":768, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":840, + "uri":"data:application/octet-stream;base64,AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AAAAPgAAAD8AAMA+AABAPwAAwD4AAEA/AAAgPwAAAAAAACA/AACAPwAAYD8AAIA+AAAAPgAAgD4AAMA+AAAAAAAAwD4AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAQAOABQAAQAUAAcACgAGABIACgASABYAFwATAAwAFwAMABAADwADAAkADwAJABUABQACAAgABQAIAAsAEQANAAAAEQAAAAQA" + } + ] +} diff --git a/lyra-resource/test_files/gltf/test-sep.bin b/lyra-resource/test_files/gltf/test-sep.bin new file mode 100644 index 0000000000000000000000000000000000000000..9322438cd45aec0d8a555a9fbe532cd726c3f498 GIT binary patch literal 840 zcma)&Sq{QL3`5P{vbSu%oP=_e9%XJ;jz->eB9&1=M9SE)g9%8XQQuuc<}r@u`ZwMT zGQM%oz#rqTfxo!>zpzR7cn)*UeFjJPy^&;{