diff --git a/.gitignore b/.gitignore index 9727f75d..a791b69c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ gltf-json/Cargo.lock gltf-utils/target/ gltf-utils/Cargo.lock *.bk -glTF-Sample-Models \ No newline at end of file +glTF-Sample-Models +glTF-Sample-Assets \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index cee56ff5..be0faebe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ KHR_materials_variants = ["gltf-json/KHR_materials_variants"] KHR_materials_volume = ["gltf-json/KHR_materials_volume"] KHR_materials_specular = ["gltf-json/KHR_materials_specular"] KHR_materials_emissive_strength = ["gltf-json/KHR_materials_emissive_strength"] +KHR_mesh_quantization = ["gltf-json/KHR_mesh_quantization"] EXT_texture_webp = ["gltf-json/EXT_texture_webp", "image/webp"] guess_mime_type = [] diff --git a/README.md b/README.md index 643d0b4b..6afbef02 100644 --- a/README.md +++ b/README.md @@ -97,8 +97,8 @@ cargo run --example gltf-tree path/to/asset.gltf ### Tests -Running tests locally requires to clone the [`glTF-Sample-Models`](https://github.com/KhronosGroup/glTF-Sample-Models) repository first. +Running tests locally requires to clone the [`glTF-Sample-Assets`](https://github.com/KhronosGroup/glTF-Sample-Assets) repository first. ```sh -git clone https://github.com/KhronosGroup/glTF-Sample-Models.git +git clone https://github.com/KhronosGroup/glTF-Sample-Assets.git ``` diff --git a/gltf-json/Cargo.toml b/gltf-json/Cargo.toml index d981e661..ec230217 100644 --- a/gltf-json/Cargo.toml +++ b/gltf-json/Cargo.toml @@ -30,4 +30,5 @@ KHR_materials_variants = [] KHR_materials_volume = [] KHR_texture_transform = [] KHR_materials_emissive_strength = [] +KHR_mesh_quantization = [] EXT_texture_webp = [] diff --git a/gltf-json/src/extensions/mod.rs b/gltf-json/src/extensions/mod.rs index 3112da95..3ecd2f38 100644 --- a/gltf-json/src/extensions/mod.rs +++ b/gltf-json/src/extensions/mod.rs @@ -52,6 +52,8 @@ pub const ENABLED_EXTENSIONS: &[&str] = &[ "KHR_materials_ior", #[cfg(feature = "KHR_materials_emissive_strength")] "KHR_materials_emissive_strength", + #[cfg(feature = "KHR_mesh_quantization")] + "KHR_mesh_quantization", // Allowlisted texture extensions. Processing is delegated to the user. #[cfg(feature = "allow_empty_texture")] "KHR_texture_basisu", @@ -70,5 +72,6 @@ pub const SUPPORTED_EXTENSIONS: &[&str] = &[ "KHR_materials_transmission", "KHR_materials_ior", "KHR_materials_emissive_strength", + "KHR_mesh_quantization", "EXT_texture_webp", ]; diff --git a/gltf-json/src/root.rs b/gltf-json/src/root.rs index 493271de..777c18be 100644 --- a/gltf-json/src/root.rs +++ b/gltf-json/src/root.rs @@ -466,7 +466,7 @@ mod tests { }; let mut errors = Vec::new(); - root.validate(&root, Path::new, &mut |path, error| { + root.validate(&root, Path::new, &mut |path: &dyn Fn() -> Path, error| { errors.push((path(), error)); }); @@ -486,17 +486,25 @@ mod tests { assert_eq!(*error, Error::Unsupported); } - root.extensions_required = vec!["KHR_mesh_quantization".to_owned()]; - errors.clear(); - root.validate(&root, Path::new, &mut |path, error| { - errors.push((path(), error)); - }); - assert_eq!(1, errors.len()); - let (path, error) = errors.get(0).unwrap(); - assert_eq!( - path.as_str(), - "extensionsRequired[0] = \"KHR_mesh_quantization\"" - ); - assert_eq!(*error, Error::Unsupported); + #[cfg(feature = "KHR_mesh_quantization")] + { + assert!(errors.is_empty()); + } + + #[cfg(not(feature = "KHR_mesh_quantization"))] + { + root.extensions_required = vec!["KHR_mesh_quantization".to_owned()]; + errors.clear(); + root.validate(&root, Path::new, &mut |path, error| { + errors.push((path(), error)); + }); + assert_eq!(1, errors.len()); + let (path, error) = errors.get(0).unwrap(); + assert_eq!( + path.as_str(), + "extensionsRequired[0] = \"KHR_mesh_quantization\"" + ); + assert_eq!(*error, Error::Unsupported); + } } } diff --git a/gltf-json/src/texture.rs b/gltf-json/src/texture.rs index 01aea1c3..b96e2729 100644 --- a/gltf-json/src/texture.rs +++ b/gltf-json/src/texture.rs @@ -1,4 +1,3 @@ -use crate::extensions::texture; use crate::validation::{Checked, Validate}; use crate::{extensions, image, Extras, Index}; use gltf_derive::Validate; diff --git a/src/mesh/mod.rs b/src/mesh/mod.rs index bbf6b2ff..5e70e4da 100644 --- a/src/mesh/mod.rs +++ b/src/mesh/mod.rs @@ -34,10 +34,17 @@ //! for primitive in mesh.primitives() { //! println!("- Primitive #{}", primitive.index()); //! let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); -//! if let Some(iter) = reader.read_positions() { -//! for vertex_position in iter { -//! println!("{:?}", vertex_position); -//! } +//! +//! #[cfg(not(feature="KHR_mesh_quantization"))] +//! let positions = reader.read_positions(); +//! #[cfg(feature="KHR_mesh_quantization")] +//! let positions = match reader.read_positions() { +//! Some(gltf::mesh::util::ReadPositions::F32(iter)) => iter, +//! _ => unreachable!(), +//! }; +//! +//! for vertex_position in positions { +//! println!("{:?}", vertex_position); //! } //! } //! } @@ -336,21 +343,84 @@ where pub fn read_positions(&self) -> Option> { self.primitive .get(&Semantic::Positions) - .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) + .and_then(|accessor| { + #[cfg(feature = "KHR_mesh_quantization")] + match accessor.data_type() { + json::accessor::ComponentType::I8 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadPositions::I8(iter)) + } + json::accessor::ComponentType::U8 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadPositions::U8(iter)) + } + json::accessor::ComponentType::I16 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadPositions::I16(iter)) + } + json::accessor::ComponentType::U16 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadPositions::U16(iter)) + } + json::accessor::ComponentType::F32 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadPositions::F32(iter)) + } + _ => None, + } + #[cfg(not(feature = "KHR_mesh_quantization"))] + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + }) } /// Visits the vertex normals of a primitive. pub fn read_normals(&self) -> Option> { - self.primitive - .get(&Semantic::Normals) - .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) + self.primitive.get(&Semantic::Normals).and_then(|accessor| { + #[cfg(feature = "KHR_mesh_quantization")] + match accessor.data_type() { + json::accessor::ComponentType::I8 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadNormals::I8(iter)) + } + json::accessor::ComponentType::I16 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadNormals::I16(iter)) + } + json::accessor::ComponentType::F32 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadNormals::F32(iter)) + } + _ => None, + } + #[cfg(not(feature = "KHR_mesh_quantization"))] + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + }) } /// Visits the vertex tangents of a primitive. pub fn read_tangents(&self) -> Option> { self.primitive .get(&Semantic::Tangents) - .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) + .and_then(|accessor| { + #[cfg(feature = "KHR_mesh_quantization")] + match accessor.data_type() { + json::accessor::ComponentType::I8 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadTangents::I8(iter)) + } + json::accessor::ComponentType::I16 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadTangents::I16(iter)) + } + json::accessor::ComponentType::F32 => { + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + .map(|iter| util::ReadTangents::F32(iter)) + } + _ => None, + } + #[cfg(not(feature = "KHR_mesh_quantization"))] + accessor::Iter::new(accessor, self.get_buffer_data.clone()) + }) } /// Visits the vertex colors of a primitive. diff --git a/src/mesh/util/mod.rs b/src/mesh/util/mod.rs index 206373f5..ee7c7d98 100644 --- a/src/mesh/util/mod.rs +++ b/src/mesh/util/mod.rs @@ -19,15 +19,70 @@ use crate::accessor::Iter; use crate::Buffer; /// XYZ vertex positions of type `[f32; 3]`. +#[cfg(not(feature = "KHR_mesh_quantization"))] pub type ReadPositions<'a> = Iter<'a, [f32; 3]>; +/// XYZ vertex positions as potentially quantized data +#[cfg(feature = "KHR_mesh_quantization")] +pub enum ReadPositions<'a> { + /// Position data of type I8 + I8(Iter<'a, [i8; 3]>), + /// Position data of type U8 + U8(Iter<'a, [u8; 3]>), + /// Position data of type I16 + I16(Iter<'a, [i16; 3]>), + /// Position data of type U16 + U16(Iter<'a, [u16; 3]>), + /// Position data of type F32 + F32(Iter<'a, [f32; 3]>), +} + +#[cfg(feature = "KHR_mesh_quantization")] +impl<'a> ReadPositions<'a> { + /// Returns size hint of internal iterator + pub fn size_hint(&self) -> (usize, Option) { + match self { + Self::I8(iter) => iter.size_hint(), + Self::U8(iter) => iter.size_hint(), + Self::I16(iter) => iter.size_hint(), + Self::U16(iter) => iter.size_hint(), + Self::F32(iter) => iter.size_hint(), + } + } +} + /// XYZ vertex normals of type `[f32; 3]`. +#[cfg(not(feature = "KHR_mesh_quantization"))] pub type ReadNormals<'a> = Iter<'a, [f32; 3]>; +/// XYZ vertex normals as potentially quantized data +#[cfg(feature = "KHR_mesh_quantization")] +pub enum ReadNormals<'a> { + /// Position data of type I8 + I8(Iter<'a, [i8; 3]>), + /// Position data of type I16 + I16(Iter<'a, [i16; 3]>), + /// Position data of type F32 + F32(Iter<'a, [f32; 3]>), +} + /// XYZW vertex tangents of type `[f32; 4]` where the `w` component is a /// sign value (-1 or +1) indicating the handedness of the tangent basis. +#[cfg(not(feature = "KHR_mesh_quantization"))] pub type ReadTangents<'a> = Iter<'a, [f32; 4]>; +/// XYZW vertex tangents of potentially quantized data where the `w` component is a +/// sign value (-1 or +1) indicating the handedness of the tangent basis. +#[cfg(feature = "KHR_mesh_quantization")] +pub enum ReadTangents<'a> { + /// Position data of type I8 + I8(Iter<'a, [i8; 3]>), + /// Position data of type I16 + I16(Iter<'a, [i16; 3]>), + /// Position data of type F32 + F32(Iter<'a, [f32; 3]>), +} + /// XYZ vertex position displacements of type `[f32; 3]`. pub type ReadPositionDisplacements<'a> = Iter<'a, [f32; 3]>; diff --git a/tests/import_sample_models.rs b/tests/import_sample_models.rs index ead823eb..12cd58b6 100644 --- a/tests/import_sample_models.rs +++ b/tests/import_sample_models.rs @@ -15,7 +15,9 @@ fn check_import_result( Err(gltf::Error::Validation(errors)) => { assert!(errors .iter() - .all(|(_path, error)| *error == Error::Unsupported)); + .all(|(_path, error)| *error == Error::Unsupported), + "errors: {errors:?}", + ); println!("skipped"); } Err(otherwise) => { diff --git a/tests/test_wrapper.rs b/tests/test_wrapper.rs index 21c6b14b..84cbb0ec 100644 --- a/tests/test_wrapper.rs +++ b/tests/test_wrapper.rs @@ -1,6 +1,8 @@ use std::io::Read; use std::{fs, io}; +#[cfg(feature="KHR_mesh_quantization")] +use gltf::mesh::util::ReadPositions; use gltf::mesh::Bounds; #[test] @@ -37,8 +39,16 @@ fn test_sparse_accessor_with_base_buffer_view_yield_exact_size_hints() { let primitive = mesh.primitives().next().unwrap(); let reader = primitive .reader(|buffer: gltf::Buffer| buffers.get(buffer.index()).map(|data| &data.0[..])); + + #[cfg(not(feature = "KHR_mesh_quantization"))] let mut positions = reader.read_positions().unwrap(); + #[cfg(feature = "KHR_mesh_quantization")] + let mut positions = match reader.read_positions().unwrap() { + ReadPositions::F32(iter) => iter, + _ => unreachable!("Meshes in gltf sample repo should not use quantization"), + }; + const EXPECTED_POSITION_COUNT: usize = 14; for i in (0..=EXPECTED_POSITION_COUNT).rev() { assert_eq!(positions.size_hint(), (i, Some(i))); @@ -54,8 +64,17 @@ fn test_sparse_accessor_with_base_buffer_view_yield_all_values() { let primitive = mesh.primitives().next().unwrap(); let reader = primitive .reader(|buffer: gltf::Buffer| buffers.get(buffer.index()).map(|data| &data.0[..])); + + #[cfg(not(feature = "KHR_mesh_quantization"))] let positions: Vec<[f32; 3]> = reader.read_positions().unwrap().collect::>(); + #[cfg(feature = "KHR_mesh_quantization")] + let positions = match reader.read_positions().unwrap() { + ReadPositions::F32(iter) => iter, + _ => unreachable!("Meshes in gltf sample repo should not use quantization"), + } + .collect::>(); + const EXPECTED_POSITIONS: [[f32; 3]; 14] = [ [0.0, 0.0, 0.0], [1.0, 0.0, 0.0],