diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..3bd57472 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "glTF-Sample-Assets"] + path = glTF-Sample-Assets + url = https://github.com/KhronosGroup/glTF-Sample-Assets.git diff --git a/Cargo.toml b/Cargo.toml index a94a7add..5f767329 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ rust-version = "1.61" travis-ci = { repository = "gltf-rs/gltf" } [workspace] -members = ["gltf-derive", "gltf-json"] +members = ["gltf-derive"] [dev-dependencies] approx = "0.5" @@ -25,53 +25,30 @@ approx = "0.5" [dependencies] base64 = { optional = true, version = "0.13" } byteorder = "1.3" -gltf-json = { path = "gltf-json", version = "=1.4.1" } +gltf-derive = { path = "gltf-derive", version = "=1.4.1" } lazy_static = "1" urlencoding = { optional = true, version = "2.1" } +serde = "1.0" +serde_derive = "1.0" serde_json = { features = ["raw_value"], version = "1.0" } +serde_repr = "0.1" +serde_with = "3.6" -[dependencies.image] +[dependencies.image_crate] default-features = false +package = "image" features = ["jpeg", "png"] optional = true version = "0.25" [features] -default = ["import", "utils", "names"] -allow_empty_texture = ["gltf-json/allow_empty_texture"] -extensions = ["gltf-json/extensions"] -extras = ["gltf-json/extras"] -names = ["gltf-json/names"] +default = ["import", "utils"] +allow_empty_texture = [] +extensions = [] utils = [] -import = ["base64", "image", "urlencoding"] -KHR_lights_punctual = ["gltf-json/KHR_lights_punctual"] -KHR_materials_pbrSpecularGlossiness = ["gltf-json/KHR_materials_pbrSpecularGlossiness"] -KHR_materials_unlit = ["gltf-json/KHR_materials_unlit"] -KHR_texture_transform = ["gltf-json/KHR_texture_transform"] -KHR_materials_transmission = ["gltf-json/KHR_materials_transmission"] -KHR_materials_ior = ["gltf-json/KHR_materials_ior"] -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"] +import = ["base64", "image_crate", "urlencoding"] guess_mime_type = [] -[[example]] -name = "gltf-display" -path = "examples/display/main.rs" - -[[example]] -name = "gltf-export" -path = "examples/export/main.rs" - -[[example]] -name = "gltf-roundtrip" -path = "examples/roundtrip/main.rs" - -[[example]] -name = "gltf-tree" -path = "examples/tree/main.rs" - [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/README.md b/README.md index 643d0b4b..512d18ee 100644 --- a/README.md +++ b/README.md @@ -27,18 +27,6 @@ This crate is intended to load [glTF 2.0](https://www.khronos.org/gltf), a file See the [crate documentation](https://docs.rs/gltf) for example usage. -### Features - -#### Extras and names - -By default, `gltf` ignores all `extras` and `names` included with glTF assets. You can negate this by enabling the `extras` and `names` features, respectively. - -```toml -[dependencies.gltf] -version = "1.4" -features = ["extras", "names"] -``` - #### glTF extensions The following glTF extensions are supported by the crate: @@ -54,13 +42,6 @@ The following glTF extensions are supported by the crate: - `KHR_materials_ior` - `KHR_materials_emissive_strength ` -To use an extension, list its name in the `features` section. - -```toml -[dependencies.gltf] -features = ["KHR_materials_unlit"] -``` - ### Examples #### gltf-display diff --git a/examples/Box.glb b/examples/Box.glb deleted file mode 100644 index 95ec886b..00000000 Binary files a/examples/Box.glb and /dev/null differ diff --git a/examples/Lantern.gltf b/examples/Lantern.gltf deleted file mode 100644 index 74ef9165..00000000 --- a/examples/Lantern.gltf +++ /dev/null @@ -1,468 +0,0 @@ -{ - "accessors": [ - { - "bufferView": 0, - "componentType": 5126, - "count": 926, - "type": "VEC2", - "max": [ - 0.992169142, - -0.007998445 - ], - "min": [ - 0.0107366741, - -0.992534757 - ] - }, - { - "bufferView": 1, - "componentType": 5126, - "count": 926, - "type": "VEC3", - "max": [ - 1.0, - 1.0, - 1.0 - ], - "min": [ - -1.0, - -1.0, - -1.0 - ] - }, - { - "bufferView": 2, - "componentType": 5126, - "count": 926, - "type": "VEC4", - "max": [ - 1.0, - 0.9999658, - 1.0, - 1.0 - ], - "min": [ - -1.0, - -0.962171, - -1.0, - 1.0 - ] - }, - { - "bufferView": 3, - "componentType": 5126, - "count": 926, - "type": "VEC3", - "max": [ - 7.74559927, - 12.8321095, - 2.31570983 - ], - "min": [ - -7.74559927, - -12.8321095, - -2.31570983 - ] - }, - { - "bufferView": 4, - "componentType": 5123, - "count": 2616, - "type": "SCALAR", - "max": [ - 925 - ], - "min": [ - 0 - ] - }, - { - "bufferView": 5, - "componentType": 5126, - "count": 756, - "type": "VEC2", - "max": [ - 0.8007193, - -0.0581455827 - ], - "min": [ - 0.351246148, - -0.361011624 - ] - }, - { - "bufferView": 6, - "componentType": 5126, - "count": 756, - "type": "VEC3", - "max": [ - 1.0, - 1.0, - 0.999998 - ], - "min": [ - -1.0, - -1.0, - -0.999998 - ] - }, - { - "bufferView": 7, - "componentType": 5126, - "count": 756, - "type": "VEC4", - "max": [ - 0.9997526, - 1.0, - 0.999752462, - 1.0 - ], - "min": [ - -0.9995128, - -1.0, - -0.999752462, - 1.0 - ] - }, - { - "bufferView": 8, - "componentType": 5126, - "count": 756, - "type": "VEC3", - "max": [ - 0.129208073, - 0.6523504, - 0.129208073 - ], - "min": [ - -0.129208073, - -0.6523504, - -0.129208073 - ] - }, - { - "bufferView": 9, - "componentType": 5123, - "count": 3744, - "type": "SCALAR", - "max": [ - 755 - ], - "min": [ - 0 - ] - }, - { - "bufferView": 10, - "componentType": 5126, - "count": 2463, - "type": "VEC2", - "max": [ - 0.9923278, - -0.00728821754 - ], - "min": [ - 0.3673209, - -0.413352221 - ] - }, - { - "bufferView": 11, - "componentType": 5126, - "count": 2463, - "type": "VEC3", - "max": [ - 1.0, - 1.0, - 1.0 - ], - "min": [ - -1.0, - -1.0, - -1.0 - ] - }, - { - "bufferView": 12, - "componentType": 5126, - "count": 2463, - "type": "VEC4", - "max": [ - 1.0, - 1.0, - 1.0, - 1.0 - ], - "min": [ - -1.0, - -1.0, - -1.0, - -1.0 - ] - }, - { - "bufferView": 13, - "componentType": 5126, - "count": 2463, - "type": "VEC3", - "max": [ - 1.03408229, - 2.529281, - 1.03408468 - ], - "min": [ - -1.03408229, - -2.529281, - -1.03408468 - ] - }, - { - "bufferView": 14, - "componentType": 5123, - "count": 9822, - "type": "SCALAR", - "max": [ - 2462 - ], - "min": [ - 0 - ] - } - ], - "asset": { - "generator": "glTF Tools for Unity", - "version": "2.0" - }, - "bufferViews": [ - { - "buffer": 0, - "byteLength": 7408 - }, - { - "buffer": 0, - "byteOffset": 7408, - "byteLength": 11112 - }, - { - "buffer": 0, - "byteOffset": 18520, - "byteLength": 14816 - }, - { - "buffer": 0, - "byteOffset": 33336, - "byteLength": 11112 - }, - { - "buffer": 0, - "byteOffset": 44448, - "byteLength": 5232 - }, - { - "buffer": 0, - "byteOffset": 49680, - "byteLength": 6048 - }, - { - "buffer": 0, - "byteOffset": 55728, - "byteLength": 9072 - }, - { - "buffer": 0, - "byteOffset": 64800, - "byteLength": 12096 - }, - { - "buffer": 0, - "byteOffset": 76896, - "byteLength": 9072 - }, - { - "buffer": 0, - "byteOffset": 85968, - "byteLength": 7488 - }, - { - "buffer": 0, - "byteOffset": 93456, - "byteLength": 19704 - }, - { - "buffer": 0, - "byteOffset": 113160, - "byteLength": 29556 - }, - { - "buffer": 0, - "byteOffset": 142716, - "byteLength": 39408 - }, - { - "buffer": 0, - "byteOffset": 182124, - "byteLength": 29556 - }, - { - "buffer": 0, - "byteOffset": 211680, - "byteLength": 19644 - } - ], - "buffers": [ - { - "uri": "Lantern.bin", - "byteLength": 231324 - } - ], - "images": [ - { - "uri": "Lantern_baseColor.png" - }, - { - "uri": "Lantern_roughnessMetallic.png" - }, - { - "uri": "Lantern_normal.png" - }, - { - "uri": "Lantern_emissive.png" - } - ], - "meshes": [ - { - "primitives": [ - { - "attributes": { - "TEXCOORD_0": 0, - "NORMAL": 1, - "TANGENT": 2, - "POSITION": 3 - }, - "indices": 4, - "material": 0 - } - ], - "name": "LanternPole_Body" - }, - { - "primitives": [ - { - "attributes": { - "TEXCOORD_0": 5, - "NORMAL": 6, - "TANGENT": 7, - "POSITION": 8 - }, - "indices": 9, - "material": 0 - } - ], - "name": "LanternPole_Chain" - }, - { - "primitives": [ - { - "attributes": { - "TEXCOORD_0": 10, - "NORMAL": 11, - "TANGENT": 12, - "POSITION": 13 - }, - "indices": 14, - "material": 0 - } - ], - "name": "LanternPole_Lantern" - } - ], - "materials": [ - { - "pbrMetallicRoughness": { - "baseColorFactor": [ - 0.214041144, - 0.214041144, - 0.214041144, - 1.0 - ], - "baseColorTexture": { - "index": 0 - }, - "metallicRoughnessTexture": { - "index": 1 - } - }, - "normalTexture": { - "index": 2 - }, - "emissiveFactor": [ - 1.0, - 1.0, - 1.0 - ], - "emissiveTexture": { - "index": 3 - }, - "name": "LanternPost_Mat" - } - ], - "nodes": [ - { - "mesh": 0, - "translation": [ - -3.82315421, - 13.01603, - 0.0 - ], - "name": "LanternPole_Body" - }, - { - "mesh": 1, - "translation": [ - -9.582001, - 21.0378723, - 0.0 - ], - "name": "LanternPole_Chain" - }, - { - "mesh": 2, - "translation": [ - -9.582007, - 18.0091515, - 0.0 - ], - "name": "LanternPole_Lantern" - }, - { - "children": [ - 0, - 1, - 2 - ], - "name": "Lantern" - } - ], - "scene": 0, - "scenes": [ - { - "nodes": [ - 3 - ] - } - ], - "textures": [ - { - "source": 0 - }, - { - "source": 1 - }, - { - "source": 2 - }, - { - "source": 3 - } - ] -} \ No newline at end of file diff --git a/examples/display/main.rs b/examples/display/main.rs deleted file mode 100644 index 36ab2393..00000000 --- a/examples/display/main.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::{fs, io}; - -use std::boxed::Box; -use std::error::Error as StdError; - -fn run(path: &str) -> Result<(), Box> { - let file = fs::File::open(path)?; - let reader = io::BufReader::new(file); - let gltf = gltf::Gltf::from_reader(reader)?; - println!("{:#?}", gltf); - Ok(()) -} - -fn main() { - if let Some(path) = std::env::args().nth(1) { - run(&path).expect("runtime error"); - } else { - println!("usage: gltf-display "); - } -} diff --git a/examples/export/main.rs b/examples/export/main.rs index 98332c69..5e54769f 100644 --- a/examples/export/main.rs +++ b/examples/export/main.rs @@ -1,11 +1,8 @@ -use gltf_json as json; - -use std::{fs, mem}; - -use json::validation::Checked::Valid; -use json::validation::USize64; +use gltf::validation::USize64; +use gltf::Stub; use std::borrow::Cow; use std::io::Write; +use std::{fs, mem}; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] enum Output { @@ -72,96 +69,72 @@ fn export(output: Output) { let (min, max) = bounding_coords(&triangle_vertices); - let mut root = gltf_json::Root::default(); + let mut root = gltf::Root::default(); let buffer_length = triangle_vertices.len() * mem::size_of::(); - let buffer = root.push(json::Buffer { - byte_length: USize64::from(buffer_length), - extensions: Default::default(), - extras: Default::default(), - name: None, + let buffer = root.push(gltf::Buffer { + length: USize64::from(buffer_length), uri: if output == Output::Standard { Some("buffer0.bin".into()) } else { None }, + ..Stub::stub() }); - let buffer_view = root.push(json::buffer::View { + let buffer_view = root.push(gltf::buffer::View { buffer, - byte_length: USize64::from(buffer_length), - byte_offset: None, - byte_stride: Some(json::buffer::Stride(mem::size_of::())), - extensions: Default::default(), - extras: Default::default(), - name: None, - target: Some(Valid(json::buffer::Target::ArrayBuffer)), + length: USize64::from(buffer_length), + offset: USize64(0), + stride: Some(mem::size_of::()), + target: Some(gltf::buffer::Target::ArrayBuffer), + ..Stub::stub() }); - let positions = root.push(json::Accessor { + let positions = root.push(gltf::Accessor { buffer_view: Some(buffer_view), byte_offset: Some(USize64(0)), count: USize64::from(triangle_vertices.len()), - component_type: Valid(json::accessor::GenericComponentType( - json::accessor::ComponentType::F32, - )), - extensions: Default::default(), - extras: Default::default(), - type_: Valid(json::accessor::Type::Vec3), - min: Some(json::Value::from(Vec::from(min))), - max: Some(json::Value::from(Vec::from(max))), - name: None, + component_type: gltf::accessor::ComponentType::F32, + attribute_type: gltf::accessor::AttributeType::Vec3, + min: Some(gltf::Value::from(Vec::from(min))), + max: Some(gltf::Value::from(Vec::from(max))), normalized: false, - sparse: None, + ..Stub::stub() }); - let colors = root.push(json::Accessor { + let colors = root.push(gltf::Accessor { buffer_view: Some(buffer_view), byte_offset: Some(USize64::from(3 * mem::size_of::())), count: USize64::from(triangle_vertices.len()), - component_type: Valid(json::accessor::GenericComponentType( - json::accessor::ComponentType::F32, - )), - extensions: Default::default(), - extras: Default::default(), - type_: Valid(json::accessor::Type::Vec3), - min: None, - max: None, - name: None, + component_type: gltf::accessor::ComponentType::F32, + attribute_type: gltf::accessor::AttributeType::Vec3, normalized: false, - sparse: None, + ..Stub::stub() }); - let primitive = json::mesh::Primitive { - attributes: { - let mut map = std::collections::BTreeMap::new(); - map.insert(Valid(json::mesh::Semantic::Positions), positions); - map.insert(Valid(json::mesh::Semantic::Colors(0)), colors); - map - }, - extensions: Default::default(), - extras: Default::default(), + let primitive = gltf::mesh::Primitive { + attributes: [ + (gltf::mesh::Semantic::Positions, positions), + (gltf::mesh::Semantic::Colors(0), colors), + ] + .into(), indices: None, material: None, - mode: Valid(json::mesh::Mode::Triangles), - targets: None, + mode: gltf::mesh::Mode::Triangles, + ..Stub::stub() }; - let mesh = root.push(json::Mesh { - extensions: Default::default(), - extras: Default::default(), - name: None, + let mesh = root.push(gltf::Mesh { primitives: vec![primitive], - weights: None, + ..Stub::stub() }); - let node = root.push(json::Node { + let node = root.push(gltf::Node { mesh: Some(mesh), ..Default::default() }); - root.push(json::Scene { - extensions: Default::default(), - extras: Default::default(), - name: None, + root.push(gltf::Scene { nodes: vec![node], + ..Stub::stub() }); match output { @@ -169,14 +142,14 @@ fn export(output: Output) { let _ = fs::create_dir("triangle"); let writer = fs::File::create("triangle/triangle.gltf").expect("I/O error"); - json::serialize::to_writer_pretty(writer, &root).expect("Serialization error"); + serde_json::to_writer_pretty(writer, &root).expect("Serialization error"); let bin = to_padded_byte_vector(triangle_vertices); let mut writer = fs::File::create("triangle/buffer0.bin").expect("I/O error"); writer.write_all(&bin).expect("I/O error"); } Output::Binary => { - let json_string = json::serialize::to_string(&root).expect("Serialization error"); + let json_string = serde_json::to_string(&root).expect("Serialization error"); let mut json_offset = json_string.len(); align_to_multiple_of_four(&mut json_offset); let glb = gltf::binary::Glb { diff --git a/examples/roundtrip/main.rs b/examples/roundtrip/main.rs deleted file mode 100644 index 3482d553..00000000 --- a/examples/roundtrip/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::{fs, io}; - -use std::boxed::Box; -use std::error::Error as StdError; - -fn run(path: &str) -> Result<(), Box> { - let file = fs::File::open(path)?; - let reader = io::BufReader::new(file); - let gltf = gltf::Gltf::from_reader(reader)?; - let json = gltf.document.into_json().to_string_pretty()?; - println!("{}", json); - Ok(()) -} - -fn main() { - if let Some(path) = std::env::args().nth(1) { - run(&path).expect("runtime error"); - } else { - println!("usage: gltf-roundtrip "); - } -} diff --git a/examples/tree/main.rs b/examples/tree/main.rs deleted file mode 100644 index 8e76154f..00000000 --- a/examples/tree/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -use std::boxed::Box; -use std::error::Error as StdError; -use std::{fs, io}; - -fn print_tree(node: &gltf::Node, depth: i32) { - for _ in 0..(depth - 1) { - print!(" "); - } - print!(" -"); - print!(" Node {}", node.index()); - #[cfg(feature = "names")] - print!(" ({})", node.name().unwrap_or("")); - println!(); - - for child in node.children() { - print_tree(&child, depth + 1); - } -} - -fn run(path: &str) -> Result<(), Box> { - let file = fs::File::open(path)?; - let reader = io::BufReader::new(file); - let gltf = gltf::Gltf::from_reader(reader)?; - for scene in gltf.scenes() { - print!("Scene {}", scene.index()); - #[cfg(feature = "names")] - print!(" ({})", scene.name().unwrap_or("")); - println!(); - for node in scene.nodes() { - print_tree(&node, 1); - } - } - Ok(()) -} - -fn main() { - if let Some(path) = std::env::args().nth(1) { - run(&path).expect("runtime error"); - } else { - println!("usage: gltf-tree "); - } -} diff --git a/glTF-Sample-Assets b/glTF-Sample-Assets new file mode 160000 index 00000000..b40880a4 --- /dev/null +++ b/glTF-Sample-Assets @@ -0,0 +1 @@ +Subproject commit b40880a4823deb041cce26fac4b55af662ceded3 diff --git a/gltf-derive/Cargo.toml b/gltf-derive/Cargo.toml index 30a1c5a7..d76b5c61 100644 --- a/gltf-derive/Cargo.toml +++ b/gltf-derive/Cargo.toml @@ -14,4 +14,9 @@ proc-macro = true inflections = "1.1" proc-macro2 = "1" quote = "1" -syn = "2" +syn = { version = "2", features = ["full"] } + +[dev-dependencies] +serde = "1" +serde_derive = "1" +serde_json = "1" diff --git a/gltf-derive/src/lib.rs b/gltf-derive/src/lib.rs index 7bd7533d..aef11508 100644 --- a/gltf-derive/src/lib.rs +++ b/gltf-derive/src/lib.rs @@ -1,3 +1,5 @@ +//! Macros used by the `gltf` crate. + // Adapted from `validator_derive` (https://github.com/Keats/validator). // // See LICENSE for details. @@ -7,42 +9,862 @@ extern crate proc_macro; use proc_macro::TokenStream; -use syn::DeriveInput; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{parse_quote, DeriveInput}; -#[proc_macro_derive(Validate, attributes(gltf))] -pub fn derive_validate(input: TokenStream) -> TokenStream { - expand(&syn::parse_macro_input!(input as DeriveInput)).into() +/// Provided `struct` attributes. +enum StructAttribute { + /// Identifies an indexable data structure. + /// + /// Data structures marked with `#[gltf(indexed)]` will have an + /// extra `fn index(&self) -> usize` function defined in their + /// generated reader type. + Indexed, + + /// A hook for extra validation steps applied to the whole struct. + Validate(syn::Ident), } -struct ValidateHook(pub syn::Ident); +/// Provided attributes for named `struct` fields. +enum FieldAttribute { + /// Provides a field with a default value produced by an expression. + Default(Option), + + /// Identifies a field belonging to a particular extension. + /// + /// Fields marked with `#[gltf(extension = "EXT_foo")]` are grouped together and + /// (de)serialized in a separate extension JSON object. + Extension(syn::Ident), + + /// A hook for extra validation steps applied to a single field. + Validate(syn::Ident), +} -impl syn::parse::Parse for ValidateHook { +impl syn::parse::Parse for StructAttribute { fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result { let tag = input.parse::()?; - if tag == "validate_hook" { - let _eq = input.parse::()?; - let literal = input.parse::()?; - let ident = syn::Ident::new(&literal.value(), tag.span()); - Ok(ValidateHook(ident)) + match tag.to_string().as_str() { + "indexed" => Ok(Self::Indexed), + "validate" => { + let _eq = input.parse::()?; + let literal = input.parse::()?; + let ident = syn::Ident::new(&literal.value(), tag.span()); + Ok(Self::Validate(ident)) + } + unrecognized => { + panic!("gltf({unrecognized}) is not a recognized `struct` attribute") + } + } + } +} + +impl syn::parse::Parse for FieldAttribute { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result { + let tag = input.parse::()?; + match tag.to_string().as_str() { + "default" => { + if input.is_empty() { + Ok(Self::Default(None)) + } else { + let _eq = input.parse::(); + let expr = input.parse::()?; + Ok(Self::Default(Some(expr))) + } + } + "extension" => { + let _eq = input.parse::()?; + let literal = input.parse::()?; + let ident = syn::Ident::new(&literal.value(), tag.span()); + Ok(Self::Extension(ident)) + } + "validate" => { + let _eq = input.parse::()?; + let literal = input.parse::()?; + let ident = syn::Ident::new(&literal.value(), tag.span()); + Ok(Self::Validate(ident)) + } + unrecognized => { + panic!("gltf({unrecognized}) is not a recognized named `struct` field attribute") + } + } + } +} + +/// Implements the `Default` trait. +/// +/// This macro is similar to the built-in `#[derive(Default)]` but allows default values for fields +/// to be defined inline using the `#[gltf(default = ...)]` attribute. +/// +/// # Basic usage +/// +/// Declaration +/// +/// ```rust +/// #[derive(gltf_derive::Default)] +/// struct Example { +/// #[gltf(default = 123)] +/// pub foo: i32, +/// pub bar: Option, +/// } +/// +/// let example: Example = Default::default(); +/// assert_eq!(example.foo, 123); +/// assert_eq!(example.bar, None); +/// ``` +#[proc_macro_derive(Default, attributes(gltf))] +pub fn derive_default(input: TokenStream) -> TokenStream { + expand_default(&syn::parse_macro_input!(input as DeriveInput)).into() +} + +fn expand_default(ast: &DeriveInput) -> TokenStream2 { + let ident = &ast.ident; + match ast.data { + syn::Data::Struct(ref data) => impl_default_for_struct(ident, data), + _ => panic!("#[derive(Default)] only works on `struct` declarations"), + } +} + +fn impl_default_for_struct(ident: &syn::Ident, data: &syn::DataStruct) -> TokenStream2 { + let mut per_field_idents = Vec::new(); + let mut per_field_defaults = Vec::new(); + for field in &data.fields { + let mut default = None; + for attr in &field.attrs { + if attr.path().is_ident("gltf") { + let parsed_attribute = attr + .parse_args::() + .expect("failed to parse attribute"); + if let FieldAttribute::Default(Some(expr)) = parsed_attribute { + default = Some(quote! { #expr }); + } + } + } + + per_field_idents.push(&field.ident); + if let Some(expr) = default { + per_field_defaults.push(quote! { #expr }); } else { - panic!("unrecognized gltf attribute"); + let type_ = &field.ty; + per_field_defaults.push(quote! { <#type_>::default() }); + } + } + + quote! { + impl Default for #ident { + fn default() -> Self { + Self { + #( + #per_field_idents: #per_field_defaults, + )* + } + } + } + } +} + +/// Implements the `Stub` trait. +/// +/// # Basic usage +/// +/// Declaration +/// +/// ```rust +/// #[derive(gltf_derive::Stub)] +/// struct Example { +/// pub foo: i32, +/// pub bar: Option, +/// } +/// +/// let example: Example = Stub::stub(); +/// assert_eq!(example.foo, 0); +/// assert_eq!(example.bar, None); +/// ``` +#[proc_macro_derive(Stub, attributes(gltf))] +pub fn derive_stub(input: TokenStream) -> TokenStream { + expand_stub(&syn::parse_macro_input!(input as DeriveInput)).into() +} + +fn expand_stub(ast: &DeriveInput) -> TokenStream2 { + let ident = &ast.ident; + match ast.data { + syn::Data::Struct(ref data) => impl_stub_for_struct(ident, data), + _ => panic!("#[derive(Stub)] only works on `struct` declarations"), + } +} + +fn impl_stub_for_struct(ident: &syn::Ident, data: &syn::DataStruct) -> TokenStream2 { + let mut per_field_idents = Vec::new(); + let mut per_field_stubs = Vec::new(); + for field in &data.fields { + let mut default = None; + for attr in &field.attrs { + if attr.path().is_ident("gltf") { + let parsed_attribute = attr + .parse_args::() + .expect("failed to parse attribute"); + if let FieldAttribute::Default(Some(expr)) = parsed_attribute { + default = Some(quote! { #expr }); + } else { + default = Some(quote! { Default::default() }); + } + } } + + per_field_idents.push(&field.ident); + if let Some(expr) = default { + per_field_stubs.push(quote! { #expr }); + } else { + let type_ = &field.ty; + per_field_stubs.push(quote! { <#type_>::stub() }); + } + } + + quote! { + impl crate::Stub for #ident { + fn stub() -> Self { + Self { + #( + #per_field_idents: #per_field_stubs, + )* + } + } + } + } +} + +/// Implements the `Wrap` trait for a data structure and provides an associated reader type. +/// +/// # Basic usage +/// +/// Declaration: +/// +/// ```rust,no_run +/// # pub type Root = (); +/// # +/// # pub struct Index(std::marker::PhantomData); +/// # +/// # pub trait Wrap<'a> { +/// # type Wrapped; +/// # fn wrap(&'a self, root: &'a Root) -> Self::Wrapped; +/// # fn wrap_indexed(&'a self, root: &'a Root, index: usize) -> Self::Wrapped { +/// # let _ = index; +/// # self.wrap(root) +/// # } +/// # } +/// # +/// # impl<'a, T> Wrap<'a> for Index { +/// # type Wrapped = T; +/// # fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { unimplemented!() } +/// # } +/// # +/// # impl<'a> Wrap<'a> for i32 { +/// # type Wrapped = i32; +/// # fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { unimplemented!() } +/// # } +/// # +/// # fn main() {} +/// # +/// # type Foo = i32; +/// # +/// /// Object documentation. +/// #[derive(gltf_derive::Wrap)] +/// struct Object { +/// /// Documentation for field foo. +/// pub foo: Index, +/// /// Documentation for field bar. +/// pub bar: i32, +/// } +///``` +/// +/// Generated wrap implementation: +/// +/// ```rust,no_run +/// # type Object = (); +/// # type Root = (); +/// # trait Wrap<'a> { +/// # type Wrapped; +/// # fn wrap(&'a self, root: &'a Root) -> Self::Wrapped; +/// # fn wrap_indexed(&'a self, root: &'a Root, index: usize) -> Self::Wrapped { +/// # let _ = index; +/// # self.wrap(root) +/// # } +/// # } +/// # +/// #[doc = "Object documentation."] +/// #[derive(Clone, Copy)] +/// struct ObjectReader<'a>(&'a Object, &'a Root, usize); +/// +/// impl<'a> Wrap<'a> for Object { +/// // New type generated by this macro—see below. +/// type Wrapped = ObjectReader<'a>; +/// +/// fn wrap(&'a self, root: &'a Root) -> Self::Wrapped { +/// ObjectReader(self, root, !0) +/// } +/// } +/// ``` +/// +/// Generated reader type: +/// +/// ```rust,no_run +/// # type Root = (); +/// # +/// # trait Wrap<'a> { +/// # type Wrapped; +/// # fn wrap(&'a self, root: &'a Root) -> Self::Wrapped; +/// # fn wrap_indexed(&'a self, root: &'a Root, index: usize) -> Self::Wrapped { +/// # let _ = index; +/// # self.wrap(root) +/// # } +/// # } +/// # +/// # pub struct Index(std::marker::PhantomData); +/// # +/// # impl<'a, T> Wrap<'a> for Index { +/// # type Wrapped = T; +/// # fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { unimplemented!() } +/// # } +/// # +/// # impl<'a> Wrap<'a> for i32 { +/// # type Wrapped = i32; +/// # fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { unimplemented!() } +/// # } +/// # +/// # type Foo = i32; +/// # +/// # struct Object { +/// # pub foo: Index, +/// # pub bar: i32, +/// # } +/// # +/// #[doc = "Object documentation."] +/// #[derive(Clone, Copy)] +/// struct ObjectReader<'a>(&'a Object, &'a Root, usize); +/// +/// impl<'a> ObjectReader<'a> { +/// #[doc = "Documentation for field foo."] +/// pub fn foo(&self) -> as Wrap<'a>>::Wrapped { +/// self.0.foo.wrap(self.1) +/// } +/// +/// #[doc = "Documentation for field bar."] +/// pub fn bar(&self) -> >::Wrapped { +/// self.0.bar.wrap(self.1) +/// } +/// } +/// ``` +/// +/// # With indexed attribute +/// +/// If the type is marked with `#[gltf(indexed)]` then the `wrap_indexed` function is +/// implemented and the reader type gains an additional `index` function: +/// +/// ```rust,no_run +/// # pub type Root = (); +/// # +/// # pub struct Index(std::marker::PhantomData); +/// # +/// # pub trait Wrap<'a> { +/// # type Wrapped; +/// # fn wrap(&'a self, root: &'a Root) -> Self::Wrapped; +/// # fn wrap_indexed(&'a self, root: &'a Root, index: usize) -> Self::Wrapped { +/// # let _ = index; +/// # self.wrap(root) +/// # } +/// # } +/// # +/// # impl<'a, T> Wrap<'a> for Index +/// # where T: 'a + Wrap<'a>, +/// # { +/// # type Wrapped = >::Wrapped; +/// # fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { +/// # unimplemented!() +/// # } +/// # } +/// # +/// # impl<'a> Wrap<'a> for i32 { +/// # type Wrapped = Self; +/// # fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { +/// # unimplemented!() +/// # } +/// # } +/// # +/// # fn main() {} +/// # +/// # type Foo = i32; +/// # +/// /// Object documentation. +/// #[derive(gltf_derive::Wrap)] +/// struct Object { +/// /// Documentation for field foo. +/// pub foo: Index, +/// /// Documentation for field bar. +/// pub bar: i32, +/// } +///``` +/// Generated `Wrap` implementation: +/// +/// ```rust,no_run +/// # pub type Root = (); +/// # pub trait Wrap<'a> { +/// # type Wrapped; +/// # fn wrap(&'a self, root: &'a Root) -> Self::Wrapped; +/// # fn wrap_indexed(&'a self, root: &'a Root, index: usize) -> Self::Wrapped { +/// # let _ = index; +/// # self.wrap(root) +/// # } +/// # } +/// # +/// # fn main() {} +/// # +/// # type Object = (); +/// # +/// pub struct ObjectReader<'a>(&'a Object, &'a Root, usize); +/// +/// impl<'a> Wrap<'a> for Object { +/// // New type generated by this macro—see below. +/// type Wrapped = ObjectReader<'a>; +/// +/// fn wrap(&'a self, root: &'a Root) -> Self::Wrapped { +/// ObjectReader(self, root, !0) +/// } +/// +/// fn wrap_indexed(&'a self, root: &'a Root, index: usize) -> Self::Wrapped { +/// ObjectReader(self, root, index) +/// } +/// } +/// ``` +/// +/// Generated additional `index` function: +/// +/// ```rust,no_run +/// # type Object = (); +/// # type Root = (); +/// # struct ObjectReader<'a>(&'a Object, &'a Root, usize); +/// impl<'a> ObjectReader<'a> { +/// /// The index of this object in its parent container. +/// pub fn index(&self) -> usize { +/// self.2 +/// } +/// } +/// ``` +/// +/// The `wrap_indexed` function is called when `Vec` is iterated over using +/// a reader. Refer to the implementation of `Wrap` for `Vec` in the main `gltf` +/// crate for clarity. +#[proc_macro_derive(Wrap, attributes(gltf))] +pub fn derive_wrap(input: TokenStream) -> TokenStream { + expand_wrap(&syn::parse_macro_input!(input as DeriveInput)).into() +} + +fn doc_attribute(attributes: &[syn::Attribute]) -> TokenStream2 { + for attribute in attributes { + if let syn::Meta::NameValue(ref name_value) = attribute.meta { + if name_value.path.is_ident("doc") { + return quote! { #attribute }; + } + } + } + + quote! { + #[doc = "Missing documentation"] + } +} + +fn wrap_indexed(attributes: &[syn::Attribute]) -> TokenStream2 { + for attribute in attributes { + if attribute.path().is_ident("gltf") { + let parsed_attribute = attribute + .parse_args::() + .expect("failed to parse `struct` field attribute"); + match parsed_attribute { + StructAttribute::Indexed => { + return quote! { + #[doc = "The index of this item in its parent container."] + pub fn index(&self) -> usize { + self.2 + } + } + } + StructAttribute::Validate(_) => {} + } + } + } + + quote! {} +} + +fn make_reader_ident(ident: &syn::Ident) -> syn::Ident { + syn::Ident::new(&format!("{ident}Reader"), ident.span()) +} + +fn impl_wrap_for_enum(ident: &syn::Ident, _data: &syn::DataEnum) -> TokenStream2 { + quote! { + impl<'a> crate::Wrap<'a> for #ident { + type Wrapped = Self; + + fn wrap(&'a self, _root: &'a crate::Root) -> Self::Wrapped { + self.clone() + } + } + } +} + +fn impl_wrap_for_struct( + ident: &syn::Ident, + data: &syn::DataStruct, + attributes: &[syn::Attribute], +) -> TokenStream2 { + let fields = &data.fields; + let reader = make_reader_ident(ident); + let index_fn = wrap_indexed(attributes); + let docs = doc_attribute(attributes); + let per_field_functions = fields + .iter() + .map(|f| { + ( + f.ident.as_ref().unwrap(), + f.ty.clone(), + doc_attribute(&f.attrs), + ) + }) + .map(|(field_ident, field_type, docs)| { + quote! { + #docs + pub fn #field_ident(&self) -> <#field_type as crate::Wrap>::Wrapped { + use crate::Wrap; + self.1.#field_ident.wrap(self.0) + } + } + }) + .collect::>(); + + quote! { + #docs + pub struct #reader<'a>(&'a crate::Root, &'a #ident, usize); + + impl<'a> #reader<'a> { + #index_fn + + #(#per_field_functions)* + } + + impl<'a> crate::Wrap<'a> for #ident { + type Wrapped = #reader<'a>; + + fn wrap(&'a self, root: &'a crate::Root) -> Self::Wrapped { + #reader(root, self, !0) + } + + fn wrap_indexed(&'a self, root: &'a crate::Root, index: usize) -> Self::Wrapped { + #reader(root, self, index) + } + } + } +} + +fn expand_wrap(ast: &DeriveInput) -> TokenStream2 { + let ident = &ast.ident; + match ast.data { + syn::Data::Struct(ref data) => impl_wrap_for_struct(ident, data, &ast.attrs), + syn::Data::Enum(ref data) => impl_wrap_for_enum(ident, data), + _ => panic!("#[derive(Wrap)] only works on `struct` and `enum` declarations"), } } -fn expand(ast: &DeriveInput) -> proc_macro2::TokenStream { - use proc_macro2::TokenStream; - use quote::quote; +/// Implements the `Validate` trait for a `struct` with named fields. +/// +/// For data structures, the generated code will call `Validate::validate` on each field +/// with the camel-case name of the field appended to the JSON path builder. +/// +/// # Basic usage +/// +/// Declaration: +/// +/// ```rust,no_run +/// # #[derive(Clone, Copy)] +/// # pub struct Path; +/// # +/// # impl Path { +/// # pub fn field(self, _: &str) -> Self { +/// # self +/// # } +/// # } +/// # pub type Root = (); +/// # +/// # pub mod validation { +/// # use super::{Path, Root}; +/// # +/// # pub type Error = (); +/// # +/// # pub trait Validate { +/// # fn validate(&self, _root: &Root, _path: P, _report: &mut R) +/// # where +/// # P: Fn() -> Path, +/// # R: FnMut(&dyn Fn() -> Path, Error), +/// # { +/// # } +/// # } +/// # +/// # impl Validate for i32 {} +/// # } +/// # +/// # #[derive(gltf_derive::Validate)] +/// # struct FooBar {} +/// # +/// # #[derive(gltf_derive::Validate)] +/// # struct Baz {} +/// # +/// # fn main() {} +/// # +/// #[derive(gltf_derive::Validate)] +/// struct Object { +/// pub foo_bar: FooBar, +/// pub baz: Baz, +/// } +/// ``` +/// +/// Generated code: +/// +/// ```rust,no_run +/// # pub type Root = (); +/// # +/// # #[derive(Clone, Copy)] +/// # pub struct Path; +/// # +/// # impl Path { +/// # pub fn field(self, _: &str) -> Self { +/// # self +/// # } +/// # } +/// # +/// # pub mod validation { +/// # use super::{Path, Root}; +/// # +/// # pub type Error = (); +/// # +/// # pub trait Validate { +/// # fn validate(&self, _root: &Root, _path: P, _report: &mut R) +/// # where +/// # P: Fn() -> Path, +/// # R: FnMut(&dyn Fn() -> Path, Error), +/// # { +/// # } +/// # } +/// # +/// # impl Validate for i32 {} +/// # } +/// # +/// # #[derive(gltf_derive::Validate)] +/// # struct FooBar {} +/// # +/// # #[derive(gltf_derive::Validate)] +/// # struct Baz {} +/// # +/// # struct Object { +/// # pub foo_bar: FooBar, +/// # pub baz: Baz, +/// # } +/// # +/// # fn main() {} +/// # +/// # use validation::{Error, Validate}; +/// impl Validate for Object { +/// fn validate(&self, root: &Root, path: P, report: &mut R) +/// where +/// P: Fn() -> Path, +/// R: FnMut(&dyn Fn() -> Path, Error), +/// { +/// self.foo_bar.validate(root, || path().field("fooBar"), report); +/// self.baz.validate(root, || path().field("baz"), report); +/// } +/// } +/// ``` +/// +/// # Hooks +/// +/// In addition to the standard per field code generation, ad hoc validation can be inserted using +/// the `gltf(validate = "...")` attribute. This attribute can be applied to structs and fields. +/// +/// Declaration: +/// +/// ```rust,no_run +/// # #[derive(Clone, Copy)] +/// # pub struct Path; +/// # +/// # impl Path { +/// # pub fn field(self, _: &str) -> Self { +/// # self +/// # } +/// # } +/// # pub type Root = (); +/// # +/// # pub mod validation { +/// # use super::{Path, Root}; +/// # +/// # pub enum Error { +/// # Invalid, +/// # Missing, +/// # } +/// # +/// # pub trait Validate { +/// # fn validate(&self, _root: &Root, _path: P, _report: &mut R) +/// # where +/// # P: Fn() -> Path, +/// # R: FnMut(&dyn Fn() -> Path, Error), +/// # { +/// # } +/// # } +/// # +/// # impl Validate for i32 {} +/// # } +/// # +/// # use validation::{Error, Validate}; +/// # +/// # impl Validate for Option { +/// # fn validate(&self, _root: &Root, _path: P, _report: &mut R) +/// # where +/// # P: Fn() -> Path, +/// # R: FnMut(&dyn Fn() -> Path, Error), +/// # { +/// # } +/// # } +/// # +/// # #[derive(gltf_derive::Validate)] +/// # struct Foo {} +/// # +/// # fn main() {} +/// # +/// #[derive(gltf_derive::Validate)] +/// #[gltf(validate = "validate_example_struct")] +/// struct ExampleStruct { +/// #[gltf(validate = "validate_example_field")] +/// pub example_field: i32, +/// pub regular_field: Option, +/// } +/// +/// fn validate_example_struct(example: &ExampleStruct, _root: &Root, path: P, report: &mut R) +/// where +/// P: Fn() -> Path, +/// R: FnMut(&dyn Fn() -> Path, Error), +/// { +/// if example.regular_field.is_none() { +/// report(&|| path().field("regularField"), Error::Missing); +/// } +/// } +/// +/// fn validate_example_field(example: &i32, _root: &Root, path: P, report: &mut R) +/// where +/// P: Fn() -> Path, +/// R: FnMut(&dyn Fn() -> Path, Error), +/// { +/// if *example != 42 { +/// report(&path, Error::Invalid); +/// } +/// } +/// ``` +/// +/// Generated code: +/// +/// ```rust,no_run +/// # #[derive(Clone, Copy)] +/// # pub struct Path; +/// # +/// # impl Path { +/// # pub fn field(self, _: &str) -> Self { +/// # self +/// # } +/// # } +/// # pub type Root = (); +/// # +/// # pub mod validation { +/// # use super::{Path, Root}; +/// # +/// # pub enum Error { +/// # Invalid, +/// # Missing, +/// # } +/// # +/// # pub trait Validate { +/// # fn validate(&self, _root: &Root, _path: P, _report: &mut R) +/// # where +/// # P: Fn() -> Path, +/// # R: FnMut(&dyn Fn() -> Path, Error), +/// # { +/// # } +/// # } +/// # +/// # impl Validate for i32 {} +/// # } +/// # +/// # #[derive(gltf_derive::Validate)] +/// # struct Foo {} +/// # +/// # fn main() {} +/// # +/// # use validation::{Error, Validate}; +/// # +/// # struct ExampleStruct { +/// # pub example_field: i32, +/// # pub regular_field: Option, +/// # } +/// # +/// # impl Validate for Option { +/// # fn validate(&self, _root: &Root, _path: P, _report: &mut R) +/// # where +/// # P: Fn() -> Path, +/// # R: FnMut(&dyn Fn() -> Path, Error), +/// # { +/// # } +/// # } +/// # +/// # fn validate_example_struct(example: &ExampleStruct, _root: &Root, _path: P, _report: &mut R) +/// # where +/// # P: Fn() -> Path, +/// # R: FnMut(&dyn Fn() -> Path, Error), +/// # { +/// # } +/// # +/// # fn validate_example_field(example: &i32, _root: &Root, _path: P, _report: &mut R) +/// # where +/// # P: Fn() -> Path, +/// # R: FnMut(&dyn Fn() -> Path, Error), +/// # { +/// # } +/// # +/// impl Validate for ExampleStruct { +/// fn validate(&self, root: &Root, path: P, report: &mut R) +/// where +/// P: Fn() -> Path, +/// R: FnMut(&dyn Fn() -> Path, Error), +/// { +/// validate_example_struct(self, root, &path, report); +/// +/// self.example_field.validate(root, || path().field("exampleField"), report); +/// validate_example_field(&self.example_field, root, || path().field("exampleField"), report); +/// +/// self.regular_field.validate(root, || path().field("regularField"), report); +/// } +/// } +/// ``` +#[proc_macro_derive(Validate, attributes(gltf))] +pub fn derive_validate(input: TokenStream) -> TokenStream { + expand_validate(&syn::parse_macro_input!(input as DeriveInput)).into() +} +fn expand_validate(ast: &DeriveInput) -> TokenStream2 { let mut validate_hook = quote! {}; for attr in &ast.attrs { if attr.path().is_ident("gltf") { - let ValidateHook(ident) = attr - .parse_args::() + let parsed_attr = attr + .parse_args::() .expect("failed to parse attribute"); - validate_hook = quote! { - #ident(self, _root, _path, _report); - }; + if let StructAttribute::Validate(hook_ident) = parsed_attr { + validate_hook = quote! { + #hook_ident(self, _root, _path, _report); + }; + } } } @@ -51,13 +873,30 @@ fn expand(ast: &DeriveInput) -> proc_macro2::TokenStream { _ => panic!("#[derive(Validate)] only works on `struct`s"), }; let ident = &ast.ident; - let validations: Vec = fields + let validations = fields .iter() - .map(|f| f.ident.as_ref().unwrap()) - .map(|ident| { + .map(|f| { use inflections::Inflect; + let ident = f.ident.as_ref().unwrap(); let field = ident.to_string().to_camel_case(); + + let mut validate_hook = quote! {}; + for attr in &f.attrs { + if attr.path().is_ident("gltf") { + let parsed_attr = attr + .parse_args::() + .expect("failed to parse attribute"); + if let FieldAttribute::Validate(hook_ident) = parsed_attr { + validate_hook = quote! { + #hook_ident(&self.#ident, _root, || _path().field(#field), _report); + }; + } + } + } + quote!( + #validate_hook + self.#ident.validate( _root, || _path().field(#field), @@ -65,7 +904,7 @@ fn expand(ast: &DeriveInput) -> proc_macro2::TokenStream { ) ) }) - .collect(); + .collect::>(); let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); quote!( impl #impl_generics crate::validation::Validate @@ -89,3 +928,640 @@ fn expand(ast: &DeriveInput) -> proc_macro2::TokenStream { } ) } + +enum Generator { + Deserialize, + Serialize, +} + +/// Extension of `serde_derive::Deserialize` for glTF objects. +/// +/// This macro allows fields for glTF extensions to be declared alongside standard glTF fields. +/// Attributes from `serde_derive` are compatible with this macro. +/// +/// The generated code identifies fields marked with the `#[gltf(extension = "...")]` attribute +/// and deserializes them separately from the `"extensions"` object in the glTF JSON. It also +/// applies some common serde attributes automatically. +/// +/// # Basic usage +/// +/// Declaration: +/// +/// ```rust,no_run +/// # type UnrecognizedExtensions = serde_json::Map; +/// # +/// #[derive(serde_derive::Deserialize)] +/// struct ObjectExtensionFoo(pub i32); +/// +/// #[derive(gltf_derive::Deserialize)] +/// struct Object { +/// // Note: extension field types must appear within an `Option` type. +/// #[gltf(extension = "EXT_object_foo")] +/// pub foo: Option, +/// pub bar: i32, +/// pub unrecognized_extensions: UnrecognizedExtensions, +/// } +/// ``` +/// +/// Generated code: +/// +/// ```rust,no_run +/// # #[derive(serde_derive::Deserialize)] +/// # struct ObjectExtensionFoo(pub i32); +/// # +/// # struct Object { +/// # pub foo: Option, +/// # pub bar: i32, +/// # } +/// # +/// #[allow(non_snake_case)] +/// impl<'de> serde::Deserialize<'de> for Object { +/// fn deserialize(deserializer: D) -> Result +/// where +/// D: serde::de::Deserializer<'de>, +/// { +/// #[derive(Default, serde_derive::Deserialize)] +/// struct Extensions { +/// #[serde(default)] +/// EXT_object_foo: Option, +/// } +/// +/// #[derive(serde_derive::Deserialize)] +/// struct Intermediate { +/// #[serde(default)] +/// extensions: Extensions, +/// bar: i32, +/// } +/// +/// let intermediate = Intermediate::deserialize(deserializer)?; +/// Ok(Self { +/// foo: intermediate.extensions.EXT_object_foo, +/// bar: intermediate.bar, +/// }) +/// } +/// } +/// ``` +/// +/// # Automatically applied attributes +/// +/// `#[serde(default)]` is applied for `bool`, `Option`, and `Vec` types. +/// +/// Declaration: +/// +/// ```rust,no_run +/// # type UnrecognizedExtensions = serde_json::Map; +/// #[derive(gltf_derive::Deserialize)] +/// struct Foo { +/// a: i32, +/// b: bool, +/// c: Option, +/// d: Vec, +/// unrecognized_extensions: UnrecognizedExtensions, +/// } +/// ``` +/// +/// Equivalent code: +/// +/// ```rust,no_run +/// # type UnrecognizedExtensions = serde_json::Map; +/// #[derive(serde_derive::Deserialize)] +/// struct Foo { +/// a: i32, +/// #[serde(default)] +/// b: bool, +/// #[serde(default)] +/// c: Option, +/// #[serde(default)] +/// d: Vec, +/// #[serde(flatten)] +/// unrecognized_extensions: UnrecognizedExtensions, +/// } +/// ``` +#[proc_macro_derive(Deserialize, attributes(gltf))] +pub fn derive_deserialize(input: TokenStream) -> TokenStream { + expand( + Generator::Deserialize, + &syn::parse_macro_input!(input as DeriveInput), + ) + .into() +} + +/// Extension of `serde_derive::Serialize` for glTF objects. +/// +/// This macro allows fields for glTF extensions to be declared alongside standard glTF fields. +/// Attributes from `serde_derive` are compatible with this macro. +/// +/// The generated code identifies fields marked with the `#[gltf(extension = "...")]` attribute +/// and serializes them separately into the `"extensions"` object in the glTF JSON. It also +/// applies some common serde attributes automatically. +/// +/// # Basic usage +/// +/// Declaration: +/// +/// ```rust,no_run +/// # type UnrecognizedExtensions = ::serde_json::Map; +/// # +/// #[derive(serde_derive::Serialize)] +/// struct ObjectExtensionFoo(i32); +/// +/// #[derive(gltf_derive::Serialize)] +/// struct Object { +/// // Note: extension field types must appear within an `Option` type. +/// #[gltf(extension = "EXT_object_foo")] +/// pub foo: Option, +/// pub bar: i32, +/// pub unrecognized_extensions: UnrecognizedExtensions, +/// } +/// ``` +/// +/// Generated code: +/// +/// ```rust,no_run +/// # type UnrecognizedExtensions = ::serde_json::Map; +/// # +/// # #[derive(serde_derive::Serialize)] +/// # struct ObjectExtensionFoo(pub i32); +/// # +/// # struct Object { +/// # pub foo: Option, +/// # pub bar: i32, +/// # pub unrecognized_extensions: UnrecognizedExtensions, +/// # } +/// # +/// #[allow(non_snake_case)] +/// impl serde::Serialize for Object { +/// fn serialize(&self, serializer: S) -> Result +/// where +/// S: serde::ser::Serializer, +/// { +/// #[derive(serde_derive::Serialize)] +/// struct Extensions<'a> { +/// #[serde(skip_serializing_if = "Option::is_none")] +/// EXT_object_foo: &'a Option, +/// #[serde(flatten)] +/// unrecognized: &'a UnrecognizedExtensions, +/// } +/// +/// impl<'a> Extensions<'a> { +/// fn is_empty(&self) -> bool { +/// self.EXT_object_foo.is_none() && self.unrecognized.is_empty() +/// } +/// } +/// +/// #[derive(serde_derive::Serialize)] +/// struct Intermediate<'a> { +/// #[serde(skip_serializing_if = "Extensions::is_empty")] +/// extensions: Extensions<'a>, +/// bar: &'a i32, +/// } +/// +/// let intermediate = Intermediate { +/// bar: &self.bar, +/// extensions: Extensions { +/// EXT_object_foo: &self.foo, +/// unrecognized: &self.unrecognized_extensions, +/// }, +/// }; +/// +/// intermediate.serialize(serializer) +/// } +/// } +/// ``` +/// +/// # Automatically applied attributes +/// +/// `#[serde(skip_serializing_if = "...")]` is applied for `bool`, `Option`, and `Vec` types. +/// +/// Declaration: +/// +/// ```rust,no_run +/// # type UnrecognizedExtensions = serde_json::Map; +/// #[derive(gltf_derive::Serialize)] +/// struct Foo { +/// a: i32, +/// b: bool, +/// c: Option, +/// d: Vec, +/// unrecognized_extensions: UnrecognizedExtensions, +/// } +/// ``` +/// +/// Equivalent code: +/// +/// ```rust,no_run +/// # type UnrecognizedExtensions = serde_json::Map; +/// #[derive(serde_derive::Serialize)] +/// struct Foo { +/// a: i32, +/// #[serde(skip_serializing_if = "std::ops::Not::not")] +/// b: bool, +/// #[serde(skip_serializing_if = "Option::is_none")] +/// c: Option, +/// #[serde(skip_serializing_if = "Vec::is_empty")] +/// d: Vec, +/// unrecognized_extensions: UnrecognizedExtensions, +/// } +/// ``` +#[proc_macro_derive(Serialize, attributes(gltf, serde))] +pub fn derive_serialize(input: TokenStream) -> TokenStream { + expand( + Generator::Serialize, + &syn::parse_macro_input!(input as DeriveInput), + ) + .into() +} + +fn expand(generator: Generator, ast: &DeriveInput) -> TokenStream2 { + match ast.data { + syn::Data::Struct(ref data) => expand_for_struct(generator, &ast.ident, data, &ast.attrs), + _ => panic!("gltf_derive::Deserialize only works on `struct` declarations"), + } +} + +fn has_type_name(type_: &syn::Type, type_name: &str) -> bool { + match type_ { + syn::Type::Path(type_path) => { + if let Some(segment) = type_path.path.segments.first() { + segment.ident == type_name + } else { + false + } + } + syn::Type::Reference(ref_type) => has_type_name(&ref_type.elem, type_name), + _ => false, + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum SpecialCase { + /// The `bool` primitive type. + Bool, + /// The standard library `Option` type. + Option, + /// The standard library `Vec` type. + Vec, +} + +impl SpecialCase { + pub fn skip_serializing_attribute(self) -> syn::Attribute { + match self { + Self::Bool => parse_quote! { #[serde(skip_serializing_if = "is_false")] }, + Self::Option => parse_quote! { #[serde(skip_serializing_if = "Option::is_none")] }, + Self::Vec => parse_quote! { #[serde(skip_serializing_if = "Vec::is_empty")] }, + } + } +} + +fn detect_special_case(type_: &syn::Type) -> Option { + if has_type_name(type_, "bool") { + Some(SpecialCase::Bool) + } else if has_type_name(type_, "Option") { + Some(SpecialCase::Option) + } else if has_type_name(type_, "Vec") { + Some(SpecialCase::Vec) + } else { + None + } +} + +struct Attributes(pub Vec); + +impl quote::ToTokens for Attributes { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + for attribute in &self.0 { + attribute.to_tokens(tokens); + } + } +} + +fn expand_for_struct( + generator: Generator, + ident: &syn::Ident, + data: &syn::DataStruct, + attrs: &[syn::Attribute], +) -> TokenStream2 { + let serde_attrs = attrs + .iter() + .filter(|attr| attr.path().is_ident("serde")) + .collect::>(); + + let mut ext_attrs = Vec::new(); + let mut ext_idents = Vec::new(); + let mut ext_types = Vec::new(); + let mut ext_renames = Vec::new(); + + let mut core_attrs = Vec::new(); + let mut core_idents = Vec::new(); + let mut core_types = Vec::new(); + + let mut is_default_fn_bodies = Vec::new(); + let mut default_fn_bodies = Vec::new(); + + for field in &data.fields { + if field + .ident + .as_ref() + .map(|ident| ident == "unrecognized_extensions") + .unwrap_or_default() + { + // Avoid double field. + continue; + } + + let mut is_extension = false; + let mut default_attr = None; // Option> + for attr in &field.attrs { + if attr.path().is_ident("gltf") { + let parsed_attribute = attr + .parse_args::() + .expect("failed to parse attribute"); + match parsed_attribute { + FieldAttribute::Default(expr) => { + default_attr = Some(expr); + } + FieldAttribute::Extension(ident) => { + is_extension = true; + ext_renames.push(ident); + } + FieldAttribute::Validate(_) => {} + } + } + } + + if is_extension && default_attr.is_some() { + panic!("#[gltf(default)] cannot be combined with #[gltf(extension)]"); + } + + // We need to filter out any gltf attributes from the next stage of compilation + // or else they will cease to be recognized and compilation will fail. + let mut filtered_attrs = field + .attrs + .iter() + .filter(|attr| attr.path().is_ident("serde")) + .cloned() + .collect::>(); + + // Insert extra attributes for special cases such as `Option` and `Vec` types. + if let Some(special_case) = detect_special_case(&field.ty) { + match generator { + Generator::Serialize => { + filtered_attrs.push(special_case.skip_serializing_attribute()); + } + Generator::Deserialize => { + filtered_attrs.push(parse_quote! { #[serde(default)] }); + } + } + } + + if let Some(default) = default_attr { + match generator { + Generator::Serialize => { + let ident = field.ident.as_ref().unwrap(); + let type_ = &field.ty; + let is_default_fn_name = + syn::Ident::new(&format!("{}_is_default", ident), ident.span()); + let is_default_fn_body = if let Some(expr) = default { + quote! { + fn #is_default_fn_name(value: &#type_) -> bool { + *value == #expr + } + } + } else { + // The default case relies on `PartialEq` being implemented. + // A special case is required for `Option` and `Vec` + // since `T` might not implement the `PartialEq` trait. + match detect_special_case(type_) { + Some(SpecialCase::Option) => { + quote! { + fn #is_default_fn_name(value: &#type_) -> bool { + value.is_none() + } + } + } + Some(SpecialCase::Vec) => { + quote! { + fn #is_default_fn_name(value: &#type_) -> bool { + value.is_empty() + } + } + } + _ => { + quote! { + fn #is_default_fn_name(value: &#type_) -> bool { + *value == <#type_>::default() + } + } + } + } + }; + is_default_fn_bodies.push(is_default_fn_body); + let is_default_fn_lit = syn::LitStr::new( + &format!("{is_default_fn_name}"), + proc_macro2::Span::call_site(), + ); + filtered_attrs + .push(parse_quote! { #[serde(skip_serializing_if = #is_default_fn_lit)] }); + } + Generator::Deserialize => { + let ident = field.ident.as_ref().unwrap(); + let type_ = &field.ty; + let default_fn_name = + syn::Ident::new(&format!("{}_default", ident), ident.span()); + let default_fn_body = if let Some(expr) = default { + quote! { + fn #default_fn_name() -> #type_ { + #expr + } + } + } else { + quote! { + fn #default_fn_name() -> #type_ { + Default::default() + } + } + }; + default_fn_bodies.push(default_fn_body); + let default_fn_lit = syn::LitStr::new( + &format!("{default_fn_name}"), + proc_macro2::Span::call_site(), + ); + filtered_attrs.push(parse_quote! { #[serde(default = #default_fn_lit)] }); + } + } + } + + if is_extension { + ext_attrs.push(Attributes(filtered_attrs)); + ext_idents.push(&field.ident); + ext_types.push(&field.ty); + } else { + core_attrs.push(Attributes(filtered_attrs)); + core_idents.push(&field.ident); + core_types.push(&field.ty); + } + } + + match generator { + Generator::Deserialize => quote! { + #[allow(non_snake_case)] + impl<'de> ::serde::de::Deserialize<'de> for #ident { + fn deserialize(deserializer: D) -> Result + where + D: ::serde::de::Deserializer<'de>, + { + #[derive(Default, serde_derive::Deserialize)] + #(#serde_attrs)* + struct Extensions { + #( + #ext_attrs + #ext_renames: #ext_types, + )* + + #[serde(flatten)] + unrecognized: ::serde_json::Map, + } + + #(#default_fn_bodies)* + + #[derive(serde_derive::Deserialize)] + #[serde(rename_all = "camelCase")] + #(#serde_attrs)* + struct Intermediate { + #( + #core_attrs + #core_idents: #core_types, + )* + + #[serde(default)] + extensions: Extensions, + } + + let intermediate = Intermediate::deserialize(deserializer)?; + + Ok(Self { + #(#core_idents: intermediate.#core_idents,)* + #(#ext_idents: intermediate.extensions.#ext_renames,)* + unrecognized_extensions: intermediate.extensions.unrecognized, + }) + } + } + }, + Generator::Serialize => quote! { + #[allow(non_snake_case)] + impl ::serde::ser::Serialize for #ident { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::ser::Serializer, + { + #[allow(unused)] + #[inline] + fn is_false(b: &bool) -> bool { + !*b + } + + #[derive(serde_derive::Serialize)] + #(#serde_attrs)* + struct Extensions<'a> { + #( + #ext_attrs + #ext_renames: &'a #ext_types, + )* + + #[serde(flatten)] + unrecognized: &'a ::serde_json::Map, + } + + impl<'a> Extensions<'a> { + fn is_empty(&self) -> bool { + #(self.#ext_renames.is_none() &&)* + self.unrecognized.is_empty() + } + } + + #(#is_default_fn_bodies)* + + #[derive(serde_derive::Serialize)] + #[serde(rename_all = "camelCase")] + #(#serde_attrs)* + struct Intermediate<'a> { + #( + #core_attrs + #core_idents: &'a #core_types, + )* + + #[serde(skip_serializing_if = "Extensions::is_empty")] + extensions: Extensions<'a>, + } + + let intermediate = Intermediate { + #( + #core_idents: &self.#core_idents, + )* + extensions: Extensions { + #(#ext_renames: &self.#ext_idents,)* + unrecognized: &self.unrecognized_extensions, + }, + }; + + intermediate.serialize(serializer) + } + } + }, + } +} + +mod tests { + #[test] + fn detect_special_cases() { + use super::{detect_special_case, SpecialCase}; + use syn::parse_quote; + + assert_eq!( + Some(SpecialCase::Option), + detect_special_case(&parse_quote! { Option }), + ); + + assert_eq!( + Some(SpecialCase::Option), + detect_special_case(&parse_quote! { Option> }), + ); + + assert_eq!( + Some(SpecialCase::Option), + detect_special_case(&parse_quote! { Option<(i32, i32)> }), + ); + + assert_eq!( + Some(SpecialCase::Option), + detect_special_case(&parse_quote! { &'a Option }), + ); + + assert_eq!( + Some(SpecialCase::Vec), + detect_special_case(&parse_quote! { Vec> }), + ); + + assert_eq!( + Some(SpecialCase::Vec), + detect_special_case(&parse_quote! { Vec<(i32, i32)> }), + ); + + assert_eq!( + Some(SpecialCase::Vec), + detect_special_case(&parse_quote! { &'a Vec }), + ); + + assert_eq!( + Some(SpecialCase::Bool), + detect_special_case(&parse_quote! { bool }), + ); + + assert_eq!( + Some(SpecialCase::Bool), + detect_special_case(&parse_quote! { &'a bool }), + ); + + assert_eq!(None, detect_special_case(&parse_quote! { i32 }),); + } +} diff --git a/gltf-derive/tests/main.rs b/gltf-derive/tests/main.rs new file mode 100644 index 00000000..09f1dfff --- /dev/null +++ b/gltf-derive/tests/main.rs @@ -0,0 +1,330 @@ +/// Collection for extension data without built-in support. +/// +/// The deserialize and serialize macros assume a field called `unrecognized_extensions` exists in +/// every data structure with this type. +type UnrecognizedExtensions = serde_json::Map; + +mod boolean { + #[derive(gltf_derive::Deserialize, gltf_derive::Serialize)] + struct Example { + pub x: bool, + pub unrecognized_extensions: crate::UnrecognizedExtensions, + } + + #[test] + fn serialize() { + let a = Example { + x: false, + unrecognized_extensions: Default::default(), + }; + assert_eq!("{}", serde_json::to_string(&a).unwrap()); + + let b = Example { + x: true, + unrecognized_extensions: Default::default(), + }; + assert_eq!("{\"x\":true}", serde_json::to_string(&b).unwrap()); + } + + #[test] + fn deserialize() { + let a = serde_json::from_str::("{}").unwrap(); + assert!(!a.x); + + let b = serde_json::from_str::("{\"x\":false}").unwrap(); + assert!(!b.x); + + let c = serde_json::from_str::("{\"x\":true}").unwrap(); + assert!(c.x); + } +} + +mod option { + #[derive(gltf_derive::Default, gltf_derive::Deserialize, gltf_derive::Serialize)] + struct Example { + pub required: i32, + pub optional: Option, + pub unrecognized_extensions: crate::UnrecognizedExtensions, + } + + #[test] + fn serialize() { + let a = Example { + required: 123, + optional: None, + unrecognized_extensions: Default::default(), + }; + assert_eq!( + "{\"required\":123}", + serde_json::to_string(&a).unwrap().as_str() + ); + + let b = Example { + required: 123, + optional: Some(456), + unrecognized_extensions: Default::default(), + }; + assert_eq!( + "{\"required\":123,\"optional\":456}", + serde_json::to_string(&b).unwrap().as_str() + ); + } + + #[test] + fn deserialize() { + let a = serde_json::from_str::("{\"required\":123}").unwrap(); + assert_eq!(a.required, 123); + assert_eq!(a.optional, None); + + let b = serde_json::from_str::("{\"required\":123,\"optional\":456}").unwrap(); + assert_eq!(b.required, 123); + assert_eq!(b.optional, Some(456)); + } +} + +mod vec { + #[derive(gltf_derive::Deserialize, gltf_derive::Serialize)] + struct Example { + pub xs: Vec, + pub unrecognized_extensions: crate::UnrecognizedExtensions, + } + + #[test] + fn serialize() { + let a = Example { + xs: vec![123], + unrecognized_extensions: Default::default(), + }; + assert_eq!( + "{\"xs\":[123]}", + serde_json::to_string(&a).unwrap().as_str() + ); + + let b = Example { + xs: Vec::new(), + unrecognized_extensions: Default::default(), + }; + assert_eq!("{}", serde_json::to_string(&b).unwrap().as_str()); + } + + #[test] + fn deserialize() { + let a = serde_json::from_str::("{\"xs\":[123]}").unwrap(); + assert_eq!(a.xs.as_slice(), &[123]); + + let b = serde_json::from_str::("{}").unwrap(); + assert!(b.xs.is_empty()); + } +} + +mod camel_case { + #[derive(gltf_derive::Deserialize, gltf_derive::Serialize)] + struct Example { + /// This field should be converted to camel case automatically. + pub the_quick_brown_fox: i32, + /// This field has an explicit name override and should not be converted to camel case. + #[serde(rename = "TheLazyDog")] + pub the_lazy_dog: i32, + pub unrecognized_extensions: crate::UnrecognizedExtensions, + } + + #[test] + fn serialize() { + let a = Example { + the_quick_brown_fox: 123, + the_lazy_dog: 456, + unrecognized_extensions: Default::default(), + }; + assert_eq!( + "{\"theQuickBrownFox\":123,\"TheLazyDog\":456}", + serde_json::to_string(&a).unwrap().as_str() + ); + } + + #[test] + fn deserialize() { + let a = serde_json::from_str::("{\"theQuickBrownFox\":123,\"TheLazyDog\":456}") + .unwrap(); + assert_eq!(a.the_quick_brown_fox, 123); + assert_eq!(a.the_lazy_dog, 456); + } +} + +mod extensions { + use serde_json::Value; + + #[derive(serde_derive::Deserialize, serde_derive::Serialize, Debug, PartialEq, Eq)] + struct Y(pub i32); + + #[derive(serde_derive::Deserialize, serde_derive::Serialize, Debug, PartialEq, Eq)] + struct Z(pub i32); + + #[derive(gltf_derive::Deserialize, gltf_derive::Serialize, Debug, PartialEq, Eq)] + struct Example { + /// Regular field. + pub x: i32, + /// Field corresponding to an extension named `EXT_y`. + #[gltf(extension = "EXT_y")] + pub y: Option, + /// Field corresponding to an extension named `EXT_z`. + #[gltf(extension = "EXT_z")] + pub z: Option, + /// Collects extension data that doesn't have built-in support. + /// + /// This field must appear on every data structure. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + } + + #[test] + fn serialize() { + let a = Example { + x: 123, + y: Some(Y(456)), + z: None, + unrecognized_extensions: { + let mut map = crate::UnrecognizedExtensions::default(); + map.insert("EXT_?".to_owned(), Value::Number(789.into())); + map + }, + }; + assert_eq!( + r#"{"x":123,"extensions":{"EXT_y":456,"EXT_?":789}}"#, + serde_json::to_string(&a).unwrap().as_str() + ); + + let b = Example { + x: 123, + y: None, + z: Some(Z(789)), + unrecognized_extensions: Default::default(), + }; + assert_eq!( + r#"{"x":123,"extensions":{"EXT_z":789}}"#, + serde_json::to_string(&b).unwrap().as_str() + ); + + let c = Example { + x: 123, + y: Some(Y(456)), + z: Some(Z(789)), + unrecognized_extensions: Default::default(), + }; + assert_eq!( + r#"{"x":123,"extensions":{"EXT_y":456,"EXT_z":789}}"#, + serde_json::to_string(&c).unwrap().as_str() + ); + + let d = Example { + x: 123, + y: None, + z: None, + unrecognized_extensions: Default::default(), + }; + assert_eq!(r#"{"x":123}"#, serde_json::to_string(&d).unwrap().as_str()); + } + + #[test] + fn deserialize() { + let a = + serde_json::from_str::(r#"{"x":123,"extensions":{"EXT_y":456,"EXT_?":789}}"#) + .unwrap(); + assert_eq!(a.x, 123); + assert_eq!(a.y, Some(Y(456))); + assert_eq!(a.z, None); + assert_eq!( + a.unrecognized_extensions.get("EXT_?").cloned(), + Some(Value::Number(789.into())) + ); + assert_eq!(a.unrecognized_extensions.len(), 1); + + let b = serde_json::from_str::(r#"{"x":123,"extensions":{"EXT_z":789}}"#).unwrap(); + assert_eq!(b.x, 123); + assert_eq!(b.y, None); + assert_eq!(b.z, Some(Z(789))); + assert!(b.unrecognized_extensions.is_empty()); + + let c = + serde_json::from_str::(r#"{"x":123,"extensions":{"EXT_y":456,"EXT_z":789}}"#) + .unwrap(); + assert_eq!(c.x, 123); + assert_eq!(c.y, Some(Y(456))); + assert_eq!(c.z, Some(Z(789))); + assert!(c.unrecognized_extensions.is_empty()); + + let d = serde_json::from_str::(r#"{"x":123}"#).unwrap(); + assert_eq!(d.x, 123); + assert_eq!(d.y, None); + assert_eq!(d.z, None); + assert!(d.unrecognized_extensions.is_empty()); + } +} + +mod default { + #[derive(gltf_derive::Default, gltf_derive::Deserialize, gltf_derive::Serialize, Debug)] + struct Example { + #[gltf(default)] + pub x: i32, + #[gltf(default = 123)] + pub y: i32, + #[gltf(default = [1.2, 3.4])] + pub z: [f32; 2], + pub unrecognized_extensions: crate::UnrecognizedExtensions, + } + + #[test] + fn default() { + let a: Example = Default::default(); + assert_eq!(a.x, 0); + assert_eq!(a.y, 123); + assert_eq!(a.z, [1.2, 3.4]); + } + + #[test] + fn serialize() { + let a = Example { + x: 123, + y: 456, + z: [7.8, 9.0], + unrecognized_extensions: Default::default(), + }; + assert_eq!( + r#"{"x":123,"y":456,"z":[7.8,9.0]}"#, + serde_json::to_string(&a).unwrap(), + ); + + let b = Example { + x: 123, + y: 123, + z: [7.8, 9.0], + unrecognized_extensions: Default::default(), + }; + assert_eq!( + r#"{"x":123,"z":[7.8,9.0]}"#, + serde_json::to_string(&b).unwrap(), + ); + + let c = Example { + x: 123, + y: 456, + z: [1.2, 3.4], + unrecognized_extensions: Default::default(), + }; + assert_eq!(r#"{"x":123,"y":456}"#, serde_json::to_string(&c).unwrap()); + + let d = Example::default(); + assert_eq!(r#"{}"#, serde_json::to_string(&d).unwrap()); + } + + #[test] + fn deserialize() { + let a = serde_json::from_str::("{}").unwrap(); + assert_eq!(a.x, 0); + assert_eq!(a.y, 123); + assert_eq!(a.z, [1.2, 3.4]); + + let b = serde_json::from_str::(r#"{"x":1,"y":2,"z":[3.4,5.6]}"#).unwrap(); + assert_eq!(b.x, 1); + assert_eq!(b.y, 2); + assert_eq!(b.z, [3.4, 5.6]); + } +} diff --git a/gltf-json/Cargo.toml b/gltf-json/Cargo.toml deleted file mode 100644 index 015510df..00000000 --- a/gltf-json/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "gltf-json" -version = "1.4.1" -authors = ["David Harvey-Macaulay "] -description = "JSON parsing for the gltf crate" -repository = "https://github.com/gltf-rs/gltf" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.61" - -[dependencies] -gltf-derive = { path = "../gltf-derive", version = "=1.4.1" } -serde = "1.0" -serde_derive = "1.0" -serde_json = { features = ["raw_value"], version = "1.0" } - -[features] -default = [] -allow_empty_texture = [] -names = [] -extensions = [] -extras = [] -KHR_lights_punctual = [] -KHR_materials_ior = [] -KHR_materials_pbrSpecularGlossiness = [] -KHR_materials_specular = [] -KHR_materials_transmission = [] -KHR_materials_unlit = [] -KHR_materials_variants = [] -KHR_materials_volume = [] -KHR_texture_transform = [] -KHR_materials_emissive_strength = [] diff --git a/gltf-json/LICENSE-APACHE b/gltf-json/LICENSE-APACHE deleted file mode 100644 index 1e5006dc..00000000 --- a/gltf-json/LICENSE-APACHE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - diff --git a/gltf-json/LICENSE-MIT b/gltf-json/LICENSE-MIT deleted file mode 100644 index e812a247..00000000 --- a/gltf-json/LICENSE-MIT +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2017 The gltf Library Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - diff --git a/gltf-json/src/accessor.rs b/gltf-json/src/accessor.rs deleted file mode 100644 index b8c14cb1..00000000 --- a/gltf-json/src/accessor.rs +++ /dev/null @@ -1,423 +0,0 @@ -use crate::validation::{Checked, Error, USize64}; -use crate::{buffer, extensions, Extras, Index, Path, Root}; -use gltf_derive::Validate; -use serde::{de, ser}; -use serde_derive::{Deserialize, Serialize}; -use serde_json::Value; -use std::fmt; - -/// The component data type. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)] -pub enum ComponentType { - /// Corresponds to `GL_BYTE`. - I8 = 1, - /// Corresponds to `GL_UNSIGNED_BYTE`. - U8, - /// Corresponds to `GL_SHORT`. - I16, - /// Corresponds to `GL_UNSIGNED_SHORT`. - U16, - /// Corresponds to `GL_UNSIGNED_INT`. - U32, - /// Corresponds to `GL_FLOAT`. - F32, -} - -/// Specifies whether an attribute, vector, or matrix. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)] -pub enum Type { - /// Scalar quantity. - Scalar = 1, - /// 2D vector. - Vec2, - /// 3D vector. - Vec3, - /// 4D vector. - Vec4, - /// 2x2 matrix. - Mat2, - /// 3x3 matrix. - Mat3, - /// 4x4 matrix. - Mat4, -} - -/// Corresponds to `GL_BYTE`. -pub const BYTE: u32 = 5120; - -/// Corresponds to `GL_UNSIGNED_BYTE`. -pub const UNSIGNED_BYTE: u32 = 5121; - -/// Corresponds to `GL_SHORT`. -pub const SHORT: u32 = 5122; - -/// Corresponds to `GL_UNSIGNED_SHORT`. -pub const UNSIGNED_SHORT: u32 = 5123; - -/// Corresponds to `GL_UNSIGNED_INT`. -pub const UNSIGNED_INT: u32 = 5125; - -/// Corresponds to `GL_FLOAT`. -pub const FLOAT: u32 = 5126; - -/// All valid generic vertex attribute component types. -pub const VALID_COMPONENT_TYPES: &[u32] = &[ - BYTE, - UNSIGNED_BYTE, - SHORT, - UNSIGNED_SHORT, - UNSIGNED_INT, - FLOAT, -]; - -/// All valid index component types. -pub const VALID_INDEX_TYPES: &[u32] = &[UNSIGNED_BYTE, UNSIGNED_SHORT, UNSIGNED_INT]; - -/// All valid accessor types. -pub const VALID_ACCESSOR_TYPES: &[&str] = - &["SCALAR", "VEC2", "VEC3", "VEC4", "MAT2", "MAT3", "MAT4"]; - -/// Contains data structures for sparse storage. -pub mod sparse { - use super::*; - use crate::extensions; - - /// Indices of those attributes that deviate from their initialization value. - #[derive(Clone, Debug, Deserialize, Serialize, Validate)] - pub struct Indices { - /// The parent buffer view containing the sparse indices. - /// - /// The referenced buffer view must not have `ARRAY_BUFFER` nor - /// `ELEMENT_ARRAY_BUFFER` as its target. - #[serde(rename = "bufferView")] - pub buffer_view: Index, - - /// The offset relative to the start of the parent `BufferView` in bytes. - #[serde(default, rename = "byteOffset")] - pub byte_offset: USize64, - - /// The data type of each index. - #[serde(rename = "componentType")] - pub component_type: Checked, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - } - - /// Sparse storage of attributes that deviate from their initialization value. - #[derive(Clone, Debug, Deserialize, Serialize, Validate)] - pub struct Sparse { - /// The number of attributes encoded in this sparse accessor. - pub count: USize64, - - /// Index array of size `count` that points to those accessor attributes - /// that deviate from their initialization value. - /// - /// Indices must strictly increase. - pub indices: Indices, - - /// Array of size `count * number_of_components` storing the displaced - /// accessor attributes pointed by `indices`. - /// - /// Substituted values must have the same `component_type` and number of - /// components as the base `Accessor`. - pub values: Values, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - } - - /// Array of size `count * number_of_components` storing the displaced - /// accessor attributes pointed by `accessor::sparse::Indices`. - #[derive(Clone, Debug, Deserialize, Serialize, Validate)] - pub struct Values { - /// The parent buffer view containing the sparse indices. - /// - /// The referenced buffer view must not have `ARRAY_BUFFER` nor - /// `ELEMENT_ARRAY_BUFFER` as its target. - #[serde(rename = "bufferView")] - pub buffer_view: Index, - - /// The offset relative to the start of the parent buffer view in bytes. - #[serde(default, rename = "byteOffset")] - pub byte_offset: USize64, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - } -} - -/// A typed view into a buffer view. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -#[gltf(validate_hook = "accessor_validate_hook")] -pub struct Accessor { - /// The parent buffer view this accessor reads from. - /// - /// This field can be omitted in sparse accessors. - #[serde(rename = "bufferView")] - #[serde(skip_serializing_if = "Option::is_none")] - pub buffer_view: Option>, - - /// The offset relative to the start of the parent `BufferView` in bytes. - /// - /// This field can be omitted in sparse accessors. - #[serde(default, rename = "byteOffset")] - #[serde(skip_serializing_if = "Option::is_none")] - pub byte_offset: Option, - - /// The number of components within the buffer view - not to be confused - /// with the number of bytes in the buffer view. - pub count: USize64, - - /// The data type of components in the attribute. - #[serde(rename = "componentType")] - pub component_type: Checked, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - - /// Specifies if the attribute is a scalar, vector, or matrix. - #[serde(rename = "type")] - pub type_: Checked, - - /// Minimum value of each component in this attribute. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub min: Option, - - /// Maximum value of each component in this attribute. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub max: Option, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// Specifies whether integer data values should be normalized. - #[serde(default, skip_serializing_if = "is_normalized_default")] - pub normalized: bool, - - /// Sparse storage of attributes that deviate from their initialization - /// value. - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - pub sparse: Option, -} - -fn accessor_validate_hook(accessor: &Accessor, _root: &Root, path: P, report: &mut R) -where - P: Fn() -> Path, - R: FnMut(&dyn Fn() -> Path, Error), -{ - if accessor.sparse.is_none() && accessor.buffer_view.is_none() { - // If sparse is missing, then bufferView must be present. Report that bufferView is - // missing since it is the more common one to require. - report(&|| path().field("bufferView"), Error::Missing); - } -} - -// Help serde avoid serializing this glTF 2.0 default value. -fn is_normalized_default(b: &bool) -> bool { - !*b -} - -/// The data type of an index. -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct IndexComponentType(pub ComponentType); - -/// The data type of a generic vertex attribute. -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct GenericComponentType(pub ComponentType); - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_COMPONENT_TYPES) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - use self::ComponentType::*; - use crate::validation::Checked::*; - Ok(match value as u32 { - BYTE => Valid(GenericComponentType(I8)), - UNSIGNED_BYTE => Valid(GenericComponentType(U8)), - SHORT => Valid(GenericComponentType(I16)), - UNSIGNED_SHORT => Valid(GenericComponentType(U16)), - UNSIGNED_INT => Valid(GenericComponentType(U32)), - FLOAT => Valid(GenericComponentType(F32)), - _ => Invalid, - }) - } - } - deserializer.deserialize_u64(Visitor) - } -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_INDEX_TYPES) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - use self::ComponentType::*; - use crate::validation::Checked::*; - Ok(match value as u32 { - UNSIGNED_BYTE => Valid(IndexComponentType(U8)), - UNSIGNED_SHORT => Valid(IndexComponentType(U16)), - UNSIGNED_INT => Valid(IndexComponentType(U32)), - _ => Invalid, - }) - } - } - deserializer.deserialize_u64(Visitor) - } -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_ACCESSOR_TYPES) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - use self::Type::*; - use crate::validation::Checked::*; - Ok(match value { - "SCALAR" => Valid(Scalar), - "VEC2" => Valid(Vec2), - "VEC3" => Valid(Vec3), - "VEC4" => Valid(Vec4), - "MAT2" => Valid(Mat2), - "MAT3" => Valid(Mat3), - "MAT4" => Valid(Mat4), - _ => Invalid, - }) - } - } - deserializer.deserialize_str(Visitor) - } -} - -impl ser::Serialize for Type { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_str(match *self { - Type::Scalar => "SCALAR", - Type::Vec2 => "VEC2", - Type::Vec3 => "VEC3", - Type::Vec4 => "VEC4", - Type::Mat2 => "MAT2", - Type::Mat3 => "MAT3", - Type::Mat4 => "MAT4", - }) - } -} - -impl ComponentType { - /// Returns the number of bytes this value represents. - pub fn size(&self) -> usize { - use self::ComponentType::*; - match *self { - I8 | U8 => 1, - I16 | U16 => 2, - F32 | U32 => 4, - } - } - - /// Returns the corresponding `GLenum`. - pub fn as_gl_enum(self) -> u32 { - match self { - ComponentType::I8 => BYTE, - ComponentType::U8 => UNSIGNED_BYTE, - ComponentType::I16 => SHORT, - ComponentType::U16 => UNSIGNED_SHORT, - ComponentType::U32 => UNSIGNED_INT, - ComponentType::F32 => FLOAT, - } - } -} - -impl ser::Serialize for ComponentType { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_u32(self.as_gl_enum()) - } -} - -impl Type { - /// Returns the equivalent number of scalar quantities this type represents. - pub fn multiplicity(&self) -> usize { - use self::Type::*; - match *self { - Scalar => 1, - Vec2 => 2, - Vec3 => 3, - Vec4 | Mat2 => 4, - Mat3 => 9, - Mat4 => 16, - } - } -} diff --git a/gltf-json/src/animation.rs b/gltf-json/src/animation.rs deleted file mode 100644 index cc1bca04..00000000 --- a/gltf-json/src/animation.rs +++ /dev/null @@ -1,263 +0,0 @@ -use crate::validation::{Checked, Error, Validate}; -use crate::{accessor, extensions, scene, Extras, Index, Path, Root}; -use gltf_derive::Validate; -use serde::{de, ser}; -use serde_derive::{Deserialize, Serialize}; -use std::fmt; - -/// All valid animation interpolation algorithms. -pub const VALID_INTERPOLATIONS: &[&str] = &["LINEAR", "STEP", "CUBICSPLINE"]; - -/// All valid animation property names. -pub const VALID_PROPERTIES: &[&str] = &["translation", "rotation", "scale", "weights"]; - -/// Specifies an interpolation algorithm. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)] -pub enum Interpolation { - /// Linear interpolation. - /// - /// The animated values are linearly interpolated between keyframes. - /// When targeting a rotation, spherical linear interpolation (slerp) should be - /// used to interpolate quaternions. The number output of elements must equal - /// the number of input elements. - Linear = 1, - - /// Step interpolation. - /// - /// The animated values remain constant to the output of the first keyframe, - /// until the next keyframe. The number of output elements must equal the number - /// of input elements. - Step, - - /// Cubic spline interpolation. - /// - /// The animation's interpolation is computed using a cubic spline with specified - /// tangents. The number of output elements must equal three times the number of - /// input elements. For each input element, the output stores three elements, an - /// in-tangent, a spline vertex, and an out-tangent. There must be at least two - /// keyframes when using this interpolation - CubicSpline, -} - -/// Specifies a property to animate. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)] -pub enum Property { - /// XYZ translation vector. - Translation = 1, - /// XYZW rotation quaternion. - Rotation, - /// XYZ scale vector. - Scale, - /// Weights of morph targets. - MorphTargetWeights, -} - -/// A keyframe animation. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Animation { - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - - /// An array of channels, each of which targets an animation's sampler at a - /// node's property. - /// - /// Different channels of the same animation must not have equal targets. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub channels: Vec, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// An array of samplers that combine input and output accessors with an - /// interpolation algorithm to define a keyframe graph (but not its target). - #[serde(skip_serializing_if = "Vec::is_empty")] - pub samplers: Vec, -} - -/// Targets an animation's sampler at a node's property. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Channel { - /// The index of a sampler in this animation used to compute the value for the - /// target. - pub sampler: Index, - - /// The index of the node and TRS property to target. - pub target: Target, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// The index of the node and TRS property that an animation channel targets. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct Target { - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - - /// The index of the node to target. - pub node: Index, - - /// The name of the node's property to modify or the 'weights' of the - /// morph targets it instantiates. - pub path: Checked, -} - -/// Defines a keyframe graph but not its target. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct Sampler { - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - - /// The index of an accessor containing keyframe input values, e.g., time. - pub input: Index, - - /// The interpolation algorithm. - #[serde(default)] - pub interpolation: Checked, - - /// The index of an accessor containing keyframe output values. - pub output: Index, -} - -impl Validate for Animation { - fn validate(&self, root: &Root, path: P, report: &mut R) - where - P: Fn() -> Path, - R: FnMut(&dyn Fn() -> Path, Error), - { - self.samplers - .validate(root, || path().field("samplers"), report); - for (index, channel) in self.channels.iter().enumerate() { - if channel.sampler.value() >= self.samplers.len() { - let path = || path().field("channels").index(index).field("sampler"); - report(&path, Error::IndexOutOfBounds); - } - } - } -} - -impl Default for Interpolation { - fn default() -> Self { - Interpolation::Linear - } -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_INTERPOLATIONS) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - use self::Interpolation::*; - use crate::validation::Checked::*; - Ok(match value { - "LINEAR" => Valid(Linear), - "STEP" => Valid(Step), - "CUBICSPLINE" => Valid(CubicSpline), - _ => Invalid, - }) - } - } - deserializer.deserialize_str(Visitor) - } -} - -impl ser::Serialize for Interpolation { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_str(match *self { - Interpolation::Linear => "LINEAR", - Interpolation::Step => "STEP", - Interpolation::CubicSpline => "CUBICSPLINE", - }) - } -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_PROPERTIES) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - use self::Property::*; - use crate::validation::Checked::*; - Ok(match value { - "translation" => Valid(Translation), - "rotation" => Valid(Rotation), - "scale" => Valid(Scale), - "weights" => Valid(MorphTargetWeights), - _ => Invalid, - }) - } - } - deserializer.deserialize_str(Visitor) - } -} - -impl ser::Serialize for Property { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_str(match *self { - Property::Translation => "translation", - Property::Rotation => "rotation", - Property::Scale => "scale", - Property::MorphTargetWeights => "weights", - }) - } -} diff --git a/gltf-json/src/asset.rs b/gltf-json/src/asset.rs deleted file mode 100644 index 1efcbe2d..00000000 --- a/gltf-json/src/asset.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::{extensions, Extras}; -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; - -/// Metadata about the glTF asset. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct Asset { - /// A copyright message suitable for display to credit the content creator. - #[serde(skip_serializing_if = "Option::is_none")] - pub copyright: Option, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - - /// Tool that generated this glTF model. - #[serde(skip_serializing_if = "Option::is_none")] - pub generator: Option, - - /// The minimum glTF version that this asset targets. - #[serde(rename = "minVersion")] - #[serde(skip_serializing_if = "Option::is_none")] - pub min_version: Option, - - /// The glTF version of this asset. - pub version: String, -} - -impl Default for Asset { - fn default() -> Self { - Self { - copyright: None, - extensions: Default::default(), - extras: Default::default(), - generator: None, - min_version: None, - version: "2.0".to_string(), - } - } -} diff --git a/gltf-json/src/buffer.rs b/gltf-json/src/buffer.rs deleted file mode 100644 index 7e0dbef0..00000000 --- a/gltf-json/src/buffer.rs +++ /dev/null @@ -1,165 +0,0 @@ -use crate::validation::{Checked, Error, USize64, Validate}; -use crate::{extensions, Extras, Index, Path, Root}; -use gltf_derive::Validate; -use serde::{de, ser}; -use serde_derive::{Deserialize, Serialize}; -use std::fmt; - -/// Corresponds to `GL_ARRAY_BUFFER`. -pub const ARRAY_BUFFER: u32 = 34_962; - -/// Corresponds to `GL_ELEMENT_ARRAY_BUFFER`. -pub const ELEMENT_ARRAY_BUFFER: u32 = 34_963; - -/// The minimum byte stride. -pub const MIN_BYTE_STRIDE: usize = 4; - -/// The maximum byte stride. -pub const MAX_BYTE_STRIDE: usize = 252; - -/// All valid GPU buffer targets. -pub const VALID_TARGETS: &[u32] = &[ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER]; - -/// Specifies the target a GPU buffer should be bound to. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Target { - /// Corresponds to `GL_ARRAY_BUFFER`. - ArrayBuffer = 1, - - /// Corresponds to `GL_ELEMENT_ARRAY_BUFFER`. - ElementArrayBuffer, -} - -impl ser::Serialize for Target { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - match *self { - Target::ArrayBuffer => serializer.serialize_u32(ARRAY_BUFFER), - Target::ElementArrayBuffer => serializer.serialize_u32(ELEMENT_ARRAY_BUFFER), - } - } -} - -/// Distance between individual items in a buffer view, measured in bytes. -#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct Stride(pub usize); - -impl Validate for Stride { - fn validate(&self, _root: &Root, path: P, report: &mut R) - where - P: Fn() -> Path, - R: FnMut(&dyn Fn() -> Path, Error), - { - if self.0 < MIN_BYTE_STRIDE || self.0 > MAX_BYTE_STRIDE { - report(&path, Error::Invalid); - } - } -} - -/// A buffer points to binary data representing geometry, animations, or skins. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct Buffer { - /// The length of the buffer in bytes. - #[serde(default, rename = "byteLength")] - pub byte_length: USize64, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// The uri of the buffer. Relative paths are relative to the .gltf file. - /// Instead of referencing an external file, the uri can also be a data-uri. - #[serde(skip_serializing_if = "Option::is_none")] - pub uri: Option, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// A view into a buffer generally representing a subset of the buffer. -/// -/// -/// -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct View { - /// The parent `Buffer`. - pub buffer: Index, - - /// The length of the `BufferView` in bytes. - #[serde(rename = "byteLength")] - pub byte_length: USize64, - - /// Offset into the parent buffer in bytes. - #[serde( - default, - rename = "byteOffset", - skip_serializing_if = "Option::is_none" - )] - pub byte_offset: Option, - - /// The stride in bytes between vertex attributes or other interleavable data. - /// - /// When zero, data is assumed to be tightly packed. - #[serde(rename = "byteStride")] - #[serde(skip_serializing_if = "Option::is_none")] - pub byte_stride: Option, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// Optional target the buffer should be bound to. - #[serde(skip_serializing_if = "Option::is_none")] - pub target: Option>, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_TARGETS) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - use self::Target::*; - use crate::validation::Checked::*; - Ok(match value as u32 { - ARRAY_BUFFER => Valid(ArrayBuffer), - ELEMENT_ARRAY_BUFFER => Valid(ElementArrayBuffer), - _ => Invalid, - }) - } - } - deserializer.deserialize_u64(Visitor) - } -} diff --git a/gltf-json/src/camera.rs b/gltf-json/src/camera.rs deleted file mode 100644 index e7c23d3b..00000000 --- a/gltf-json/src/camera.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::validation::{Checked, Error}; -use crate::{extensions, Extras, Path, Root}; -use gltf_derive::Validate; -use serde::{de, ser}; -use serde_derive::{Deserialize, Serialize}; -use std::fmt; - -/// All valid camera types. -pub const VALID_CAMERA_TYPES: &[&str] = &["perspective", "orthographic"]; - -/// Specifies the camera type. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Type { - /// A perspective projection. - Perspective = 1, - - /// An orthographic projection. - Orthographic, -} - -/// A camera's projection. -/// -/// A node can reference a camera to apply a transform to place the camera in the -/// scene. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -#[gltf(validate_hook = "camera_validate_hook")] -pub struct Camera { - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// An orthographic camera containing properties to create an orthographic - /// projection matrix. - #[serde(skip_serializing_if = "Option::is_none")] - pub orthographic: Option, - - /// A perspective camera containing properties to create a perspective - /// projection matrix. - #[serde(skip_serializing_if = "Option::is_none")] - pub perspective: Option, - - /// Specifies if the camera uses a perspective or orthographic projection. - #[serde(rename = "type")] - pub type_: Checked, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -fn camera_validate_hook(camera: &Camera, _root: &Root, path: P, report: &mut R) -where - P: Fn() -> Path, - R: FnMut(&dyn Fn() -> Path, Error), -{ - if camera.orthographic.is_none() && camera.perspective.is_none() { - report(&path, Error::Missing); - } -} - -/// Values for an orthographic camera. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct Orthographic { - /// The horizontal magnification of the view. - pub xmag: f32, - - /// The vertical magnification of the view. - pub ymag: f32, - - /// The distance to the far clipping plane. - pub zfar: f32, - - /// The distance to the near clipping plane. - pub znear: f32, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// Values for a perspective camera. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct Perspective { - /// Aspect ratio of the field of view. - #[serde(rename = "aspectRatio")] - #[serde(skip_serializing_if = "Option::is_none")] - pub aspect_ratio: Option, - - /// The vertical field of view in radians. - pub yfov: f32, - - /// The distance to the far clipping plane. - #[serde(skip_serializing_if = "Option::is_none")] - pub zfar: Option, - - /// The distance to the near clipping plane. - pub znear: f32, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_CAMERA_TYPES) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - use self::Type::*; - use crate::validation::Checked::*; - Ok(match value { - "perspective" => Valid(Perspective), - "orthographic" => Valid(Orthographic), - _ => Invalid, - }) - } - } - deserializer.deserialize_str(Visitor) - } -} - -impl ser::Serialize for Type { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - match *self { - Type::Perspective => serializer.serialize_str("perspective"), - Type::Orthographic => serializer.serialize_str("orthographic"), - } - } -} diff --git a/gltf-json/src/extensions/accessor.rs b/gltf-json/src/extensions/accessor.rs deleted file mode 100644 index 109d2597..00000000 --- a/gltf-json/src/extensions/accessor.rs +++ /dev/null @@ -1,30 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// Contains data structures for sparse storage. -pub mod sparse { - use super::*; - - /// Indices of those attributes that deviate from their initialization value. - #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] - pub struct Indices {} - - /// Sparse storage of attributes that deviate from their initialization value. - #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] - pub struct Sparse {} - - /// Array of size `count * number_of_components` storing the displaced - /// accessor attributes pointed by `accessor::sparse::Indices`. - #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] - pub struct Values {} -} - -/// A typed view into a buffer view. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Accessor { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} diff --git a/gltf-json/src/extensions/animation.rs b/gltf-json/src/extensions/animation.rs deleted file mode 100644 index f12da808..00000000 --- a/gltf-json/src/extensions/animation.rs +++ /dev/null @@ -1,24 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// A keyframe animation. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Animation { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// Targets an animation's sampler at a node's property. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Channel {} - -/// The index of the node and TRS property that an animation channel targets. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Target {} - -/// Defines a keyframe graph but not its target. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Sampler {} diff --git a/gltf-json/src/extensions/asset.rs b/gltf-json/src/extensions/asset.rs deleted file mode 100644 index 8c375987..00000000 --- a/gltf-json/src/extensions/asset.rs +++ /dev/null @@ -1,6 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; - -/// Metadata about the glTF asset. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Asset {} diff --git a/gltf-json/src/extensions/buffer.rs b/gltf-json/src/extensions/buffer.rs deleted file mode 100644 index d8169ae6..00000000 --- a/gltf-json/src/extensions/buffer.rs +++ /dev/null @@ -1,20 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// A buffer points to binary data representing geometry, animations, or skins. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Buffer { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// A view into a buffer generally representing a subset of the buffer. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct View { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} diff --git a/gltf-json/src/extensions/camera.rs b/gltf-json/src/extensions/camera.rs deleted file mode 100644 index 1e35a47a..00000000 --- a/gltf-json/src/extensions/camera.rs +++ /dev/null @@ -1,31 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// A camera's projection. -/// -/// A node can reference a camera to apply a transform to place the camera in the -/// scene. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Camera { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// Values for an orthographic camera. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Orthographic { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// Values for a perspective camera. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Perspective { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} diff --git a/gltf-json/src/extensions/image.rs b/gltf-json/src/extensions/image.rs deleted file mode 100644 index f9a3046b..00000000 --- a/gltf-json/src/extensions/image.rs +++ /dev/null @@ -1,12 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// Image data used to create a texture. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Image { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} diff --git a/gltf-json/src/extensions/material.rs b/gltf-json/src/extensions/material.rs deleted file mode 100644 index c649fc60..00000000 --- a/gltf-json/src/extensions/material.rs +++ /dev/null @@ -1,417 +0,0 @@ -#[allow(unused_imports)] // different features use different imports -use crate::{material::StrengthFactor, texture, validation::Validate, Extras}; -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// The material appearance of a primitive. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Material { - #[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] - #[serde( - default, - rename = "KHR_materials_pbrSpecularGlossiness", - skip_serializing_if = "Option::is_none" - )] - pub pbr_specular_glossiness: Option, - - #[cfg(feature = "KHR_materials_unlit")] - #[serde( - default, - rename = "KHR_materials_unlit", - skip_serializing_if = "Option::is_none" - )] - pub unlit: Option, - - #[cfg(feature = "KHR_materials_transmission")] - #[serde( - default, - rename = "KHR_materials_transmission", - skip_serializing_if = "Option::is_none" - )] - pub transmission: Option, - - #[cfg(feature = "KHR_materials_volume")] - #[serde( - default, - rename = "KHR_materials_volume", - skip_serializing_if = "Option::is_none" - )] - pub volume: Option, - - #[cfg(feature = "KHR_materials_specular")] - #[serde( - default, - rename = "KHR_materials_specular", - skip_serializing_if = "Option::is_none" - )] - pub specular: Option, - - #[cfg(feature = "KHR_materials_ior")] - #[serde( - default, - rename = "KHR_materials_ior", - skip_serializing_if = "Option::is_none" - )] - pub ior: Option, - - #[cfg(feature = "KHR_materials_emissive_strength")] - #[serde( - default, - rename = "KHR_materials_emissive_strength", - skip_serializing_if = "Option::is_none" - )] - pub emissive_strength: Option, - - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// A set of parameter values that are used to define the metallic-roughness -/// material model from Physically-Based Rendering (PBR) methodology. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct PbrMetallicRoughness { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// A set of parameter values that are used to define the specular-glossiness -/// material model from Physically-Based Rendering (PBR) methodology. -/// -/// This model supports more materials than metallic-roughness, at the cost of -/// increased memory use. When both are available, specular-glossiness should be -/// preferred. -#[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[serde(default, rename_all = "camelCase")] -pub struct PbrSpecularGlossiness { - /// The material's diffuse factor. - /// - /// The RGBA components of the reflected diffuse color of the - /// material. Metals have a diffuse value of `[0.0, 0.0, 0.0]`. The fourth - /// component (A) is the alpha coverage of the material. The `alphaMode` - /// property specifies how alpha is interpreted. The values are linear. - pub diffuse_factor: PbrDiffuseFactor, - - /// The diffuse texture. - /// - /// This texture contains RGB(A) components of the reflected diffuse color - /// of the material in sRGB color space. If the fourth component (A) is - /// present, it represents the alpha coverage of the material. Otherwise, an - /// alpha of 1.0 is assumed. The `alphaMode` property specifies how alpha is - /// interpreted. The stored texels must not be premultiplied. - #[serde(skip_serializing_if = "Option::is_none")] - pub diffuse_texture: Option, - - /// The material's specular factor. - pub specular_factor: PbrSpecularFactor, - - /// The glossiness or smoothness of the material. - /// - /// A value of 1.0 means the material has full glossiness or is perfectly - /// smooth. A value of 0.0 means the material has no glossiness or is - /// completely rough. This value is linear. - pub glossiness_factor: StrengthFactor, - - /// The specular-glossiness texture. - /// - /// A RGBA texture, containing the specular color of the material (RGB - /// components) and its glossiness (A component). The values are in sRGB - /// space. - #[serde(skip_serializing_if = "Option::is_none")] - pub specular_glossiness_texture: Option, - - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, - - /// Optional application specific data. - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// Defines the normal texture of a material. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct NormalTexture { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// Defines the occlusion texture of a material. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct OcclusionTexture { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// The diffuse factor of a material. -#[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct PbrDiffuseFactor(pub [f32; 4]); - -#[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] -impl Default for PbrDiffuseFactor { - fn default() -> Self { - PbrDiffuseFactor([1.0, 1.0, 1.0, 1.0]) - } -} - -#[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] -impl Validate for PbrDiffuseFactor {} - -/// The specular factor of a material. -#[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct PbrSpecularFactor(pub [f32; 3]); - -#[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] -impl Default for PbrSpecularFactor { - fn default() -> Self { - PbrSpecularFactor([1.0, 1.0, 1.0]) - } -} - -#[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] -impl Validate for PbrSpecularFactor {} - -/// Empty struct that should be present for primitives which should not be shaded with the PBR shading model. -#[cfg(feature = "KHR_materials_unlit")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Unlit {} - -/// A number in the inclusive range [0.0, 1.0] with a default value of 0.0. -#[cfg(feature = "KHR_materials_transmission")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct TransmissionFactor(pub f32); - -#[cfg(feature = "KHR_materials_transmission")] -impl Default for TransmissionFactor { - fn default() -> Self { - TransmissionFactor(0.0) - } -} - -#[cfg(feature = "KHR_materials_transmission")] -impl Validate for TransmissionFactor {} - -#[cfg(feature = "KHR_materials_transmission")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[serde(default, rename_all = "camelCase")] -pub struct Transmission { - /// The base percentage of light that is transmitted through the surface. - /// - /// The amount of light that is transmitted by the surface rather than diffusely re-emitted. - /// This is a percentage of all the light that penetrates a surface (i.e. isn’t specularly reflected) - /// rather than a percentage of the total light that hits a surface. - /// A value of 1.0 means that 100% of the light that penetrates the surface is transmitted through. - pub transmission_factor: TransmissionFactor, - - /// The transmission texture. - /// - /// The R channel of this texture defines the amount of light that is transmitted by the surface - /// rather than diffusely re-emitted. A value of 1.0 in the red channel means that 100% of the light - /// that penetrates the surface (i.e. isn’t specularly reflected) is transmitted through. - /// The value is linear and is multiplied by the transmissionFactor to determine the total transmission value. - #[serde(skip_serializing_if = "Option::is_none")] - pub transmission_texture: Option, - - /// Optional application specific data. - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// A positive number with default value of 1.5 -#[cfg(feature = "KHR_materials_ior")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct IndexOfRefraction(pub f32); - -#[cfg(feature = "KHR_materials_ior")] -impl Default for IndexOfRefraction { - fn default() -> Self { - IndexOfRefraction(1.5) - } -} - -#[cfg(feature = "KHR_materials_ior")] -impl Validate for IndexOfRefraction {} - -#[cfg(feature = "KHR_materials_ior")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[serde(default, rename_all = "camelCase")] -pub struct Ior { - /// The index of refraction. - /// - /// Typical values for the index of refraction range from 1 to 2. - /// In rare cases values greater than 2 are possible. - /// For example, the ior of water is 1.33, and diamond is 2.42 - pub ior: IndexOfRefraction, - - /// Optional application specific data. - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// A positive number with 1.0 as the default value. -#[cfg(feature = "KHR_materials_emissive_strength")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct EmissiveStrengthFactor(pub f32); - -#[cfg(feature = "KHR_materials_emissive_strength")] -impl Default for EmissiveStrengthFactor { - fn default() -> Self { - EmissiveStrengthFactor(1.0) - } -} - -#[cfg(feature = "KHR_materials_emissive_strength")] -impl Validate for EmissiveStrengthFactor {} - -#[cfg(feature = "KHR_materials_emissive_strength")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[serde(default, rename_all = "camelCase")] -pub struct EmissiveStrength { - /// The factor by which to scale the emissive factor or emissive texture. - pub emissive_strength: EmissiveStrengthFactor, -} - -/// A number in the inclusive range [0.0, +inf] with a default value of 0.0. -#[cfg(feature = "KHR_materials_volume")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct ThicknessFactor(pub f32); - -#[cfg(feature = "KHR_materials_volume")] -impl Default for ThicknessFactor { - fn default() -> Self { - ThicknessFactor(0.0) - } -} - -#[cfg(feature = "KHR_materials_volume")] -impl Validate for ThicknessFactor {} - -/// A number in the inclusive range [0.0, +inf] with a default value of +inf. -#[cfg(feature = "KHR_materials_volume")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct AttenuationDistance(pub f32); - -#[cfg(feature = "KHR_materials_volume")] -impl Default for AttenuationDistance { - fn default() -> Self { - AttenuationDistance(f32::INFINITY) - } -} - -#[cfg(feature = "KHR_materials_volume")] -impl Validate for AttenuationDistance {} - -/// A colour in the inclusive range [[0.0; 3], [1.0; 3]] with a default value of [1.0; 3]. -#[cfg(feature = "KHR_materials_volume")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct AttenuationColor(pub [f32; 3]); - -#[cfg(feature = "KHR_materials_volume")] -impl Default for AttenuationColor { - fn default() -> Self { - AttenuationColor([1.0, 1.0, 1.0]) - } -} - -#[cfg(feature = "KHR_materials_volume")] -impl Validate for AttenuationColor {} - -#[cfg(feature = "KHR_materials_volume")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[serde(default, rename_all = "camelCase")] -pub struct Volume { - /// The thickness of the volume beneath the surface. The value is - /// given in the coordinate space of the mesh. If the value is 0 - /// the material is thin-walled. Otherwise the material is a - /// volume boundary. The `doubleSided` property has no effect on - /// volume boundaries. Range is [0, +inf). - pub thickness_factor: ThicknessFactor, - - /// A texture that defines the thickness, stored in the G channel. - /// This will be multiplied by `thickness_factor`. Range is [0, 1]. - #[serde(skip_serializing_if = "Option::is_none")] - pub thickness_texture: Option, - - /// Density of the medium given as the average distance that light - /// travels in the medium before interacting with a particle. The - /// value is given in world space. Range is (0, +inf). - pub attenuation_distance: AttenuationDistance, - - /// The color that white light turns into due to absorption when - /// reaching the attenuation distance. - pub attenuation_color: AttenuationColor, - - /// Optional application specific data. - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// A number in the inclusive range [0.0, +inf] with a default value of 1.0. -#[cfg(feature = "KHR_materials_specular")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct SpecularFactor(pub f32); - -#[cfg(feature = "KHR_materials_specular")] -impl Default for SpecularFactor { - fn default() -> Self { - SpecularFactor(1.0) - } -} - -#[cfg(feature = "KHR_materials_specular")] -impl Validate for SpecularFactor {} - -/// A colour in the inclusive range [[0.0; 3], [1.0; 3]] with a default value of [1.0; 3]. -#[cfg(feature = "KHR_materials_specular")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct SpecularColorFactor(pub [f32; 3]); - -#[cfg(feature = "KHR_materials_specular")] -impl Default for SpecularColorFactor { - fn default() -> Self { - SpecularColorFactor([1.0, 1.0, 1.0]) - } -} - -#[cfg(feature = "KHR_materials_specular")] -impl Validate for SpecularColorFactor {} - -#[cfg(feature = "KHR_materials_specular")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[serde(default, rename_all = "camelCase")] -pub struct Specular { - /// The strength of the specular reflection. - pub specular_factor: SpecularFactor, - - /// A texture that defines the strength of the specular reflection, - /// stored in the alpha (`A`) channel. This will be multiplied by - /// `specular_factor`. - #[serde(skip_serializing_if = "Option::is_none")] - pub specular_texture: Option, - - /// The F0 color of the specular reflection (linear RGB). - pub specular_color_factor: SpecularColorFactor, - - /// A texture that defines the F0 color of the specular reflection, - /// stored in the `RGB` channels and encoded in sRGB. This texture - /// will be multiplied by `specular_color_factor`. - #[serde(skip_serializing_if = "Option::is_none")] - pub specular_color_texture: Option, - - /// Optional application specific data. - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} diff --git a/gltf-json/src/extensions/mesh.rs b/gltf-json/src/extensions/mesh.rs deleted file mode 100644 index c17bc9b6..00000000 --- a/gltf-json/src/extensions/mesh.rs +++ /dev/null @@ -1,43 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// A set of primitives to be rendered. -/// -/// A node can contain one or more meshes and its transform places the meshes in -/// the scene. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Mesh { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// Geometry to be rendered with the given material. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Primitive { - #[cfg(feature = "KHR_materials_variants")] - #[serde( - default, - rename = "KHR_materials_variants", - skip_serializing_if = "Option::is_none" - )] - pub khr_materials_variants: Option, - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -#[cfg(feature = "KHR_materials_variants")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct KhrMaterialsVariants { - pub mappings: Vec, -} - -#[cfg(feature = "KHR_materials_variants")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Mapping { - pub material: u32, - pub variants: Vec, -} diff --git a/gltf-json/src/extensions/mod.rs b/gltf-json/src/extensions/mod.rs deleted file mode 100644 index 611a8652..00000000 --- a/gltf-json/src/extensions/mod.rs +++ /dev/null @@ -1,73 +0,0 @@ -/// Contains `Accessor` and other related data structures. -pub mod accessor; - -/// Contains `Animation` and other related data structures. -pub mod animation; - -/// Contains `Asset` metadata. -pub mod asset; - -/// Contains `Buffer`, `View`, and other related data structures. -pub mod buffer; - -/// Contains `Camera` and other related data structures. -pub mod camera; - -/// Contains `Image` and other related data structures. -pub mod image; - -/// Contains `Material` and other related data structures. -pub mod material; - -/// Contains `Mesh` and other related data structures. -pub mod mesh; - -/// Contains `Root`. -pub mod root; - -/// Contains `Scene`, `Node`, and other related data structures. -pub mod scene; - -/// Contains `Skin` and other related data structures. -pub mod skin; - -/// Contains `Texture`, `Sampler`, and other related data structures. -pub mod texture; - -pub use self::root::Root; - -/// Names of glTF 2.0 extensions enabled by the user. -pub const ENABLED_EXTENSIONS: &[&str] = &[ - #[cfg(feature = "KHR_lights_punctual")] - "KHR_lights_punctual", - #[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] - "KHR_materials_pbrSpecularGlossiness", - #[cfg(feature = "KHR_materials_unlit")] - "KHR_materials_unlit", - #[cfg(feature = "KHR_texture_transform")] - "KHR_texture_transform", - #[cfg(feature = "KHR_materials_transmission")] - "KHR_materials_transmission", - #[cfg(feature = "KHR_materials_ior")] - "KHR_materials_ior", - #[cfg(feature = "KHR_materials_emissive_strength")] - "KHR_materials_emissive_strength", - // Allowlisted texture extensions. Processing is delegated to the user. - #[cfg(feature = "allow_empty_texture")] - "KHR_texture_basisu", - #[cfg(feature = "allow_empty_texture")] - "EXT_texture_webp", - #[cfg(feature = "allow_empty_texture")] - "MSFT_texture_dds", -]; - -/// Names of glTF 2.0 extensions supported by the library. -pub const SUPPORTED_EXTENSIONS: &[&str] = &[ - "KHR_lights_punctual", - "KHR_materials_pbrSpecularGlossiness", - "KHR_materials_unlit", - "KHR_texture_transform", - "KHR_materials_transmission", - "KHR_materials_ior", - "KHR_materials_emissive_strength", -]; diff --git a/gltf-json/src/extensions/root.rs b/gltf-json/src/extensions/root.rs deleted file mode 100644 index 0883d138..00000000 --- a/gltf-json/src/extensions/root.rs +++ /dev/null @@ -1,118 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// The root object of a glTF 2.0 asset. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Root { - #[cfg(feature = "KHR_lights_punctual")] - #[serde( - default, - rename = "KHR_lights_punctual", - skip_serializing_if = "Option::is_none" - )] - pub khr_lights_punctual: Option, - - #[cfg(feature = "KHR_materials_variants")] - #[serde( - default, - rename = "KHR_materials_variants", - skip_serializing_if = "Option::is_none" - )] - pub khr_materials_variants: Option, - - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -#[cfg(feature = "KHR_lights_punctual")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct KhrLightsPunctual { - /// Lights at this node. - pub lights: Vec, -} - -#[cfg(feature = "KHR_lights_punctual")] -impl crate::root::Get for crate::Root { - fn get( - &self, - id: crate::Index, - ) -> Option<&crate::extensions::scene::khr_lights_punctual::Light> { - if let Some(extensions) = self.extensions.as_ref() { - if let Some(khr_lights_punctual) = extensions.khr_lights_punctual.as_ref() { - khr_lights_punctual.lights.get(id.value()) - } else { - None - } - } else { - None - } - } -} - -#[cfg(feature = "KHR_lights_punctual")] -impl AsRef<[crate::extensions::scene::khr_lights_punctual::Light]> for crate::Root { - fn as_ref(&self) -> &[crate::extensions::scene::khr_lights_punctual::Light] { - self.extensions - .as_ref() - .and_then(|extensions| extensions.khr_lights_punctual.as_ref()) - .map(|khr_lights_punctual| khr_lights_punctual.lights.as_slice()) - .unwrap_or(&[]) - } -} -#[cfg(feature = "KHR_lights_punctual")] -impl AsMut> for crate::Root { - fn as_mut(&mut self) -> &mut Vec { - &mut self - .extensions - .get_or_insert_with(Default::default) - .khr_lights_punctual - .get_or_insert_with(Default::default) - .lights - } -} - -#[cfg(feature = "KHR_materials_variants")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct KhrMaterialsVariants { - pub variants: Vec, -} - -#[cfg(feature = "KHR_materials_variants")] -impl crate::root::Get for crate::Root { - fn get( - &self, - id: crate::Index, - ) -> Option<&crate::extensions::scene::khr_materials_variants::Variant> { - self.extensions - .as_ref()? - .khr_materials_variants - .as_ref()? - .variants - .get(id.value()) - } -} - -#[cfg(feature = "KHR_materials_variants")] -impl AsRef<[crate::extensions::scene::khr_materials_variants::Variant]> for crate::Root { - fn as_ref(&self) -> &[crate::extensions::scene::khr_materials_variants::Variant] { - self.extensions - .as_ref() - .and_then(|extensions| extensions.khr_materials_variants.as_ref()) - .map(|khr_materials_variants| khr_materials_variants.variants.as_slice()) - .unwrap_or(&[]) - } -} -#[cfg(feature = "KHR_materials_variants")] -impl AsMut> for crate::Root { - fn as_mut(&mut self) -> &mut Vec { - &mut self - .extensions - .get_or_insert_with(Default::default) - .khr_materials_variants - .get_or_insert_with(Default::default) - .variants - } -} diff --git a/gltf-json/src/extensions/scene.rs b/gltf-json/src/extensions/scene.rs deleted file mode 100644 index 68a212d3..00000000 --- a/gltf-json/src/extensions/scene.rs +++ /dev/null @@ -1,230 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// A node in the node hierarchy. When the node contains `skin`, all -/// `mesh.primitives` must contain `JOINTS_0` and `WEIGHTS_0` attributes. -/// A node can have either a `matrix` or any combination of -/// `translation`/`rotation`/`scale` (TRS) properties. TRS properties are converted -/// to matrices and postmultiplied in the `T * R * S` order to compose the -/// transformation matrix; first the scale is applied to the vertices, then the -/// rotation, and then the translation. If none are provided, the transform is the -/// identity. When a node is targeted for animation (referenced by an -/// animation.channel.target), only TRS properties may be present; `matrix` will not -/// be present. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Node { - #[cfg(feature = "KHR_lights_punctual")] - #[serde( - default, - rename = "KHR_lights_punctual", - skip_serializing_if = "Option::is_none" - )] - pub khr_lights_punctual: Option, - - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -#[cfg(feature = "KHR_lights_punctual")] -pub mod khr_lights_punctual { - use crate::validation::{Checked, Error}; - use crate::{Extras, Index, Path, Root}; - use gltf_derive::Validate; - use serde::{de, ser}; - use serde_derive::{Deserialize, Serialize}; - use std::fmt; - - /// All valid light types. - pub const VALID_TYPES: &[&str] = &["directional", "point", "spot"]; - - #[derive(Clone, Debug, Deserialize, Serialize, Validate)] - pub struct KhrLightsPunctual { - pub light: Index, - } - - /// Specifies the light type. - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] - pub enum Type { - /// Directional lights act as though they are infinitely far away and emit light in - /// the direction of the local -z axis. This light type inherits the orientation of - /// the node that it belongs to; position and scale are ignored except for their - /// effect on the inherited node orientation. Because it is at an infinite distance, - /// the light is not attenuated. Its intensity is defined in lumens per metre squared, - /// or lux (lm/m^2). - Directional = 1, - - /// Point lights emit light in all directions from their position in space; rotation - /// and scale are ignored except for their effect on the inherited node position. The - /// brightness of the light attenuates in a physically correct manner as distance - /// increases from the light's position (i.e. brightness goes like the inverse square - /// of the distance). Point light intensity is defined in candela, which is lumens per - /// square radian (lm/sr)." - Point, - - /// Spot lights emit light in a cone in the direction of the local -z axis. The angle - /// and falloff of the cone is defined using two numbers, the innerConeAngle and outer - /// ConeAngle. As with point lights, the brightness also attenuates in a physically - /// correct manner as distance increases from the light's position (i.e. brightness - /// goes like the inverse square of the distance). Spot light intensity refers to the - /// brightness inside the innerConeAngle (and at the location of the light) and is - /// defined in candela, which is lumens per square radian (lm/sr). Engines that don't - /// support two angles for spotlights should use outerConeAngle as the spotlight angle - /// (leaving innerConeAngle to implicitly be 0). - Spot, - } - - #[derive(Clone, Debug, Deserialize, Serialize, Validate)] - #[gltf(validate_hook = "light_validate_hook")] - pub struct Light { - /// Color of the light source. - #[serde(default = "color_default")] - pub color: [f32; 3], - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option>, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - - /// Intensity of the light source. `point` and `spot` lights use luminous intensity - /// in candela (lm/sr) while `directional` lights use illuminance in lux (lm/m^2). - #[serde(default = "intensity_default")] - pub intensity: f32, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// A distance cutoff at which the light's intensity may be considered to have reached - /// zero. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub range: Option, - - /// Spot light parameters. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub spot: Option, - - /// Specifies the light type. - #[serde(rename = "type")] - pub type_: Checked, - } - - fn light_validate_hook(light: &Light, _root: &Root, path: P, report: &mut R) - where - P: Fn() -> Path, - R: FnMut(&dyn Fn() -> Path, Error), - { - if let Checked::Valid(ty) = light.type_.as_ref() { - if *ty == Type::Spot && light.spot.is_none() { - report(&|| path().field("spot"), Error::Missing); - } - } - } - - fn color_default() -> [f32; 3] { - [1.0, 1.0, 1.0] - } - - fn intensity_default() -> f32 { - 1.0 - } - - /// Spot light parameters. - #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] - #[serde(rename_all = "camelCase")] - pub struct Spot { - /// Angle in radians from centre of spotlight where falloff begins. - #[serde(default)] - pub inner_cone_angle: f32, - - /// Angle in radians from centre of spotlight where falloff ends. - #[serde(default = "outer_cone_angle_default")] - pub outer_cone_angle: f32, - } - - fn outer_cone_angle_default() -> f32 { - std::f32::consts::FRAC_PI_4 - } - - impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_TYPES) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - use self::Type::*; - use crate::validation::Checked::*; - Ok(match value { - "directional" => Valid(Directional), - "point" => Valid(Point), - "spot" => Valid(Spot), - _ => Invalid, - }) - } - } - deserializer.deserialize_str(Visitor) - } - } - - impl ser::Serialize for Type { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_str(match *self { - Type::Directional => "directional", - Type::Point => "point", - Type::Spot => "spot", - }) - } - } -} - -#[cfg(feature = "KHR_materials_variants")] -pub mod khr_materials_variants { - use crate::validation::{Error, Validate}; - use crate::{Path, Root}; - use serde_derive::{Deserialize, Serialize}; - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct Variant { - pub name: String, - } - - impl Validate for Variant { - fn validate(&self, root: &Root, path: P, report: &mut R) - where - P: Fn() -> Path, - R: FnMut(&dyn Fn() -> Path, Error), - { - self.name.validate(root, || path().field("name"), report); - } - } -} - -/// The root `Node`s of a scene. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Scene { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} diff --git a/gltf-json/src/extensions/skin.rs b/gltf-json/src/extensions/skin.rs deleted file mode 100644 index 418ce5ab..00000000 --- a/gltf-json/src/extensions/skin.rs +++ /dev/null @@ -1,12 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// Joints and matrices defining a skin. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Skin { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} diff --git a/gltf-json/src/extensions/texture.rs b/gltf-json/src/extensions/texture.rs deleted file mode 100644 index 276720c0..00000000 --- a/gltf-json/src/extensions/texture.rs +++ /dev/null @@ -1,114 +0,0 @@ -#[cfg(feature = "KHR_texture_transform")] -use crate::{extras::Extras, validation::Validate}; -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// Texture sampler properties for filtering and wrapping modes. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Sampler { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// A texture and its sampler. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Texture { - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -/// Reference to a `Texture`. -pub struct Info { - #[cfg(feature = "KHR_texture_transform")] - #[serde( - default, - rename = "KHR_texture_transform", - skip_serializing_if = "Option::is_none" - )] - pub texture_transform: Option, - #[cfg(feature = "extensions")] - #[serde(default, flatten)] - pub others: Map, -} - -/// Many techniques can be used to optimize resource usage for a 3d scene. -/// Chief among them is the ability to minimize the number of textures the GPU must load. -/// To achieve this, many engines encourage packing many objects' low-resolution textures into a single large texture atlas. -/// The region of the resulting atlas that corresponds with each object is then defined by vertical and horizontal offsets, -/// and the width and height of the region. -/// -/// To support this use case, this extension adds `offset`, `rotation`, and `scale` properties to textureInfo structures. -/// These properties would typically be implemented as an affine transform on the UV coordinates. -#[cfg(feature = "KHR_texture_transform")] -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[serde(default, rename_all = "camelCase")] -pub struct TextureTransform { - // The offset of the UV coordinate origin as a factor of the texture dimensions. - pub offset: TextureTransformOffset, - - /// Rotate the UVs by this many radians counter-clockwise around the origin. - /// This is equivalent to a similar rotation of the image clockwise. - pub rotation: TextureTransformRotation, - - /// The scale factor applied to the components of the UV coordinates. - pub scale: TextureTransformScale, - - /// Overrides the textureInfo texCoord value if supplied, and if this extension is supported. - pub tex_coord: Option, - - /// Optional application specific data. - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// The offset of the UV coordinate origin as a factor of the texture dimensions. -#[cfg(feature = "KHR_texture_transform")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct TextureTransformOffset(pub [f32; 2]); - -#[cfg(feature = "KHR_texture_transform")] -impl Default for TextureTransformOffset { - fn default() -> Self { - Self([0.0, 0.0]) - } -} - -#[cfg(feature = "KHR_texture_transform")] -impl Validate for TextureTransformOffset {} - -/// Rotate the UVs by this many radians counter-clockwise around the origin. -/// This is equivalent to a similar rotation of the image clockwise. -#[cfg(feature = "KHR_texture_transform")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct TextureTransformRotation(pub f32); - -#[cfg(feature = "KHR_texture_transform")] -impl Default for TextureTransformRotation { - fn default() -> Self { - Self(0.0) - } -} - -#[cfg(feature = "KHR_texture_transform")] -impl Validate for TextureTransformRotation {} - -/// The scale factor applied to the components of the UV coordinates. -#[cfg(feature = "KHR_texture_transform")] -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct TextureTransformScale(pub [f32; 2]); - -#[cfg(feature = "KHR_texture_transform")] -impl Default for TextureTransformScale { - fn default() -> Self { - Self([1.0, 1.0]) - } -} - -#[cfg(feature = "KHR_texture_transform")] -impl Validate for TextureTransformScale {} diff --git a/gltf-json/src/extras.rs b/gltf-json/src/extras.rs deleted file mode 100644 index 4d5f9e15..00000000 --- a/gltf-json/src/extras.rs +++ /dev/null @@ -1,33 +0,0 @@ -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -use std::fmt; - -#[cfg(feature = "extras")] -pub use serde_json::value::RawValue; - -/// Data type of the `extras` attribute on all glTF objects. -#[cfg(feature = "extras")] -pub type Extras = Option<::std::boxed::Box>; - -/// Data type of the `extras` attribute on all glTF objects. -#[cfg(not(feature = "extras"))] -pub type Extras = Void; - -/// Type representing no user-defined data. -#[derive(Clone, Default, Serialize, Deserialize, Validate)] -pub struct Void { - #[serde(default, skip_serializing)] - _allow_unknown_fields: (), -} - -impl fmt::Debug for Void { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{{}}") - } -} - -impl fmt::Display for Void { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{{}}") - } -} diff --git a/gltf-json/src/image.rs b/gltf-json/src/image.rs deleted file mode 100644 index 2ee1b62a..00000000 --- a/gltf-json/src/image.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::validation::Validate; -use crate::{buffer, extensions, Extras, Index}; -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; - -/// All valid MIME types. -pub const VALID_MIME_TYPES: &[&str] = &["image/jpeg", "image/png"]; - -/// Image data used to create a texture. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct Image { - /// The index of the buffer view that contains the image. Use this instead of - /// the image's uri property. - #[serde(rename = "bufferView")] - #[serde(skip_serializing_if = "Option::is_none")] - pub buffer_view: Option>, - - /// The image's MIME type. - #[serde(rename = "mimeType")] - #[serde(skip_serializing_if = "Option::is_none")] - pub mime_type: Option, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// The uri of the image. Relative paths are relative to the .gltf file. - /// Instead of referencing an external file, the uri can also be a data-uri. - /// The image format must be jpg or png. - #[serde(skip_serializing_if = "Option::is_none")] - pub uri: Option, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// An image MIME type. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct MimeType(pub String); - -impl Validate for MimeType {} diff --git a/gltf-json/src/lib.rs b/gltf-json/src/lib.rs deleted file mode 100644 index 95251508..00000000 --- a/gltf-json/src/lib.rs +++ /dev/null @@ -1,107 +0,0 @@ -/// Contains `Accessor` and other related data structures. -pub mod accessor; - -/// Contains `Animation` and other related data structures. -pub mod animation; - -/// Contains `Asset` metadata. -pub mod asset; - -/// Contains `Buffer`, `View`, and other related data structures. -pub mod buffer; - -/// Contains `Camera` and other related data structures. -pub mod camera; - -/// Contains extension specific data structures and the names of all -/// 2.0 extensions supported by the library. -pub mod extensions; - -/// Contains `Extras`. -pub mod extras; - -/// Contains `Image` and other related data structures. -pub mod image; - -/// Contains `Material` and other related data structures. -pub mod material; - -/// Contains `Mesh` and other related data structures. -pub mod mesh; - -/// Contains `Path`. -pub mod path; - -/// Contains `Root`. -pub mod root; - -/// Contains `Scene`, `Node`, and other related data structures. -pub mod scene; - -/// Contains `Skin` and other related data structures. -pub mod skin; - -/// Contains `Texture`, `Sampler`, and other related data structures. -pub mod texture; - -/// Contains functions that validate glTF JSON data against the specification. -pub mod validation; - -#[doc(inline)] -pub use accessor::Accessor; -#[doc(inline)] -pub use animation::Animation; -#[doc(inline)] -pub use asset::Asset; -#[doc(inline)] -pub use buffer::Buffer; -#[doc(inline)] -pub use camera::Camera; -#[doc(inline)] -pub use image::Image; -#[doc(inline)] -pub use material::Material; -#[doc(inline)] -pub use mesh::Mesh; -#[doc(inline)] -pub use scene::Node; -#[doc(inline)] -pub use scene::Scene; -#[doc(inline)] -pub use skin::Skin; -#[doc(inline)] -pub use texture::Texture; - -#[doc(inline)] -pub use self::extras::Extras; -#[doc(inline)] -pub use self::path::Path; -#[doc(inline)] -pub use self::root::Index; -#[doc(inline)] -pub use self::root::Root; - -#[doc(inline)] -pub use serde_json::Error; -#[doc(inline)] -pub use serde_json::Value; - -/// Re-exports of `serde_json` deserialization functions. -/// -/// This module re-exports the generic serde deserialization functions -/// so that one can deserialize data structures other than `Root` without -/// being bound to a specific version of `serde_json`. -pub mod deserialize { - pub use serde_json::{from_reader, from_slice, from_str, from_value}; -} - -/// Re-exports of `serde_json` serialization functions. -/// -/// This module re-exports the generic serde serialization functions -/// so that one can serialize data structures other than `Root` without -/// being bound to a specific version of `serde_json`. -pub mod serialize { - pub use serde_json::{ - to_string, to_string_pretty, to_value, to_vec, to_vec_pretty, to_writer, to_writer_pretty, - }; -} diff --git a/gltf-json/src/material.rs b/gltf-json/src/material.rs deleted file mode 100644 index e247f992..00000000 --- a/gltf-json/src/material.rs +++ /dev/null @@ -1,311 +0,0 @@ -use crate::validation::{Checked, Validate}; -use crate::{extensions, texture, Extras, Index}; -use gltf_derive::Validate; -use serde::{de, ser}; -use serde_derive::{Deserialize, Serialize}; -use std::fmt; - -/// All valid alpha modes. -pub const VALID_ALPHA_MODES: &[&str] = &["OPAQUE", "MASK", "BLEND"]; - -/// The alpha rendering mode of a material. -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub enum AlphaMode { - /// The alpha value is ignored and the rendered output is fully opaque. - Opaque = 1, - - /// The rendered output is either fully opaque or fully transparent depending on - /// the alpha value and the specified alpha cutoff value. - Mask, - - /// The alpha value is used, to determine the transparency of the rendered output. - /// The alpha cutoff value is ignored. - Blend, -} - -impl ser::Serialize for AlphaMode { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - match *self { - AlphaMode::Opaque => serializer.serialize_str("OPAQUE"), - AlphaMode::Mask => serializer.serialize_str("MASK"), - AlphaMode::Blend => serializer.serialize_str("BLEND"), - } - } -} - -/// The material appearance of a primitive. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[serde(default)] -pub struct Material { - /// The alpha cutoff value of the material. - #[serde(rename = "alphaCutoff")] - //#[cfg_attr(feature = "alphaCutoff", serde(skip_serializing_if = "Option::is_none"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub alpha_cutoff: Option, - - /// The alpha rendering mode of the material. - /// - /// The material's alpha rendering mode enumeration specifying the - /// interpretation of the alpha value of the main factor and texture. - /// - /// * In `Opaque` mode (default) the alpha value is ignored and the rendered - /// output is fully opaque. - /// - /// * In `Mask` mode, the rendered output is either fully opaque or fully - /// transparent depending on the alpha value and the specified alpha cutoff - /// value. - /// - /// * In `Blend` mode, the alpha value is used to composite the source and - /// destination areas and the rendered output is combined with the - /// background using the normal painting operation (i.e. the Porter and - /// Duff over operator). - #[serde(rename = "alphaMode")] - pub alpha_mode: Checked, - - /// Specifies whether the material is double-sided. - /// - /// * When this value is false, back-face culling is enabled. - /// - /// * When this value is true, back-face culling is disabled and double sided - /// lighting is enabled. - /// - /// The back-face must have its normals reversed before the lighting - /// equation is evaluated. - #[serde(rename = "doubleSided")] - pub double_sided: bool, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// A set of parameter values that are used to define the metallic-roughness - /// material model from Physically-Based Rendering (PBR) methodology. When not - /// specified, all the default values of `pbrMetallicRoughness` apply. - #[serde(default, rename = "pbrMetallicRoughness")] - pub pbr_metallic_roughness: PbrMetallicRoughness, - - /// A tangent space normal map. The texture contains RGB components in linear - /// space. Each texel represents the XYZ components of a normal vector in - /// tangent space. Red [0 to 255] maps to X [-1 to 1]. Green [0 to 255] maps to - /// Y [-1 to 1]. Blue [128 to 255] maps to Z [1/255 to 1]. The normal vectors - /// use OpenGL conventions where +X is right and +Y is up. +Z points toward the - /// viewer. - #[serde(rename = "normalTexture")] - #[serde(skip_serializing_if = "Option::is_none")] - pub normal_texture: Option, - - /// The occlusion map texture. The occlusion values are sampled from the R - /// channel. Higher values indicate areas that should receive full indirect - /// lighting and lower values indicate no indirect lighting. These values are - /// linear. If other channels are present (GBA), they are ignored for occlusion - /// calculations. - #[serde(rename = "occlusionTexture")] - #[serde(skip_serializing_if = "Option::is_none")] - pub occlusion_texture: Option, - - /// The emissive map controls the color and intensity of the light being emitted - /// by the material. This texture contains RGB components in sRGB color space. - /// If a fourth component (A) is present, it is ignored. - #[serde(rename = "emissiveTexture")] - #[serde(skip_serializing_if = "Option::is_none")] - pub emissive_texture: Option, - - /// The emissive color of the material. - #[serde(rename = "emissiveFactor")] - pub emissive_factor: EmissiveFactor, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// A set of parameter values that are used to define the metallic-roughness -/// material model from Physically-Based Rendering (PBR) methodology. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[serde(default)] -pub struct PbrMetallicRoughness { - /// The material's base color factor. - #[serde(rename = "baseColorFactor")] - pub base_color_factor: PbrBaseColorFactor, - - /// The base color texture. - #[serde(rename = "baseColorTexture")] - #[serde(skip_serializing_if = "Option::is_none")] - pub base_color_texture: Option, - - /// The metalness of the material. - #[serde(rename = "metallicFactor")] - pub metallic_factor: StrengthFactor, - - /// The roughness of the material. - /// - /// * A value of 1.0 means the material is completely rough. - /// * A value of 0.0 means the material is completely smooth. - #[serde(rename = "roughnessFactor")] - pub roughness_factor: StrengthFactor, - - /// The metallic-roughness texture. - /// - /// This texture has two components: - /// - /// The metalness values are sampled from the B channel. - /// The roughness values are sampled from the G channel. - /// These values are linear. If other channels are present (R or A), - /// they are ignored for metallic-roughness calculations. - #[serde(rename = "metallicRoughnessTexture")] - #[serde(skip_serializing_if = "Option::is_none")] - pub metallic_roughness_texture: Option, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// Defines the normal texture of a material. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct NormalTexture { - /// The index of the texture. - pub index: Index, - - /// The scalar multiplier applied to each normal vector of the texture. - /// - /// This value is ignored if normalTexture is not specified. - #[serde(default = "material_normal_texture_scale_default")] - pub scale: f32, - - /// The set index of the texture's `TEXCOORD` attribute. - #[serde(default, rename = "texCoord")] - pub tex_coord: u32, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -fn material_normal_texture_scale_default() -> f32 { - 1.0 -} - -/// Defines the occlusion texture of a material. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct OcclusionTexture { - /// The index of the texture. - pub index: Index, - - /// The scalar multiplier controlling the amount of occlusion applied. - #[serde(default)] - pub strength: StrengthFactor, - - /// The set index of the texture's `TEXCOORD` attribute. - #[serde(default, rename = "texCoord")] - pub tex_coord: u32, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -/// The alpha cutoff value of a material. -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct AlphaCutoff(pub f32); - -/// The emissive color of a material. -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] -pub struct EmissiveFactor(pub [f32; 3]); - -/// The base color factor of a material. -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct PbrBaseColorFactor(pub [f32; 4]); - -/// A number in the inclusive range [0.0, 1.0] with a default value of 1.0. -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct StrengthFactor(pub f32); - -impl Default for AlphaCutoff { - fn default() -> Self { - AlphaCutoff(0.5) - } -} - -impl Validate for AlphaCutoff {} - -impl Default for AlphaMode { - fn default() -> Self { - AlphaMode::Opaque - } -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_ALPHA_MODES) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - use self::AlphaMode::*; - use crate::validation::Checked::*; - Ok(match value { - "OPAQUE" => Valid(Opaque), - "MASK" => Valid(Mask), - "BLEND" => Valid(Blend), - _ => Invalid, - }) - } - } - deserializer.deserialize_str(Visitor) - } -} - -impl Validate for EmissiveFactor {} - -impl Default for PbrBaseColorFactor { - fn default() -> Self { - PbrBaseColorFactor([1.0, 1.0, 1.0, 1.0]) - } -} - -impl Validate for PbrBaseColorFactor {} - -impl Default for StrengthFactor { - fn default() -> Self { - StrengthFactor(1.0) - } -} - -impl Validate for StrengthFactor {} diff --git a/gltf-json/src/mesh.rs b/gltf-json/src/mesh.rs deleted file mode 100644 index 14aeb44f..00000000 --- a/gltf-json/src/mesh.rs +++ /dev/null @@ -1,377 +0,0 @@ -use crate::validation::{Checked, Error}; -use crate::{accessor, extensions, material, Extras, Index}; -use gltf_derive::Validate; -use serde::{de, ser}; -use serde_derive::{Deserialize, Serialize}; -use serde_json::from_value; -use std::collections::BTreeMap; -use std::fmt; - -/// Corresponds to `GL_POINTS`. -pub const POINTS: u32 = 0; - -/// Corresponds to `GL_LINES`. -pub const LINES: u32 = 1; - -/// Corresponds to `GL_LINE_LOOP`. -pub const LINE_LOOP: u32 = 2; - -/// Corresponds to `GL_LINE_STRIP`. -pub const LINE_STRIP: u32 = 3; - -/// Corresponds to `GL_TRIANGLES`. -pub const TRIANGLES: u32 = 4; - -/// Corresponds to `GL_TRIANGLE_STRIP`. -pub const TRIANGLE_STRIP: u32 = 5; - -/// Corresponds to `GL_TRIANGLE_FAN`. -pub const TRIANGLE_FAN: u32 = 6; - -/// All valid primitive rendering modes. -pub const VALID_MODES: &[u32] = &[ - POINTS, - LINES, - LINE_LOOP, - LINE_STRIP, - TRIANGLES, - TRIANGLE_STRIP, - TRIANGLE_FAN, -]; - -/// All valid semantic names for Morph targets. -pub const VALID_MORPH_TARGETS: &[&str] = &["POSITION", "NORMAL", "TANGENT"]; - -/// The type of primitives to render. -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] -pub enum Mode { - /// Corresponds to `GL_POINTS`. - Points = 1, - - /// Corresponds to `GL_LINES`. - Lines, - - /// Corresponds to `GL_LINE_LOOP`. - LineLoop, - - /// Corresponds to `GL_LINE_STRIP`. - LineStrip, - - /// Corresponds to `GL_TRIANGLES`. - Triangles, - - /// Corresponds to `GL_TRIANGLE_STRIP`. - TriangleStrip, - - /// Corresponds to `GL_TRIANGLE_FAN`. - TriangleFan, -} - -/// A set of primitives to be rendered. -/// -/// A node can contain one or more meshes and its transform places the meshes in -/// the scene. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct Mesh { - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// Defines the geometry to be renderered with a material. - pub primitives: Vec, - - /// Defines the weights to be applied to the morph targets. - #[serde(skip_serializing_if = "Option::is_none")] - pub weights: Option>, -} - -/// Geometry to be rendered with the given material. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -#[gltf(validate_hook = "primitive_validate_hook")] -pub struct Primitive { - /// Maps attribute semantic names to the `Accessor`s containing the - /// corresponding attribute data. - pub attributes: BTreeMap, Index>, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - - /// The index of the accessor that contains the indices. - #[serde(skip_serializing_if = "Option::is_none")] - pub indices: Option>, - - /// The index of the material to apply to this primitive when rendering - #[serde(skip_serializing_if = "Option::is_none")] - pub material: Option>, - - /// The type of primitives to render. - #[serde(default, skip_serializing_if = "is_primitive_mode_default")] - pub mode: Checked, - - /// An array of Morph Targets, each Morph Target is a dictionary mapping - /// attributes (only `POSITION`, `NORMAL`, and `TANGENT` supported) to their - /// deviations in the Morph Target. - #[serde(skip_serializing_if = "Option::is_none")] - pub targets: Option>, -} - -fn is_primitive_mode_default(mode: &Checked) -> bool { - *mode == Checked::Valid(Mode::Triangles) -} - -fn primitive_validate_hook(primitive: &Primitive, root: &crate::Root, path: P, report: &mut R) -where - P: Fn() -> crate::Path, - R: FnMut(&dyn Fn() -> crate::Path, crate::validation::Error), -{ - let position_path = &|| path().field("attributes").key("POSITION"); - if let Some(pos_accessor_index) = primitive - .attributes - .get(&Checked::Valid(Semantic::Positions)) - { - // spec: POSITION accessor **must** have `min` and `max` properties defined. - let pos_accessor = &root.accessors[pos_accessor_index.value()]; - - let min_path = &|| position_path().field("min"); - if let Some(ref min) = pos_accessor.min { - if from_value::<[f32; 3]>(min.clone()).is_err() { - report(min_path, Error::Invalid); - } - } else { - report(min_path, Error::Missing); - } - - let max_path = &|| position_path().field("max"); - if let Some(ref max) = pos_accessor.max { - if from_value::<[f32; 3]>(max.clone()).is_err() { - report(max_path, Error::Invalid); - } - } else { - report(max_path, Error::Missing); - } - } else { - report(position_path, Error::Missing); - } -} - -/// A dictionary mapping attributes to their deviations in the Morph Target. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct MorphTarget { - /// XYZ vertex position displacements of type `[f32; 3]`. - #[serde(rename = "POSITION")] - #[serde(skip_serializing_if = "Option::is_none")] - pub positions: Option>, - - /// XYZ vertex normal displacements of type `[f32; 3]`. - #[serde(rename = "NORMAL")] - #[serde(skip_serializing_if = "Option::is_none")] - pub normals: Option>, - - /// XYZ vertex tangent displacements of type `[f32; 3]`. - #[serde(rename = "TANGENT")] - #[serde(skip_serializing_if = "Option::is_none")] - pub tangents: Option>, -} - -/// Vertex attribute semantic name. -#[derive(Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)] -pub enum Semantic { - /// Extra attribute name. - #[cfg(feature = "extras")] - Extras(String), - - /// XYZ vertex positions. - Positions, - - /// XYZ vertex normals. - Normals, - - /// XYZW vertex tangents where the `w` component is a sign value indicating the - /// handedness of the tangent basis. - Tangents, - - /// RGB or RGBA vertex color. - Colors(u32), - - /// UV texture co-ordinates. - TexCoords(u32), - - /// Joint indices. - Joints(u32), - - /// Joint weights. - Weights(u32), -} - -impl Default for Mode { - fn default() -> Mode { - Mode::Triangles - } -} - -impl Mode { - /// Returns the equivalent `GLenum`. - pub fn as_gl_enum(self) -> u32 { - match self { - Mode::Points => POINTS, - Mode::Lines => LINES, - Mode::LineLoop => LINE_LOOP, - Mode::LineStrip => LINE_STRIP, - Mode::Triangles => TRIANGLES, - Mode::TriangleStrip => TRIANGLE_STRIP, - Mode::TriangleFan => TRIANGLE_FAN, - } - } -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_MODES) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - use self::Mode::*; - use crate::validation::Checked::*; - Ok(match value as u32 { - POINTS => Valid(Points), - LINES => Valid(Lines), - LINE_LOOP => Valid(LineLoop), - LINE_STRIP => Valid(LineStrip), - TRIANGLES => Valid(Triangles), - TRIANGLE_STRIP => Valid(TriangleStrip), - TRIANGLE_FAN => Valid(TriangleFan), - _ => Invalid, - }) - } - } - deserializer.deserialize_u64(Visitor) - } -} - -impl ser::Serialize for Mode { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_u32(self.as_gl_enum()) - } -} - -impl Semantic { - fn checked(s: &str) -> Checked { - use self::Semantic::*; - use crate::validation::Checked::*; - match s { - "NORMAL" => Valid(Normals), - "POSITION" => Valid(Positions), - "TANGENT" => Valid(Tangents), - #[cfg(feature = "extras")] - _ if s.starts_with('_') => Valid(Extras(s[1..].to_string())), - _ if s.starts_with("COLOR_") => match s["COLOR_".len()..].parse() { - Ok(set) => Valid(Colors(set)), - Err(_) => Invalid, - }, - _ if s.starts_with("TEXCOORD_") => match s["TEXCOORD_".len()..].parse() { - Ok(set) => Valid(TexCoords(set)), - Err(_) => Invalid, - }, - _ if s.starts_with("JOINTS_") => match s["JOINTS_".len()..].parse() { - Ok(set) => Valid(Joints(set)), - Err(_) => Invalid, - }, - _ if s.starts_with("WEIGHTS_") => match s["WEIGHTS_".len()..].parse() { - Ok(set) => Valid(Weights(set)), - Err(_) => Invalid, - }, - _ => Invalid, - } - } -} - -impl ser::Serialize for Semantic { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl ToString for Semantic { - fn to_string(&self) -> String { - use self::Semantic::*; - match *self { - Positions => "POSITION".into(), - Normals => "NORMAL".into(), - Tangents => "TANGENT".into(), - Colors(set) => format!("COLOR_{}", set), - TexCoords(set) => format!("TEXCOORD_{}", set), - Joints(set) => format!("JOINTS_{}", set), - Weights(set) => format!("WEIGHTS_{}", set), - #[cfg(feature = "extras")] - Extras(ref name) => format!("_{}", name), - } - } -} - -impl ToString for Checked { - fn to_string(&self) -> String { - match *self { - Checked::Valid(ref semantic) => semantic.to_string(), - Checked::Invalid => "".into(), - } - } -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "semantic name") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - Ok(Semantic::checked(value)) - } - } - deserializer.deserialize_str(Visitor) - } -} diff --git a/gltf-json/src/scene.rs b/gltf-json/src/scene.rs deleted file mode 100644 index 5bc045c6..00000000 --- a/gltf-json/src/scene.rs +++ /dev/null @@ -1,114 +0,0 @@ -use crate::validation::Validate; -use crate::{camera, extensions, mesh, scene, skin, Extras, Index}; -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; - -/// A node in the node hierarchy. When the node contains `skin`, all -/// `mesh.primitives` must contain `JOINTS_0` and `WEIGHTS_0` attributes. -/// A node can have either a `matrix` or any combination of -/// `translation`/`rotation`/`scale` (TRS) properties. TRS properties are converted -/// to matrices and postmultiplied in the `T * R * S` order to compose the -/// transformation matrix; first the scale is applied to the vertices, then the -/// rotation, and then the translation. If none are provided, the transform is the -/// identity. When a node is targeted for animation (referenced by an -/// animation.channel.target), only TRS properties may be present; `matrix` will not -/// be present. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -pub struct Node { - /// The index of the camera referenced by this node. - #[serde(skip_serializing_if = "Option::is_none")] - pub camera: Option>, - - /// The indices of this node's children. - #[serde(skip_serializing_if = "Option::is_none")] - pub children: Option>>, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - - /// 4x4 column-major transformation matrix. - /// - /// glTF 2.0 specification: - /// When a node is targeted for animation (referenced by an - /// animation.channel.target), only TRS properties may be present; - /// matrix will not be present. - /// - /// TODO: Ensure that .matrix is set to None or otherwise skipped during - /// serialization, if the node is targeted for animation. - /// - #[serde(skip_serializing_if = "Option::is_none")] - pub matrix: Option<[f32; 16]>, - - /// The index of the mesh in this node. - #[serde(skip_serializing_if = "Option::is_none")] - pub mesh: Option>, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// The node's unit quaternion rotation in the order (x, y, z, w), where w is - /// the scalar. - #[serde(skip_serializing_if = "Option::is_none")] - pub rotation: Option, - - /// The node's non-uniform scale. - #[serde(skip_serializing_if = "Option::is_none")] - pub scale: Option<[f32; 3]>, - - /// The node's translation. - #[serde(skip_serializing_if = "Option::is_none")] - pub translation: Option<[f32; 3]>, - - /// The index of the skin referenced by this node. - #[serde(skip_serializing_if = "Option::is_none")] - pub skin: Option>, - - /// The weights of the instantiated Morph Target. Number of elements must match - /// the number of Morph Targets of used mesh. - #[serde(skip_serializing_if = "Option::is_none")] - pub weights: Option>, -} - -/// The root `Node`s of a scene. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -pub struct Scene { - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// The indices of each root node. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub nodes: Vec>, -} - -/// Unit quaternion rotation in the order (x, y, z, w), where w is the scalar. -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct UnitQuaternion(pub [f32; 4]); - -impl Default for UnitQuaternion { - fn default() -> Self { - UnitQuaternion([0.0, 0.0, 0.0, 1.0]) - } -} - -impl Validate for UnitQuaternion {} diff --git a/gltf-json/src/texture.rs b/gltf-json/src/texture.rs deleted file mode 100644 index c232f908..00000000 --- a/gltf-json/src/texture.rs +++ /dev/null @@ -1,479 +0,0 @@ -use crate::validation::{Checked, Validate}; -use crate::{extensions, image, Extras, Index}; -use gltf_derive::Validate; -use serde::{de, ser}; -use serde_derive::{Deserialize, Serialize}; -use std::fmt; - -/// Corresponds to `GL_NEAREST`. -pub const NEAREST: u32 = 9728; - -/// Corresponds to `GL_LINEAR`. -pub const LINEAR: u32 = 9729; - -/// Corresponds to `GL_NEAREST_MIPMAP_NEAREST`. -pub const NEAREST_MIPMAP_NEAREST: u32 = 9984; - -/// Corresponds to `GL_LINEAR_MIPMAP_NEAREST`. -pub const LINEAR_MIPMAP_NEAREST: u32 = 9985; - -/// Corresponds to `GL_NEAREST_MIPMAP_LINEAR`. -pub const NEAREST_MIPMAP_LINEAR: u32 = 9986; - -/// Corresponds to `GL_LINEAR_MIPMAP_LINEAR`. -pub const LINEAR_MIPMAP_LINEAR: u32 = 9987; - -/// Corresponds to `GL_CLAMP_TO_EDGE`. -pub const CLAMP_TO_EDGE: u32 = 33_071; - -/// Corresponds to `GL_MIRRORED_REPEAT`. -pub const MIRRORED_REPEAT: u32 = 33_648; - -/// Corresponds to `GL_REPEAT`. -pub const REPEAT: u32 = 10_497; - -/// All valid magnification filters. -pub const VALID_MAG_FILTERS: &[u32] = &[NEAREST, LINEAR]; - -/// All valid minification filters. -pub const VALID_MIN_FILTERS: &[u32] = &[ - NEAREST, - LINEAR, - NEAREST_MIPMAP_NEAREST, - LINEAR_MIPMAP_NEAREST, - NEAREST_MIPMAP_LINEAR, - LINEAR_MIPMAP_LINEAR, -]; - -/// All valid wrapping modes. -pub const VALID_WRAPPING_MODES: &[u32] = &[CLAMP_TO_EDGE, MIRRORED_REPEAT, REPEAT]; - -/// Magnification filter. -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] -pub enum MagFilter { - /// Corresponds to `GL_NEAREST`. - Nearest = 1, - - /// Corresponds to `GL_LINEAR`. - Linear, -} - -impl MagFilter { - /// OpenGL enum - pub fn as_gl_enum(&self) -> u32 { - match *self { - MagFilter::Nearest => NEAREST, - MagFilter::Linear => LINEAR, - } - } -} - -/// Minification filter. -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] -pub enum MinFilter { - /// Corresponds to `GL_NEAREST`. - Nearest = 1, - - /// Corresponds to `GL_LINEAR`. - Linear, - - /// Corresponds to `GL_NEAREST_MIPMAP_NEAREST`. - NearestMipmapNearest, - - /// Corresponds to `GL_LINEAR_MIPMAP_NEAREST`. - LinearMipmapNearest, - - /// Corresponds to `GL_NEAREST_MIPMAP_LINEAR`. - NearestMipmapLinear, - - /// Corresponds to `GL_LINEAR_MIPMAP_LINEAR`. - LinearMipmapLinear, -} - -impl MinFilter { - /// Returns the corresponding OpenGL enum value. - pub fn as_gl_enum(&self) -> u32 { - match *self { - MinFilter::Nearest => NEAREST, - MinFilter::Linear => LINEAR, - MinFilter::NearestMipmapNearest => NEAREST_MIPMAP_NEAREST, - MinFilter::LinearMipmapNearest => LINEAR_MIPMAP_NEAREST, - MinFilter::NearestMipmapLinear => NEAREST_MIPMAP_LINEAR, - MinFilter::LinearMipmapLinear => LINEAR_MIPMAP_LINEAR, - } - } -} - -/// Texture co-ordinate wrapping mode. -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] -pub enum WrappingMode { - /// Corresponds to `GL_CLAMP_TO_EDGE`. - ClampToEdge = 1, - - /// Corresponds to `GL_MIRRORED_REPEAT`. - MirroredRepeat, - - /// Corresponds to `GL_REPEAT`. - Repeat, -} - -impl WrappingMode { - /// Returns the corresponding OpenGL enum value. - pub fn as_gl_enum(&self) -> u32 { - match *self { - WrappingMode::ClampToEdge => CLAMP_TO_EDGE, - WrappingMode::MirroredRepeat => MIRRORED_REPEAT, - WrappingMode::Repeat => REPEAT, - } - } -} - -/// Texture sampler properties for filtering and wrapping modes. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[serde(default)] -pub struct Sampler { - /// Magnification filter. - #[serde(rename = "magFilter")] - #[serde(skip_serializing_if = "Option::is_none")] - pub mag_filter: Option>, - - /// Minification filter. - #[serde(rename = "minFilter")] - #[serde(skip_serializing_if = "Option::is_none")] - pub min_filter: Option>, - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// `s` wrapping mode. - #[serde(default, rename = "wrapS")] - pub wrap_s: Checked, - - /// `t` wrapping mode. - #[serde(default, rename = "wrapT")] - pub wrap_t: Checked, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -fn source_default() -> Index { - Index::new(u32::MAX) -} - -fn source_is_empty(source: &Index) -> bool { - source.value() == u32::MAX as usize -} - -fn source_validate(source: &Index, root: &crate::Root, path: P, report: &mut R) -where - P: Fn() -> crate::Path, - R: FnMut(&dyn Fn() -> crate::Path, crate::validation::Error), -{ - if cfg!(feature = "allow_empty_texture") { - if !source_is_empty(source) { - source.validate(root, path, report); - } - } else if source_is_empty(source) { - report(&path, crate::validation::Error::Missing); - } else { - source.validate(root, &path, report); - } -} - -/// A texture and its sampler. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Texture { - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] - pub name: Option, - - /// The index of the sampler used by this texture. - #[serde(skip_serializing_if = "Option::is_none")] - pub sampler: Option>, - - /// The index of the image used by this texture. - #[serde(default = "source_default", skip_serializing_if = "source_is_empty")] - pub source: Index, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -impl Validate for Texture { - fn validate(&self, root: &crate::Root, path: P, report: &mut R) - where - P: Fn() -> crate::Path, - R: FnMut(&dyn Fn() -> crate::Path, crate::validation::Error), - { - self.sampler - .validate(root, || path().field("sampler"), report); - self.extensions - .validate(root, || path().field("extensions"), report); - source_validate(&self.source, root, || path().field("source"), report); - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] -/// Reference to a `Texture`. -pub struct Info { - /// The index of the texture. - pub index: Index, - - /// The set index of the texture's `TEXCOORD` attribute. - #[serde(default, rename = "texCoord")] - pub tex_coord: u32, - - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_MAG_FILTERS) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - use self::MagFilter::*; - use crate::validation::Checked::*; - Ok(match value as u32 { - NEAREST => Valid(Nearest), - LINEAR => Valid(Linear), - _ => Invalid, - }) - } - } - deserializer.deserialize_u64(Visitor) - } -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_MIN_FILTERS) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - use self::MinFilter::*; - use crate::validation::Checked::*; - Ok(match value as u32 { - NEAREST => Valid(Nearest), - LINEAR => Valid(Linear), - NEAREST_MIPMAP_NEAREST => Valid(NearestMipmapNearest), - LINEAR_MIPMAP_NEAREST => Valid(LinearMipmapNearest), - NEAREST_MIPMAP_LINEAR => Valid(NearestMipmapLinear), - LINEAR_MIPMAP_LINEAR => Valid(LinearMipmapLinear), - _ => Invalid, - }) - } - } - deserializer.deserialize_u64(Visitor) - } -} - -impl ser::Serialize for MinFilter { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_u32(self.as_gl_enum()) - } -} - -impl<'de> de::Deserialize<'de> for Checked { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Checked; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "any of: {:?}", VALID_WRAPPING_MODES) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - use self::WrappingMode::*; - use crate::validation::Checked::*; - Ok(match value as u32 { - CLAMP_TO_EDGE => Valid(ClampToEdge), - MIRRORED_REPEAT => Valid(MirroredRepeat), - REPEAT => Valid(Repeat), - _ => Invalid, - }) - } - } - deserializer.deserialize_u64(Visitor) - } -} - -impl ser::Serialize for MagFilter { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_u32(self.as_gl_enum()) - } -} - -impl Default for WrappingMode { - fn default() -> Self { - WrappingMode::Repeat - } -} - -impl ser::Serialize for WrappingMode { - fn serialize(&self, serializer: S) -> Result - where - S: ser::Serializer, - { - serializer.serialize_u32(self.as_gl_enum()) - } -} - -#[cfg(test)] -mod tests { - #[test] - fn deserialize_source() { - let json = r#"{"asset":{"version":"2.0"},"textures":[{"source": 0}]}"#; - let root = serde_json::from_str::(json).unwrap(); - assert_eq!(0, root.textures[0].source.value()); - } - - #[test] - fn deserialize_empty_source() { - let json = r#"{"asset":{"version":"2.0"},"textures":[{}]}"#; - let root = serde_json::from_str::(json).unwrap(); - assert_eq!(u32::MAX as usize, root.textures[0].source.value()); - } - - #[test] - fn serialize_source() { - let root = crate::Root { - textures: vec![crate::Texture { - #[cfg(feature = "names")] - name: None, - sampler: None, - source: crate::Index::new(0), - extensions: None, - extras: Default::default(), - }], - ..Default::default() - }; - let json = serde_json::to_string(&root).unwrap(); - assert_eq!( - r#"{"asset":{"version":"2.0"},"textures":[{"source":0}]}"#, - &json - ); - } - - #[test] - fn serialize_empty_source() { - let root = crate::Root { - textures: vec![crate::Texture { - #[cfg(feature = "names")] - name: None, - sampler: None, - source: crate::Index::new(u32::MAX), - extensions: None, - extras: Default::default(), - }], - ..Default::default() - }; - let json = serde_json::to_string(&root).unwrap(); - assert_eq!(r#"{"asset":{"version":"2.0"},"textures":[{}]}"#, &json); - } - - #[test] - fn validate_source() { - use crate::validation::{Error, Validate}; - use crate::Path; - let json = r#"{"asset":{"version":"2.0"},"textures":[{"source":0}]}"#; - let root = serde_json::from_str::(json).unwrap(); - let mut errors = Vec::new(); - root.textures[0].validate( - &root, - || Path::new().field("textures").index(0), - &mut |path, error| { - errors.push((path(), error)); - }, - ); - assert_eq!(1, errors.len()); - let (path, error) = &errors[0]; - assert_eq!("textures[0].source", path.as_str()); - assert_eq!(Error::IndexOutOfBounds, *error); - } - - #[test] - fn validate_empty_source() { - use crate::validation::{Error, Validate}; - use crate::Path; - let json = r#"{"asset":{"version":"2.0"},"textures":[{}]}"#; - let root = serde_json::from_str::(json).unwrap(); - let mut errors = Vec::new(); - root.textures[0].validate( - &root, - || Path::new().field("textures").index(0), - &mut |path, error| { - errors.push((path(), error)); - }, - ); - if cfg!(feature = "allow_empty_texture") { - assert!(errors.is_empty()); - } else { - assert_eq!(1, errors.len()); - let (path, error) = &errors[0]; - assert_eq!("textures[0].source", path.as_str()); - assert_eq!(Error::Missing, *error); - } - } -} diff --git a/gltf-json/tests/minimal_accessor_invalid.gltf b/gltf-json/tests/minimal_accessor_invalid.gltf deleted file mode 100644 index 46936f47..00000000 --- a/gltf-json/tests/minimal_accessor_invalid.gltf +++ /dev/null @@ -1,53 +0,0 @@ -{ - "scenes" : [ { "nodes" : [ 0 ] } ], - "nodes" : [ { "mesh" : 0 } ], - "meshes" : [ - { - "primitives" : [ { - "attributes" : { "POSITION" : 1 }, - "indices" : 0 - } ] - } - ], - "buffers" : [ - { - "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=", - "byteLength" : 44 - } - ], - "bufferViews" : [ - { - "buffer" : 0, - "byteOffset" : 0, - "byteLength" : 6, - "target" : 34963 - }, - { - "buffer" : 0, - "byteOffset" : 8, - "byteLength" : 36, - "target" : 34962 - } - ], - "accessors" : [ - { - "bufferView" : 0, - "byteOffset" : 0, - "componentType" : 5123, - "count" : 3, - "type" : "SCALAR" - }, - { - "bufferView" : 1, - "byteOffset" : 0, - "componentType" : 5126, - "count" : 3, - "type" : "VEC3", - "max" : [ 1.0, 1.0 ] - } - ], - - "asset" : { - "version" : "2.0" - } -} diff --git a/gltf-json/tests/non_sparse_accessor_without_buffer_view.gltf b/gltf-json/tests/non_sparse_accessor_without_buffer_view.gltf deleted file mode 100644 index 3c5ea07d..00000000 --- a/gltf-json/tests/non_sparse_accessor_without_buffer_view.gltf +++ /dev/null @@ -1,53 +0,0 @@ -{ - "scenes" : [ { "nodes" : [ 0 ] } ], - "nodes" : [ { "mesh" : 0 } ], - "meshes" : [ - { - "primitives" : [ { - "attributes" : { "POSITION" : 1 }, - "indices" : 0 - } ] - } - ], - "buffers" : [ - { - "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=", - "byteLength" : 44 - } - ], - "bufferViews" : [ - { - "buffer" : 0, - "byteOffset" : 0, - "byteLength" : 6, - "target" : 34963 - }, - { - "buffer" : 0, - "byteOffset" : 8, - "byteLength" : 36, - "target" : 34962 - } - ], - "accessors" : [ - { - "byteOffset" : 0, - "componentType" : 5123, - "count" : 3, - "type" : "SCALAR" - }, - { - "bufferView" : 1, - "byteOffset" : 0, - "componentType" : 5126, - "count" : 3, - "type" : "VEC3", - "min" : [ 0.0, 0.0, 0.0 ], - "max" : [ 1.0, 1.0, 1.0 ] - } - ], - - "asset" : { - "version" : "2.0" - } -} diff --git a/gltf-json/tests/test_validation.rs b/gltf-json/tests/test_validation.rs deleted file mode 100644 index 1e868939..00000000 --- a/gltf-json/tests/test_validation.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::{fs, io}; - -use gltf_json::validation::{Error, Validate}; -use gltf_json::Path; - -fn import_json(filename: &str) -> gltf_json::Root { - let file = fs::File::open(filename).unwrap(); - let reader = io::BufReader::new(file); - gltf_json::Root::from_reader(reader).unwrap() -} - -#[test] -fn test_accessor_bounds_validate() { - // file with missing min/max values - let json = import_json("tests/minimal_accessor_invalid.gltf"); - let mut errs = vec![]; - json.validate(&json, gltf_json::Path::new, &mut |path, err| { - errs.push((path(), err)) - }); - assert_eq!( - errs, - [ - ( - Path("meshes[0].primitives[0].attributes[\"POSITION\"].min".into()), - Error::Missing - ), - ( - Path("meshes[0].primitives[0].attributes[\"POSITION\"].max".into()), - Error::Invalid - ) - ] - ); -} - -#[test] -fn test_non_sparse_accessor_without_buffer_view_validate() { - let json = import_json("tests/non_sparse_accessor_without_buffer_view.gltf"); - let mut errs = vec![]; - json.validate(&json, gltf_json::Path::new, &mut |path, err| { - errs.push((path(), err)) - }); - assert_eq!( - errs, - [(Path("accessors[0].bufferView".into()), Error::Missing)] - ); -} diff --git a/src/accessor/mod.rs b/src/accessor/mod.rs index f4d3fc67..2ff18290 100644 --- a/src/accessor/mod.rs +++ b/src/accessor/mod.rs @@ -5,12 +5,12 @@ //! ``` //! # fn run() -> Result<(), Box> { //! # let gltf = gltf::Gltf::open("examples/Box.gltf")?; -//! for accessor in gltf.accessors() { -//! println!("Accessor #{}", accessor.index()); -//! println!("offset: {:?}", accessor.offset()); -//! println!("count: {}", accessor.count()); -//! println!("data_type: {:?}", accessor.data_type()); -//! println!("dimensions: {:?}", accessor.dimensions()); +//! for (index, accessor) in gltf.root.accessors.iter().enumerate() { +//! println!("Accessor #{index}"); +//! println!("offset: {:?}", accessor.byte_offset); +//! println!("count: {:?}", accessor.count); +//! println!("attribute_type: {:?}", accessor.attribute_type); +//! println!("component_type: {:?}", accessor.component_type); //! } //! # Ok(()) //! # } @@ -34,15 +34,15 @@ //! //! ``` //! # fn run() -> Result<(), Box> { -//! # use gltf::accessor::{DataType, Dimensions, Iter}; +//! # use gltf::accessor::{AttributeType, ComponentType, Iter}; //! let (gltf, buffers, _) = gltf::import("examples/Box.gltf")?; -//! let get_buffer_data = |buffer: gltf::Buffer| buffers.get(buffer.index()).map(|x| &*x.0); -//! for accessor in gltf.accessors() { -//! match (accessor.data_type(), accessor.dimensions()) { -//! (DataType::F32, Dimensions::Vec3) => { -//! if let Some(iter) = Iter::<[f32; 3]>::new(accessor, get_buffer_data) { +//! let get_buffer_data = |buffer: gltf::Index| buffers.get(buffer.value()).map(|b| b.0.as_slice()); +//! for (index, accessor) in gltf.accessors.iter().enumerate() { +//! match (accessor.component_type, accessor.attribute_type) { +//! (ComponentType::F32, AttributeType::Vec3) => { +//! if let Some(iter) = Iter::<[f32; 3]>::new(&gltf, gltf::Index::new(index as u32), get_buffer_data) { //! for item in iter { -//! println!("{:?}", item); +//! println!("{item:?}"); //! } //! } //! } @@ -56,145 +56,322 @@ //! # } //! ``` -use crate::{buffer, Document}; - -pub use json::accessor::ComponentType as DataType; -pub use json::accessor::Type as Dimensions; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - /// Utility functions. #[cfg(feature = "utils")] #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] pub mod util; -/// Contains data structures for sparse storage. -pub mod sparse; - #[cfg(feature = "utils")] #[doc(inline)] pub use self::util::{Item, Iter}; +use crate::validation::{Error, USize64, Validate}; +use crate::{buffer, Extras, Index, Path, Root, Stub, UnrecognizedExtensions}; +use serde_json::Value; -/// A typed view into a buffer view. -#[derive(Clone, Debug)] -pub struct Accessor<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::accessor::Accessor, +/// The component data type. +#[derive( + Clone, Copy, Debug, Eq, PartialEq, serde_repr::Deserialize_repr, serde_repr::Serialize_repr, +)] +#[repr(u32)] +pub enum ComponentType { + /// Corresponds to `GL_BYTE`. + I8 = 5120, + /// Corresponds to `GL_UNSIGNED_BYTE`. + U8 = 5121, + /// Corresponds to `GL_SHORT`. + I16 = 5122, + /// Corresponds to `GL_UNSIGNED_SHORT`. + U16 = 5123, + /// Corresponds to `GL_UNSIGNED_INT`. + U32 = 5125, + /// Corresponds to `GL_FLOAT`. + F32 = 5126, } -impl<'a> Accessor<'a> { - /// Constructs an `Accessor`. - pub(crate) fn new( - document: &'a Document, - index: usize, - json: &'a json::accessor::Accessor, - ) -> Self { - Self { - document, - index, - json, +impl Validate for ComponentType {} + +impl From for ComponentType { + fn from(value: sparse::IndexType) -> Self { + match value { + sparse::IndexType::U8 => ComponentType::U8, + sparse::IndexType::U16 => ComponentType::U16, + sparse::IndexType::U32 => ComponentType::U32, } } +} - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index +impl Stub for ComponentType { + fn stub() -> Self { + Self::I8 } +} - /// Returns the size of each component that this accessor describes. - pub fn size(&self) -> usize { - self.data_type().size() * self.dimensions().multiplicity() - } +/// Specifies whether an attribute, vector, or matrix. +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde_derive::Deserialize, serde_derive::Serialize)] +pub enum AttributeType { + /// Scalar quantity. + #[serde(rename = "SCALAR")] + Scalar = 1, + /// 2D vector. + #[serde(rename = "VEC2")] + Vec2, + /// 3D vector. + #[serde(rename = "VEC3")] + Vec3, + /// 4D vector. + #[serde(rename = "VEC4")] + Vec4, + /// 2x2 matrix. + #[serde(rename = "MAT2")] + Mat2, + /// 3x3 matrix. + #[serde(rename = "MAT3")] + Mat3, + /// 4x4 matrix. + #[serde(rename = "MAT4")] + Mat4, +} - /// Returns the buffer view this accessor reads from. - /// - /// This may be `None` if the corresponding accessor is sparse. - pub fn view(&self) -> Option> { - self.json - .buffer_view - .map(|view| self.document.views().nth(view.value()).unwrap()) +impl Validate for AttributeType {} + +impl Stub for AttributeType { + fn stub() -> Self { + Self::Scalar } +} - /// Returns the offset relative to the start of the parent buffer view in bytes. - /// - /// This will be 0 if the corresponding accessor is sparse. - pub fn offset(&self) -> usize { - // TODO: Change this function to return Option in the next - // version and return None for sparse accessors. - self.json.byte_offset.unwrap_or_default().0 as usize +/// Contains data structures for sparse storage. +pub mod sparse { + use crate::validation::{USize64, Validate}; + use crate::{buffer, Extras, Index, Stub, UnrecognizedExtensions}; + + /// Data type specific to sparse indices. + #[derive( + Clone, Copy, Debug, serde_repr::Deserialize_repr, Eq, PartialEq, serde_repr::Serialize_repr, + )] + #[repr(u32)] + pub enum IndexType { + /// Corresponds to `GL_UNSIGNED_BYTE`. + U8 = super::ComponentType::U8 as u32, + /// Corresponds to `GL_UNSIGNED_SHORT`. + U16 = super::ComponentType::U16 as u32, + /// Corresponds to `GL_UNSIGNED_INT`. + U32 = super::ComponentType::U32 as u32, } - /// Returns the number of components within the buffer view - not to be confused - /// with the number of bytes in the buffer view. - pub fn count(&self) -> usize { - self.json.count.0 as usize + impl Validate for IndexType {} + + impl Stub for IndexType { + fn stub() -> Self { + Self::U8 + } } - /// Returns the data type of components in the attribute. - pub fn data_type(&self) -> DataType { - self.json.component_type.unwrap().0 + impl IndexType { + /// Returns the number of bytes this value represents. + pub fn size(self) -> usize { + super::ComponentType::from(self).size() + } + + /// Returns the corresponding `GLenum`. + pub fn as_gl_enum(self) -> u32 { + super::ComponentType::from(self).as_gl_enum() + } } - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) + /// Indices of those attributes that deviate from their initialization value. + #[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + )] + pub struct Indices { + /// The parent buffer view containing the sparse indices. + /// + /// The referenced buffer view must not have `ARRAY_BUFFER` nor + /// `ELEMENT_ARRAY_BUFFER` as its target. + pub buffer_view: Index, + + /// The offset relative to the start of the parent `BufferView` in bytes. + #[serde(default)] + pub byte_offset: USize64, + + /// The data type of each index. + #[serde(rename = "componentType")] + pub index_type: IndexType, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, } - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) + /// Sparse storage of attributes that deviate from their initialization value. + #[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + )] + pub struct Sparse { + /// The number of attributes encoded in this sparse accessor. + pub count: USize64, + + /// Index array of size `count` that points to those accessor attributes + /// that deviate from their initialization value. + /// + /// Indices must strictly increase. + pub indices: Indices, + + /// Array of size `count * number_of_components` storing the displaced + /// accessor attributes pointed by `indices`. + /// + /// Substituted values must have the same `component_type` and number of + /// components as the base `Accessor`. + pub values: Values, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, } - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras + /// Array of size `count * number_of_components` storing the displaced + /// accessor attributes pointed by `accessor::sparse::Indices`. + #[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + )] + pub struct Values { + /// The parent buffer view containing the sparse indices. + /// + /// The referenced buffer view must not have `ARRAY_BUFFER` nor + /// `ELEMENT_ARRAY_BUFFER` as its target. + pub buffer_view: Index, + + /// The offset relative to the start of the parent buffer view in bytes. + #[serde(default)] + pub byte_offset: USize64, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, } +} +/// A typed view into a buffer view. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +#[gltf(validate = "validate_accessor")] +pub struct Accessor { /// Specifies if the attribute is a scalar, vector, or matrix. - pub fn dimensions(&self) -> Dimensions { - self.json.type_.unwrap() - } + #[serde(rename = "type")] + pub attribute_type: AttributeType, + + /// The parent buffer view this accessor reads from. + /// + /// This field can be omitted in sparse accessors. + pub buffer_view: Option>, + + /// The offset relative to the start of the parent `BufferView` in bytes. + /// + /// This field can be omitted in sparse accessors. + pub byte_offset: Option, + + /// The number of components within the buffer view - not to be confused + /// with the number of bytes in the buffer view. + pub count: USize64, + + /// The data type of components in the attribute. + pub component_type: ComponentType, + + /// Minimum value of each component in this attribute. + pub min: Option, + + /// Maximum value of each component in this attribute. + pub max: Option, + + /// Optional user-defined name for this object. + pub name: Option, - /// Returns the minimum value of each component in this attribute. - pub fn min(&self) -> Option { - self.json.min.clone() + /// Specifies whether integer data values should be normalized. + pub normalized: bool, + + /// Sparse storage of attributes that deviate from their initialization + /// value. + pub sparse: Option, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, +} + +fn validate_accessor(accessor: &Accessor, _root: &Root, path: P, report: &mut R) +where + P: Fn() -> Path, + R: FnMut(&dyn Fn() -> Path, Error), +{ + if accessor.sparse.is_none() && accessor.buffer_view.is_none() { + // If sparse is missing, then bufferView must be present. Report that bufferView is + // missing since it is the more common one to require. + report(&|| path().field("bufferView"), Error::Missing); } +} - /// Returns the maximum value of each component in this attribute. - pub fn max(&self) -> Option { - self.json.max.clone() +impl Accessor { + /// Provides the size of each component that this accessor describes. + pub fn size(&self) -> usize { + self.component_type.size() * self.attribute_type.multiplicity() } +} - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(docsrs, doc(cfg(feature = "names")))] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() +impl ComponentType { + /// Returns the number of bytes this value represents. + pub fn size(self) -> usize { + match self { + Self::I8 | Self::U8 => 1, + Self::I16 | Self::U16 => 2, + Self::F32 | Self::U32 => 4, + } } - /// Specifies whether integer data values should be normalized. - pub fn normalized(&self) -> bool { - self.json.normalized + /// Returns the corresponding `GLenum`. + pub fn as_gl_enum(self) -> u32 { + self as u32 } +} - /// Returns sparse storage of attributes that deviate from their initialization - /// value. - pub fn sparse(&self) -> Option> { - self.json - .sparse - .as_ref() - .map(|json| sparse::Sparse::new(self.document, json)) +impl AttributeType { + /// Returns the equivalent number of scalar quantities this type represents. + pub fn multiplicity(&self) -> usize { + match *self { + Self::Scalar => 1, + Self::Vec2 => 2, + Self::Vec3 => 3, + Self::Vec4 | Self::Mat2 => 4, + Self::Mat3 => 9, + Self::Mat4 => 16, + } } } diff --git a/src/accessor/sparse.rs b/src/accessor/sparse.rs deleted file mode 100644 index c80aadf4..00000000 --- a/src/accessor/sparse.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::{buffer, Document}; - -/// The index data type. -#[derive(Clone, Debug)] -pub enum IndexType { - /// Corresponds to `GL_UNSIGNED_BYTE`. - U8 = 5121, - - /// Corresponds to `GL_UNSIGNED_SHORT`. - U16 = 5123, - - /// Corresponds to `GL_UNSIGNED_INT`. - U32 = 5125, -} - -/// Indices of those attributes that deviate from their initialization value. -pub struct Indices<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::accessor::sparse::Indices, -} - -impl<'a> Indices<'a> { - /// Constructs `sparse::Indices`. - pub(crate) fn new(document: &'a Document, json: &'a json::accessor::sparse::Indices) -> Self { - Self { document, json } - } - - /// Returns the buffer view containing the sparse indices. - pub fn view(&self) -> buffer::View<'a> { - self.document - .views() - .nth(self.json.buffer_view.value()) - .unwrap() - } - - /// The offset relative to the start of the parent buffer view in bytes. - pub fn offset(&self) -> usize { - self.json.byte_offset.0 as usize - } - - /// The data type of each index. - pub fn index_type(&self) -> IndexType { - match self.json.component_type.unwrap().0 { - json::accessor::ComponentType::U8 => IndexType::U8, - json::accessor::ComponentType::U16 => IndexType::U16, - json::accessor::ComponentType::U32 => IndexType::U32, - _ => unreachable!(), - } - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } -} - -/// Sparse storage of attributes that deviate from their initialization value. -pub struct Sparse<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::accessor::sparse::Sparse, -} - -impl<'a> Sparse<'a> { - /// Constructs `Sparse`. - pub(crate) fn new(document: &'a Document, json: &'a json::accessor::sparse::Sparse) -> Self { - Self { document, json } - } - - /// Returns the number of attributes encoded in this sparse accessor. - pub fn count(&self) -> usize { - self.json.count.0 as usize - } - - /// Returns an index array of size `count` that points to those accessor - /// attributes that deviate from their initialization value. - pub fn indices(&self) -> Indices<'a> { - Indices::new(self.document, &self.json.indices) - } - - /// Returns an array of size `count * number_of_components`, storing the - /// displaced accessor attributes pointed by `indices`. - pub fn values(&self) -> Values<'a> { - Values::new(self.document, &self.json.values) - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } -} - -/// Array of size `count * number_of_components` storing the displaced accessor -/// attributes pointed by `accessor::sparse::Indices`. -pub struct Values<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::accessor::sparse::Values, -} - -impl<'a> Values<'a> { - /// Constructs `sparse::Values`. - pub(crate) fn new(document: &'a Document, json: &'a json::accessor::sparse::Values) -> Self { - Self { document, json } - } - - /// Returns the buffer view containing the sparse values. - pub fn view(&self) -> buffer::View<'a> { - self.document - .views() - .nth(self.json.buffer_view.value()) - .unwrap() - } - - /// The offset relative to the start of the parent buffer view in bytes. - pub fn offset(&self) -> usize { - self.json.byte_offset.0 as usize - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } -} - -impl IndexType { - /// Returns the number of bytes this value represents. - pub fn size(&self) -> usize { - use self::IndexType::*; - match *self { - U8 => 1, - U16 => 2, - U32 => 4, - } - } -} diff --git a/src/accessor/util.rs b/src/accessor/util.rs index a9f027bd..f8f45a09 100644 --- a/src/accessor/util.rs +++ b/src/accessor/util.rs @@ -1,16 +1,16 @@ -use byteorder::{ByteOrder, LE}; +use byteorder::{ByteOrder, LittleEndian}; use std::marker::PhantomData; use std::{iter, mem}; -use crate::{accessor, buffer}; +use crate::{accessor, buffer, Accessor, Buffer, Index, Root}; fn buffer_view_slice<'a, 's>( - view: buffer::View<'a>, - get_buffer_data: &dyn Fn(buffer::Buffer<'a>) -> Option<&'s [u8]>, + view: &'a buffer::View, + get_buffer_data: &dyn Fn(Index) -> Option<&'s [u8]>, ) -> Option<&'s [u8]> { - let start = view.offset(); - let end = start + view.length(); - get_buffer_data(view.buffer()).and_then(|slice| slice.get(start..end)) + let start = view.offset.value(); + let end = start + view.length.value(); + get_buffer_data(view.buffer).and_then(|slice| slice.get(start..end)) } /// General iterator for an accessor. @@ -33,31 +33,31 @@ impl<'a, T: Item> Iterator for Iter<'a, T> { } } - fn nth(&mut self, nth: usize) -> Option { + fn size_hint(&self) -> (usize, Option) { match self { - Iter::Standard(ref mut iter) => iter.nth(nth), - Iter::Sparse(ref mut iter) => iter.nth(nth), + Iter::Standard(ref iter) => iter.size_hint(), + Iter::Sparse(ref iter) => iter.size_hint(), } } - fn last(self) -> Option { + fn count(self) -> usize { match self { - Iter::Standard(iter) => iter.last(), - Iter::Sparse(iter) => iter.last(), + Iter::Standard(iter) => iter.count(), + Iter::Sparse(iter) => iter.count(), } } - fn count(self) -> usize { + fn last(self) -> Option { match self { - Iter::Standard(iter) => iter.count(), - Iter::Sparse(iter) => iter.count(), + Iter::Standard(iter) => iter.last(), + Iter::Sparse(iter) => iter.last(), } } - fn size_hint(&self) -> (usize, Option) { + fn nth(&mut self, nth: usize) -> Option { match self { - Iter::Standard(ref iter) => iter.size_hint(), - Iter::Sparse(ref iter) => iter.size_hint(), + Iter::Standard(ref mut iter) => iter.nth(nth), + Iter::Sparse(ref mut iter) => iter.nth(nth), } } } @@ -114,15 +114,6 @@ impl<'a, T: Item> SparseIter<'a, T> { /// /// Here `base` is allowed to be `None` when the base buffer view is not explicitly specified. pub fn new( - base: Option>, - indices: SparseIndicesIter<'a>, - values: ItemIter<'a, T>, - ) -> Self { - Self::with_base_count(base, 0, indices, values) - } - - /// Supplemental constructor. - pub fn with_base_count( base: Option>, base_count: usize, indices: SparseIndicesIter<'a>, @@ -205,7 +196,7 @@ impl Item for i8 { impl Item for i16 { fn from_slice(slice: &[u8]) -> Self { - LE::read_i16(slice) + LittleEndian::read_i16(slice) } fn zero() -> Self { 0 @@ -223,7 +214,7 @@ impl Item for u8 { impl Item for u16 { fn from_slice(slice: &[u8]) -> Self { - LE::read_u16(slice) + LittleEndian::read_u16(slice) } fn zero() -> Self { 0 @@ -232,7 +223,7 @@ impl Item for u16 { impl Item for u32 { fn from_slice(slice: &[u8]) -> Self { - LE::read_u32(slice) + LittleEndian::read_u32(slice) } fn zero() -> Self { 0 @@ -241,7 +232,7 @@ impl Item for u32 { impl Item for f32 { fn from_slice(slice: &[u8]) -> Self { - LE::read_f32(slice) + LittleEndian::read_f32(slice) } fn zero() -> Self { 0.0 @@ -303,18 +294,22 @@ impl<'a, T: Item> ItemIter<'a, T> { impl<'a, 's, T: Item> Iter<'s, T> { /// Constructor. - pub fn new(accessor: super::Accessor<'a>, get_buffer_data: F) -> Option> + pub fn new(root: &'a Root, index: Index, get_buffer_data: F) -> Option> where - F: Clone + Fn(buffer::Buffer<'a>) -> Option<&'s [u8]>, + F: Clone + Fn(Index) -> Option<&'s [u8]>, { - match accessor.sparse() { + let accessor = root.get(index)?; + match accessor.sparse.as_ref() { Some(sparse) => { // Using `if let` here instead of map to preserve the early return behavior. - let base_iter = if let Some(view) = accessor.view() { - let stride = view.stride().unwrap_or(mem::size_of::()); - - let start = accessor.offset(); - let end = start + stride * (accessor.count() - 1) + mem::size_of::(); + let base_iter = if let Some(view) = accessor + .buffer_view + .as_ref() + .and_then(|index| root.get(*index)) + { + let stride = view.stride.unwrap_or(mem::size_of::()); + let start = accessor.byte_offset.unwrap_or_default().value(); + let end = start + stride * (accessor.count.value() - 1) + mem::size_of::(); let subslice = buffer_view_slice(view, &get_buffer_data) .and_then(|slice| slice.get(start..end))?; @@ -322,23 +317,22 @@ impl<'a, 's, T: Item> Iter<'s, T> { } else { None }; - let base_count = accessor.count(); - - let indices = sparse.indices(); - let values = sparse.values(); - let sparse_count = sparse.count(); + let sparse_count = sparse.count.value(); + let base_count = accessor.count.value(); + let indices = &sparse.indices; + let values = &sparse.values; let index_iter = { - let view = indices.view(); - let index_size = indices.index_type().size(); - let stride = view.stride().unwrap_or(index_size); + let view = root.get(indices.buffer_view)?; + let index_size = indices.index_type.size(); + let stride = view.stride.unwrap_or(index_size); - let start = indices.offset(); + let start = indices.byte_offset.value(); let end = start + stride * (sparse_count - 1) + index_size; let subslice = buffer_view_slice(view, &get_buffer_data) .and_then(|slice| slice.get(start..end))?; - match indices.index_type() { + match indices.index_type { accessor::sparse::IndexType::U8 => { SparseIndicesIter::U8(ItemIter::new(subslice, stride)) } @@ -352,10 +346,10 @@ impl<'a, 's, T: Item> Iter<'s, T> { }; let value_iter = { - let view = values.view(); - let stride = view.stride().unwrap_or(mem::size_of::()); + let view = root.get(values.buffer_view)?; + let stride = view.stride.unwrap_or(mem::size_of::()); - let start = values.offset(); + let start = values.byte_offset.value(); let end = start + stride * (sparse_count - 1) + mem::size_of::(); let subslice = buffer_view_slice(view, &get_buffer_data) .and_then(|slice| slice.get(start..end))?; @@ -363,7 +357,7 @@ impl<'a, 's, T: Item> Iter<'s, T> { ItemIter::new(subslice, stride) }; - Some(Iter::Sparse(SparseIter::with_base_count( + Some(Iter::Sparse(SparseIter::new( base_iter, base_count, index_iter, value_iter, ))) } @@ -371,26 +365,31 @@ impl<'a, 's, T: Item> Iter<'s, T> { debug_assert_eq!(mem::size_of::(), accessor.size()); debug_assert!(mem::size_of::() > 0); - accessor.view().and_then(|view| { - let stride = view.stride().unwrap_or(mem::size_of::()); - debug_assert!( - stride >= mem::size_of::(), - "Mismatch in stride, expected at least {} stride but found {}", - mem::size_of::(), - stride - ); - - let start = accessor.offset(); - let end = start + stride * (accessor.count() - 1) + mem::size_of::(); - let subslice = buffer_view_slice(view, &get_buffer_data) - .and_then(|slice| slice.get(start..end))?; - - Some(Iter::Standard(ItemIter { - stride, - data: subslice, - _phantom: PhantomData, - })) - }) + accessor + .buffer_view + .as_ref() + .and_then(|index| root.get(*index)) + .and_then(|view| { + let stride = view.stride.unwrap_or(mem::size_of::()); + debug_assert!( + stride >= mem::size_of::(), + "Mismatch in stride, expected at least {} stride but found {}", + mem::size_of::(), + stride + ); + + let start = accessor.byte_offset.unwrap_or_default().value(); + let end = + start + stride * (accessor.count.value() - 1) + mem::size_of::(); + let subslice = buffer_view_slice(view, &get_buffer_data) + .and_then(|slice| slice.get(start..end))?; + + Some(Iter::Standard(ItemIter { + stride, + data: subslice, + _phantom: PhantomData, + })) + }) } } } @@ -418,18 +417,14 @@ impl<'a, T: Item> Iterator for ItemIter<'a, T> { } } - fn nth(&mut self, nth: usize) -> Option { - if let Some(val_data) = self.data.get(nth * self.stride..) { - if val_data.len() >= mem::size_of::() { - let val = T::from_slice(val_data); - self.data = &val_data[self.stride.min(val_data.len())..]; - Some(val) - } else { - None - } - } else { - None - } + fn size_hint(&self) -> (usize, Option) { + let hint = self.data.len() / self.stride + + (self.data.len() % self.stride >= mem::size_of::()) as usize; + (hint, Some(hint)) + } + + fn count(self) -> usize { + self.size_hint().0 } fn last(self) -> Option { @@ -442,13 +437,17 @@ impl<'a, T: Item> Iterator for ItemIter<'a, T> { } } - fn count(self) -> usize { - self.size_hint().0 - } - - fn size_hint(&self) -> (usize, Option) { - let hint = self.data.len() / self.stride - + (self.data.len() % self.stride >= mem::size_of::()) as usize; - (hint, Some(hint)) + fn nth(&mut self, nth: usize) -> Option { + if let Some(val_data) = self.data.get(nth * self.stride..) { + if val_data.len() >= mem::size_of::() { + let val = T::from_slice(val_data); + self.data = &val_data[self.stride.min(val_data.len())..]; + Some(val) + } else { + None + } + } else { + None + } } } diff --git a/src/animation.rs b/src/animation.rs new file mode 100644 index 00000000..a92c2759 --- /dev/null +++ b/src/animation.rs @@ -0,0 +1,200 @@ +use crate::validation::{Error, Validate}; +use crate::{accessor, scene, Extras, Index, Path, Root, Stub, UnrecognizedExtensions}; + +/// Support for the `KHR_animation_pointer` extension. +pub mod khr_animation_pointer { + /// Provides a JSON pointer to the target property. + #[derive( + Clone, + Debug, + Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct Pointer { + /// JSON pointer to the target property. + pub pointer: String, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } +} + +/// Specifies an interpolation algorithm. +#[derive( + Clone, Copy, Debug, Default, serde_derive::Deserialize, Eq, PartialEq, serde_derive::Serialize, +)] +pub enum Interpolation { + /// Linear interpolation. + /// + /// The animated values are linearly interpolated between keyframes. + /// When targeting a rotation, spherical linear interpolation (slerp) should be + /// used to interpolate quaternions. The number output of elements must equal + /// the number of input elements. + #[default] + #[serde(rename = "LINEAR")] + Linear = 1, + + /// Step interpolation. + /// + /// The animated values remain constant to the output of the first keyframe, + /// until the next keyframe. The number of output elements must equal the number + /// of input elements. + #[serde(rename = "STEP")] + Step, + + /// Cubic spline interpolation. + /// + /// The animation's interpolation is computed using a cubic spline with specified + /// tangents. The number of output elements must equal three times the number of + /// input elements. For each input element, the output stores three elements, an + /// in-tangent, a spline vertex, and an out-tangent. There must be at least two + /// keyframes when using this interpolation + #[serde(rename = "CUBICSPLINE")] + CubicSpline, +} +impl Validate for Interpolation {} + +/// Specifies a property to animate. +#[derive(Clone, Copy, Debug, serde_derive::Deserialize, Eq, PartialEq, serde_derive::Serialize)] +pub enum Property { + /// XYZ translation vector. + #[serde(rename = "translation")] + Translation = 1, + /// XYZW rotation quaternion. + #[serde(rename = "rotation")] + Rotation, + /// Animation pointer as defined by the `KHR_animation_pointer` extension. + #[serde(rename = "pointer")] + Pointer, + /// XYZ scale vector. + #[serde(rename = "scale")] + Scale, + /// Weights of morph targets. + #[serde(rename = "weights")] + MorphTargetWeights, +} + +impl Validate for Property {} + +impl Stub for Property { + fn stub() -> Self { + Self::Translation + } +} + +/// A keyframe animation. +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Stub)] +pub struct Animation { + /// An array of channels, each of which targets an animation's sampler at a + /// node's property. + /// + /// Different channels of the same animation must not have equal targets. + pub channels: Vec, + + /// Optional user-defined name for this object. + pub name: Option, + + /// An array of samplers that combine input and output accessors with an + /// interpolation algorithm to define a keyframe graph (but not its target). + pub samplers: Vec, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, +} + +/// Targets an animation's sampler at a node's property. +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Stub)] +pub struct Channel { + /// The index of a sampler in this animation used to compute the value for the + /// target. + pub sampler: Index, + + /// The index of the node and TRS property to target. + pub target: Target, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, +} + +/// The index of the node and TRS property that an animation channel targets. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + gltf_derive::Stub, +)] +pub struct Target { + /// The index of the node to target. + pub node: Option>, + + /// The name of the node's property to modify or the 'weights' of the + /// morph targets it instantiates. + pub path: Property, + + /// Animation pointer. + #[gltf(extension = "KHR_animation_pointer")] + pub pointer: Option, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, +} + +/// Defines a keyframe graph but not its target. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +pub struct Sampler { + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + + /// The index of an accessor containing keyframe input values, e.g., time. + pub input: Index, + + /// The interpolation algorithm. + #[serde(default)] + pub interpolation: Interpolation, + + /// The index of an accessor containing keyframe output values. + pub output: Index, +} + +impl Validate for Animation { + fn validate(&self, root: &Root, path: P, report: &mut R) + where + P: Fn() -> Path, + R: FnMut(&dyn Fn() -> Path, Error), + { + self.samplers + .validate(root, || path().field("samplers"), report); + for (index, channel) in self.channels.iter().enumerate() { + if channel.sampler.value() >= self.samplers.len() { + let path = || path().field("channels").index(index).field("sampler"); + report(&path, Error::IndexOutOfBounds); + } + } + } +} diff --git a/src/animation/iter.rs b/src/animation/iter.rs deleted file mode 100644 index 6775a557..00000000 --- a/src/animation/iter.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::{iter, slice}; - -use crate::animation::{Animation, Channel, Sampler}; - -/// An `Iterator` that visits the channels of an animation. -#[derive(Clone, Debug)] -pub struct Channels<'a> { - /// The parent `Animation` struct. - pub(crate) anim: Animation<'a>, - - /// The internal channel iterator. - pub(crate) iter: iter::Enumerate>, -} - -/// An `Iterator` that visits the samplers of an animation. -#[derive(Clone, Debug)] -pub struct Samplers<'a> { - /// The parent `Channel` struct. - pub(crate) anim: Animation<'a>, - - /// The internal channel iterator. - pub(crate) iter: iter::Enumerate>, -} - -impl<'a> Iterator for Channels<'a> { - type Item = Channel<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Channel::new(self.anim.clone(), json, index)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let anim = self.anim; - self.iter - .last() - .map(|(index, json)| Channel::new(anim, json, index)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Channel::new(self.anim.clone(), json, index)) - } -} - -impl<'a> Iterator for Samplers<'a> { - type Item = Sampler<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Sampler::new(self.anim.clone(), json, index)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let anim = self.anim; - self.iter - .last() - .map(|(index, json)| Sampler::new(anim, json, index)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Sampler::new(self.anim.clone(), json, index)) - } -} diff --git a/src/animation/mod.rs b/src/animation/mod.rs deleted file mode 100644 index 6bcb2342..00000000 --- a/src/animation/mod.rs +++ /dev/null @@ -1,268 +0,0 @@ -use crate::{accessor, scene, Document}; - -#[cfg(feature = "utils")] -use crate::Buffer; - -pub use json::animation::{Interpolation, Property}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// Iterators. -pub mod iter; - -/// Utility functions. -#[cfg(feature = "utils")] -#[cfg_attr(docsrs, doc(cfg(feature = "utils")))] -pub mod util; - -#[cfg(feature = "utils")] -#[doc(inline)] -pub use self::util::Reader; - -/// A keyframe animation. -#[derive(Clone, Debug)] -pub struct Animation<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::animation::Animation, -} - -/// Targets an animation's sampler at a node's property. -#[derive(Clone, Debug)] -pub struct Channel<'a> { - /// The parent `Animation` struct. - anim: Animation<'a>, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::animation::Channel, -} - -/// Defines a keyframe graph (but not its target). -#[derive(Clone, Debug)] -pub struct Sampler<'a> { - /// The parent `Animation` struct. - anim: Animation<'a>, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::animation::Sampler, -} - -/// The node and TRS property that an animation channel targets. -#[derive(Clone, Debug)] -pub struct Target<'a> { - /// The parent `Animation` struct. - anim: Animation<'a>, - - /// The corresponding JSON struct. - json: &'a json::animation::Target, -} - -impl<'a> Animation<'a> { - /// Constructs an `Animation`. - pub(crate) fn new( - document: &'a Document, - index: usize, - json: &'a json::animation::Animation, - ) -> Self { - Self { - document, - index, - json, - } - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } - - /// Returns an `Iterator` over the animation channels. - /// - /// Each channel targets an animation's sampler at a node's property. - pub fn channels(&self) -> iter::Channels<'a> { - iter::Channels { - anim: self.clone(), - iter: self.json.channels.iter().enumerate(), - } - } - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } - - /// Returns an `Iterator` over the animation samplers. - /// - /// Each sampler combines input and output accessors with an - /// interpolation algorithm to define a keyframe graph (but not its target). - pub fn samplers(&self) -> iter::Samplers<'a> { - iter::Samplers { - anim: self.clone(), - iter: self.json.samplers.iter().enumerate(), - } - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } -} - -impl<'a> Channel<'a> { - /// Constructs a `Channel`. - pub(crate) fn new( - anim: Animation<'a>, - json: &'a json::animation::Channel, - index: usize, - ) -> Self { - Self { anim, json, index } - } - - /// Returns the parent `Animation` struct. - pub fn animation(&self) -> Animation<'a> { - self.anim.clone() - } - - /// Returns the sampler in this animation used to compute the value for the - /// target. - pub fn sampler(&self) -> Sampler<'a> { - self.anim.samplers().nth(self.json.sampler.value()).unwrap() - } - - /// Returns the node and property to target. - pub fn target(&self) -> Target<'a> { - Target::new(self.anim.clone(), &self.json.target) - } - - /// Constructs an animation channel reader. - #[cfg(feature = "utils")] - #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] - pub fn reader<'s, F>(&self, get_buffer_data: F) -> Reader<'a, 's, F> - where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, - { - Reader { - channel: self.clone(), - get_buffer_data, - } - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } -} - -impl<'a> Target<'a> { - /// Constructs a `Target`. - pub(crate) fn new(anim: Animation<'a>, json: &'a json::animation::Target) -> Self { - Self { anim, json } - } - - /// Returns the parent `Animation` struct. - pub fn animation(&self) -> Animation<'a> { - self.anim.clone() - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } - - /// Returns the target node. - pub fn node(&self) -> scene::Node<'a> { - self.anim - .document - .nodes() - .nth(self.json.node.value()) - .unwrap() - } - - /// Returns the node's property to modify or the 'weights' of the morph - /// targets it instantiates. - pub fn property(&self) -> Property { - self.json.path.unwrap() - } -} - -impl<'a> Sampler<'a> { - /// Constructs a `Sampler`. - pub(crate) fn new( - anim: Animation<'a>, - json: &'a json::animation::Sampler, - index: usize, - ) -> Self { - Self { anim, json, index } - } - - /// Returns the parent `Animation` struct. - pub fn animation(&self) -> Animation<'a> { - self.anim.clone() - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - - /// Returns the accessor containing the keyframe input values (e.g. time). - pub fn input(&self) -> accessor::Accessor<'a> { - self.anim - .document - .accessors() - .nth(self.json.input.value()) - .unwrap() - } - - /// Returns the keyframe interpolation algorithm. - pub fn interpolation(&self) -> Interpolation { - self.json.interpolation.unwrap() - } - - /// Returns the accessor containing the keyframe output values. - pub fn output(&self) -> accessor::Accessor<'a> { - self.anim - .document - .accessors() - .nth(self.json.output.value()) - .unwrap() - } -} diff --git a/src/animation/util/mod.rs b/src/animation/util/mod.rs deleted file mode 100644 index d55d6824..00000000 --- a/src/animation/util/mod.rs +++ /dev/null @@ -1,188 +0,0 @@ -/// Casting iterator adapters for rotations. -pub mod rotations; - -/// Casting iterator adapters for morph target weights. -pub mod morph_target_weights; - -use crate::accessor; - -use crate::animation::Channel; -use crate::Buffer; - -/// Animation input sampler values of type `f32`. -pub type ReadInputs<'a> = accessor::Iter<'a, f32>; - -/// Animation output sampler values of type `[f32; 3]`. -pub type Translations<'a> = accessor::Iter<'a, [f32; 3]>; - -/// Animation output sampler values of type `[f32; 3]`. -pub type Scales<'a> = accessor::Iter<'a, [f32; 3]>; - -/// Animation channel reader. -#[derive(Clone, Debug)] -pub struct Reader<'a, 's, F> -where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, -{ - pub(crate) channel: Channel<'a>, - pub(crate) get_buffer_data: F, -} - -/// Rotation animations -#[derive(Clone, Debug)] -pub enum Rotations<'a> { - /// Rotations of type `[i8; 4]`. - I8(accessor::Iter<'a, [i8; 4]>), - /// Rotations of type `[u8; 4]`. - U8(accessor::Iter<'a, [u8; 4]>), - /// Rotations of type `[i16; 4]`. - I16(accessor::Iter<'a, [i16; 4]>), - /// Rotations of type `[u16; 4]`. - U16(accessor::Iter<'a, [u16; 4]>), - /// Rotations of type `[f32; 4]`. - F32(accessor::Iter<'a, [f32; 4]>), -} - -/// Morph-target weight animations. -#[derive(Clone, Debug)] -pub enum MorphTargetWeights<'a> { - /// Weights of type `i8`. - I8(accessor::Iter<'a, i8>), - /// Weights of type `u8`. - U8(accessor::Iter<'a, u8>), - /// Weights of type `i16`. - I16(accessor::Iter<'a, i16>), - /// Weights of type `u16`. - U16(accessor::Iter<'a, u16>), - /// Weights of type `f32`. - F32(accessor::Iter<'a, f32>), -} - -/// Animation output sampler values. -pub enum ReadOutputs<'a> { - /// XYZ translations of type `[f32; 3]`. - Translations(Translations<'a>), - - /// Rotation animations. - Rotations(Rotations<'a>), - - /// XYZ scales of type `[f32; 3]`. - Scales(Scales<'a>), - - /// Morph target animations. - MorphTargetWeights(MorphTargetWeights<'a>), -} - -impl<'a> Rotations<'a> { - /// Reinterpret rotations as u16. Lossy if underlying iterator yields u8, - /// i16, u16 or f32. - pub fn into_i8(self) -> rotations::CastingIter<'a, rotations::I8> { - rotations::CastingIter::new(self) - } - - /// Reinterpret rotations as u16. Lossy if underlying iterator yields i16, - /// u16 or f32. - pub fn into_u8(self) -> rotations::CastingIter<'a, rotations::U8> { - rotations::CastingIter::new(self) - } - - /// Reinterpret rotations as u16. Lossy if underlying iterator yields u16 - /// or f32. - pub fn into_i16(self) -> rotations::CastingIter<'a, rotations::I16> { - rotations::CastingIter::new(self) - } - - /// Reinterpret rotations as u16. Lossy if underlying iterator yields f32. - pub fn into_u16(self) -> rotations::CastingIter<'a, rotations::U16> { - rotations::CastingIter::new(self) - } - - /// Reinterpret rotations as f32. Lossy if underlying iterator yields i16 - /// or u16. - pub fn into_f32(self) -> rotations::CastingIter<'a, rotations::F32> { - rotations::CastingIter::new(self) - } -} - -impl<'a> MorphTargetWeights<'a> { - /// Reinterpret morph weights as u16. Lossy if underlying iterator yields - /// u8, i16, u16 or f32. - pub fn into_i8(self) -> morph_target_weights::CastingIter<'a, morph_target_weights::I8> { - morph_target_weights::CastingIter::new(self) - } - - /// Reinterpret morph weights as u16. Lossy if underlying iterator yields - /// i16, u16 or f32. - pub fn into_u8(self) -> morph_target_weights::CastingIter<'a, morph_target_weights::U8> { - morph_target_weights::CastingIter::new(self) - } - - /// Reinterpret morph weights as u16. Lossy if underlying iterator yields - /// u16 or f32. - pub fn into_i16(self) -> morph_target_weights::CastingIter<'a, morph_target_weights::I16> { - morph_target_weights::CastingIter::new(self) - } - - /// Reinterpret morph weights as u16. Lossy if underlying iterator yields - /// f32. - pub fn into_u16(self) -> morph_target_weights::CastingIter<'a, morph_target_weights::U16> { - morph_target_weights::CastingIter::new(self) - } - - /// Reinterpret morph weights as f32. Lossy if underlying iterator yields - /// i16 or u16. - pub fn into_f32(self) -> morph_target_weights::CastingIter<'a, morph_target_weights::F32> { - morph_target_weights::CastingIter::new(self) - } -} - -impl<'a, 's, F> Reader<'a, 's, F> -where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, -{ - /// Visits the input samples of a channel. - pub fn read_inputs(&self) -> Option> { - accessor::Iter::new(self.channel.sampler().input(), self.get_buffer_data.clone()) - } - - /// Visits the output samples of a channel. - pub fn read_outputs(&self) -> Option> { - use crate::animation::Property; - use accessor::{DataType, Iter}; - let output = self.channel.sampler().output(); - match self.channel.target().property() { - Property::Translation => { - Iter::new(output, self.get_buffer_data.clone()).map(ReadOutputs::Translations) - } - Property::Rotation => match output.data_type() { - DataType::I8 => Iter::new(output, self.get_buffer_data.clone()) - .map(|x| ReadOutputs::Rotations(Rotations::I8(x))), - DataType::U8 => Iter::new(output, self.get_buffer_data.clone()) - .map(|x| ReadOutputs::Rotations(Rotations::U8(x))), - DataType::I16 => Iter::new(output, self.get_buffer_data.clone()) - .map(|x| ReadOutputs::Rotations(Rotations::I16(x))), - DataType::U16 => Iter::new(output, self.get_buffer_data.clone()) - .map(|x| ReadOutputs::Rotations(Rotations::U16(x))), - DataType::F32 => Iter::new(output, self.get_buffer_data.clone()) - .map(|x| ReadOutputs::Rotations(Rotations::F32(x))), - _ => unreachable!(), - }, - Property::Scale => { - Iter::new(output, self.get_buffer_data.clone()).map(ReadOutputs::Scales) - } - Property::MorphTargetWeights => match output.data_type() { - DataType::I8 => Iter::new(output, self.get_buffer_data.clone()) - .map(|x| ReadOutputs::MorphTargetWeights(MorphTargetWeights::I8(x))), - DataType::U8 => Iter::new(output, self.get_buffer_data.clone()) - .map(|x| ReadOutputs::MorphTargetWeights(MorphTargetWeights::U8(x))), - DataType::I16 => Iter::new(output, self.get_buffer_data.clone()) - .map(|x| ReadOutputs::MorphTargetWeights(MorphTargetWeights::I16(x))), - DataType::U16 => Iter::new(output, self.get_buffer_data.clone()) - .map(|x| ReadOutputs::MorphTargetWeights(MorphTargetWeights::U16(x))), - DataType::F32 => Iter::new(output, self.get_buffer_data.clone()) - .map(|x| ReadOutputs::MorphTargetWeights(MorphTargetWeights::F32(x))), - _ => unreachable!(), - }, - } - } -} diff --git a/src/animation/util/morph_target_weights.rs b/src/animation/util/morph_target_weights.rs deleted file mode 100644 index bc995c22..00000000 --- a/src/animation/util/morph_target_weights.rs +++ /dev/null @@ -1,231 +0,0 @@ -use super::MorphTargetWeights; -use crate::Normalize; -use std::marker::PhantomData; - -/// Casting iterator for `MorphTargetWeights`. -#[derive(Clone, Debug)] -pub struct CastingIter<'a, T>(MorphTargetWeights<'a>, PhantomData); - -/// Type which describes how to cast any weight into i8. -#[derive(Clone, Debug)] -pub struct I8; - -/// Type which describes how to cast any weight into u8. -#[derive(Clone, Debug)] -pub struct U8; - -/// Type which describes how to cast any weight into i16. -#[derive(Clone, Debug)] -pub struct I16; - -/// Type which describes how to cast any weight into u16. -#[derive(Clone, Debug)] -pub struct U16; - -/// Type which describes how to cast any weight into f32. -#[derive(Clone, Debug)] -pub struct F32; - -/// Trait for types which describe casting behaviour. -pub trait Cast { - /// Output type. - type Output; - - /// Cast from i8. - fn cast_i8(x: i8) -> Self::Output; - - /// Cast from u8. - fn cast_u8(x: u8) -> Self::Output; - - /// Cast from i16. - fn cast_i16(x: i16) -> Self::Output; - - /// Cast from u16. - fn cast_u16(x: u16) -> Self::Output; - - /// Cast from f32. - fn cast_f32(x: f32) -> Self::Output; -} - -impl<'a, A> CastingIter<'a, A> { - pub(crate) fn new(iter: MorphTargetWeights<'a>) -> Self { - CastingIter(iter, PhantomData) - } - - /// Unwrap underlying `MorphTargetWeights` object. - pub fn unwrap(self) -> MorphTargetWeights<'a> { - self.0 - } -} - -impl<'a, A: Cast> ExactSizeIterator for CastingIter<'a, A> {} -impl<'a, A: Cast> Iterator for CastingIter<'a, A> { - type Item = A::Output; - - #[inline] - fn next(&mut self) -> Option { - match self.0 { - MorphTargetWeights::I8(ref mut i) => i.next().map(A::cast_i8), - MorphTargetWeights::U8(ref mut i) => i.next().map(A::cast_u8), - MorphTargetWeights::I16(ref mut i) => i.next().map(A::cast_i16), - MorphTargetWeights::U16(ref mut i) => i.next().map(A::cast_u16), - MorphTargetWeights::F32(ref mut i) => i.next().map(A::cast_f32), - } - } - - #[inline] - fn nth(&mut self, x: usize) -> Option { - match self.0 { - MorphTargetWeights::I8(ref mut i) => i.nth(x).map(A::cast_i8), - MorphTargetWeights::U8(ref mut i) => i.nth(x).map(A::cast_u8), - MorphTargetWeights::I16(ref mut i) => i.nth(x).map(A::cast_i16), - MorphTargetWeights::U16(ref mut i) => i.nth(x).map(A::cast_u16), - MorphTargetWeights::F32(ref mut i) => i.nth(x).map(A::cast_f32), - } - } - - fn last(self) -> Option { - match self.0 { - MorphTargetWeights::I8(i) => i.last().map(A::cast_i8), - MorphTargetWeights::U8(i) => i.last().map(A::cast_u8), - MorphTargetWeights::I16(i) => i.last().map(A::cast_i16), - MorphTargetWeights::U16(i) => i.last().map(A::cast_u16), - MorphTargetWeights::F32(i) => i.last().map(A::cast_f32), - } - } - - fn count(self) -> usize { - self.size_hint().0 - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - match self.0 { - MorphTargetWeights::I8(ref i) => i.size_hint(), - MorphTargetWeights::U8(ref i) => i.size_hint(), - MorphTargetWeights::I16(ref i) => i.size_hint(), - MorphTargetWeights::U16(ref i) => i.size_hint(), - MorphTargetWeights::F32(ref i) => i.size_hint(), - } - } -} - -impl Cast for I8 { - type Output = i8; - - fn cast_i8(x: i8) -> Self::Output { - x.normalize() - } - - fn cast_u8(x: u8) -> Self::Output { - x.normalize() - } - - fn cast_i16(x: i16) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: u16) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: f32) -> Self::Output { - x.normalize() - } -} - -impl Cast for U8 { - type Output = u8; - - fn cast_i8(x: i8) -> Self::Output { - x.normalize() - } - - fn cast_u8(x: u8) -> Self::Output { - x.normalize() - } - - fn cast_i16(x: i16) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: u16) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: f32) -> Self::Output { - x.normalize() - } -} - -impl Cast for I16 { - type Output = i16; - - fn cast_i8(x: i8) -> Self::Output { - x.normalize() - } - - fn cast_u8(x: u8) -> Self::Output { - x.normalize() - } - - fn cast_i16(x: i16) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: u16) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: f32) -> Self::Output { - x.normalize() - } -} - -impl Cast for U16 { - type Output = u16; - - fn cast_i8(x: i8) -> Self::Output { - x.normalize() - } - - fn cast_u8(x: u8) -> Self::Output { - x.normalize() - } - - fn cast_i16(x: i16) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: u16) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: f32) -> Self::Output { - x.normalize() - } -} - -impl Cast for F32 { - type Output = f32; - - fn cast_i8(x: i8) -> Self::Output { - x.normalize() - } - - fn cast_u8(x: u8) -> Self::Output { - x.normalize() - } - - fn cast_i16(x: i16) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: u16) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: f32) -> Self::Output { - x.normalize() - } -} diff --git a/src/animation/util/rotations.rs b/src/animation/util/rotations.rs deleted file mode 100644 index e0ecfa8d..00000000 --- a/src/animation/util/rotations.rs +++ /dev/null @@ -1,231 +0,0 @@ -use super::Rotations; -use crate::Normalize; -use std::marker::PhantomData; - -/// Casting iterator for `Rotations`. -#[derive(Clone, Debug)] -pub struct CastingIter<'a, T>(Rotations<'a>, PhantomData); - -/// Type which describes how to cast any weight into i8. -#[derive(Clone, Debug)] -pub struct I8; - -/// Type which describes how to cast any weight into u8. -#[derive(Clone, Debug)] -pub struct U8; - -/// Type which describes how to cast any weight into i16. -#[derive(Clone, Debug)] -pub struct I16; - -/// Type which describes how to cast any weight into u16. -#[derive(Clone, Debug)] -pub struct U16; - -/// Type which describes how to cast any weight into f32. -#[derive(Clone, Debug)] -pub struct F32; - -/// Trait for types which describe casting behaviour. -pub trait Cast { - /// Output type. - type Output; - - /// Cast from i8. - fn cast_i8(x: [i8; 4]) -> Self::Output; - - /// Cast from u8. - fn cast_u8(x: [u8; 4]) -> Self::Output; - - /// Cast from i16. - fn cast_i16(x: [i16; 4]) -> Self::Output; - - /// Cast from u16. - fn cast_u16(x: [u16; 4]) -> Self::Output; - - /// Cast from f32. - fn cast_f32(x: [f32; 4]) -> Self::Output; -} - -impl<'a, A> CastingIter<'a, A> { - pub(crate) fn new(iter: Rotations<'a>) -> Self { - CastingIter(iter, PhantomData) - } - - /// Unwrap underlying `Rotations` object. - pub fn unwrap(self) -> Rotations<'a> { - self.0 - } -} - -impl<'a, A: Cast> ExactSizeIterator for CastingIter<'a, A> {} -impl<'a, A: Cast> Iterator for CastingIter<'a, A> { - type Item = A::Output; - - #[inline] - fn next(&mut self) -> Option { - match self.0 { - Rotations::I8(ref mut i) => i.next().map(A::cast_i8), - Rotations::U8(ref mut i) => i.next().map(A::cast_u8), - Rotations::I16(ref mut i) => i.next().map(A::cast_i16), - Rotations::U16(ref mut i) => i.next().map(A::cast_u16), - Rotations::F32(ref mut i) => i.next().map(A::cast_f32), - } - } - - #[inline] - fn nth(&mut self, x: usize) -> Option { - match self.0 { - Rotations::I8(ref mut i) => i.nth(x).map(A::cast_i8), - Rotations::U8(ref mut i) => i.nth(x).map(A::cast_u8), - Rotations::I16(ref mut i) => i.nth(x).map(A::cast_i16), - Rotations::U16(ref mut i) => i.nth(x).map(A::cast_u16), - Rotations::F32(ref mut i) => i.nth(x).map(A::cast_f32), - } - } - - fn last(self) -> Option { - match self.0 { - Rotations::I8(i) => i.last().map(A::cast_i8), - Rotations::U8(i) => i.last().map(A::cast_u8), - Rotations::I16(i) => i.last().map(A::cast_i16), - Rotations::U16(i) => i.last().map(A::cast_u16), - Rotations::F32(i) => i.last().map(A::cast_f32), - } - } - - fn count(self) -> usize { - self.size_hint().0 - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - match self.0 { - Rotations::I8(ref i) => i.size_hint(), - Rotations::U8(ref i) => i.size_hint(), - Rotations::I16(ref i) => i.size_hint(), - Rotations::U16(ref i) => i.size_hint(), - Rotations::F32(ref i) => i.size_hint(), - } - } -} - -impl Cast for I8 { - type Output = [i8; 4]; - - fn cast_i8(x: [i8; 4]) -> Self::Output { - x.normalize() - } - - fn cast_u8(x: [u8; 4]) -> Self::Output { - x.normalize() - } - - fn cast_i16(x: [i16; 4]) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: [u16; 4]) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: [f32; 4]) -> Self::Output { - x.normalize() - } -} - -impl Cast for i8 { - type Output = [u8; 4]; - - fn cast_i8(x: [i8; 4]) -> Self::Output { - x.normalize() - } - - fn cast_u8(x: [u8; 4]) -> Self::Output { - x.normalize() - } - - fn cast_i16(x: [i16; 4]) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: [u16; 4]) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: [f32; 4]) -> Self::Output { - x.normalize() - } -} - -impl Cast for I16 { - type Output = [i16; 4]; - - fn cast_i8(x: [i8; 4]) -> Self::Output { - x.normalize() - } - - fn cast_u8(x: [u8; 4]) -> Self::Output { - x.normalize() - } - - fn cast_i16(x: [i16; 4]) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: [u16; 4]) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: [f32; 4]) -> Self::Output { - x.normalize() - } -} - -impl Cast for U16 { - type Output = [u16; 4]; - - fn cast_i8(x: [i8; 4]) -> Self::Output { - x.normalize() - } - - fn cast_u8(x: [u8; 4]) -> Self::Output { - x.normalize() - } - - fn cast_i16(x: [i16; 4]) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: [u16; 4]) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: [f32; 4]) -> Self::Output { - x.normalize() - } -} - -impl Cast for F32 { - type Output = [f32; 4]; - - fn cast_i8(x: [i8; 4]) -> Self::Output { - x.normalize() - } - - fn cast_u8(x: [u8; 4]) -> Self::Output { - x.normalize() - } - - fn cast_i16(x: [i16; 4]) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: [u16; 4]) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: [f32; 4]) -> Self::Output { - x.normalize() - } -} diff --git a/src/asset.rs b/src/asset.rs new file mode 100644 index 00000000..3368c8c4 --- /dev/null +++ b/src/asset.rs @@ -0,0 +1,64 @@ +use crate::{Extras, UnrecognizedExtensions}; +use gltf_derive::{Deserialize, Serialize, Validate}; + +/// Metadata about the glTF asset. +#[derive(Clone, Debug, Deserialize, Serialize, Validate)] +pub struct Asset { + /// A copyright message suitable for display to credit the content creator. + pub copyright: Option, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + + /// Tool that generated this glTF model. + pub generator: Option, + + /// The minimum glTF version that this asset targets. + pub min_version: Option, + + /// The glTF version of this asset. + pub version: String, +} + +impl Default for Asset { + fn default() -> Self { + Self { + copyright: None, + unrecognized_extensions: Default::default(), + extras: None, + generator: None, + min_version: None, + version: "2.0".to_string(), + } + } +} + +mod tests { + #[test] + fn serialize() { + let asset = super::Asset { + copyright: Some("X".to_owned()), + min_version: Some("2.0".to_owned()), + ..Default::default() + }; + let json = serde_json::to_string(&asset).unwrap(); + assert_eq!( + json, + r#"{"copyright":"X","minVersion":"2.0","version":"2.0"}"# + ); + } + + #[test] + fn deserialize() { + let json = r#"{"copyright":"X","minVersion":"2.0","version":"2.0"}"#; + let asset = serde_json::from_str::(json).unwrap(); + assert_eq!(asset.copyright.as_deref(), Some("X")); + assert!(asset.extras.is_none()); + assert!(asset.generator.is_none()); + assert_eq!(asset.min_version.as_deref(), Some("2.0")); + assert_eq!(asset.version.as_str(), "2.0"); + } +} diff --git a/src/buffer.rs b/src/buffer.rs index 492fa88c..503e0c9d 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,209 +1,166 @@ -#[cfg(feature = "import")] -use std::ops; - -use crate::Document; - -pub use json::buffer::Target; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// A buffer points to binary data representing geometry, animations, or skins. -#[derive(Clone, Debug)] -pub struct Buffer<'a> { - /// The parent `Document` struct. - #[allow(dead_code)] - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::buffer::Buffer, -} - -/// A view into a buffer generally representing a subset of the buffer. -#[derive(Clone, Debug)] -pub struct View<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::buffer::View, - - /// The parent `Buffer`. - #[allow(dead_code)] - parent: Buffer<'a>, +use crate::validation::{Error, USize64, Validate}; +use crate::{Extras, Index, Path, Root, Stub, UnrecognizedExtensions}; + +/// The minimum byte stride. +pub const MIN_BYTE_STRIDE: usize = 4; + +/// The maximum byte stride. +pub const MAX_BYTE_STRIDE: usize = 252; + +/// Specifies the target a GPU buffer should be bound to. +#[derive( + Clone, Copy, Debug, Eq, PartialEq, serde_repr::Deserialize_repr, serde_repr::Serialize_repr, +)] +#[repr(u32)] +pub enum Target { + /// Corresponds to `GL_ARRAY_BUFFER`. + ArrayBuffer = 34_962, + + /// Corresponds to `GL_ELEMENT_ARRAY_BUFFER`. + ElementArrayBuffer = 34_963, } -/// Describes a buffer data source. -#[derive(Clone, Debug)] -pub enum Source<'a> { - /// Buffer data is contained in the `BIN` section of binary glTF. - Bin, +impl Validate for Target {} - /// Buffer data is contained in an external data source. - Uri(&'a str), -} - -/// Buffer data belonging to an imported glTF asset. -#[cfg(feature = "import")] -#[cfg_attr(docsrs, doc(cfg(feature = "import")))] -#[derive(Clone, Debug)] -pub struct Data(pub Vec); - -#[cfg(feature = "import")] -#[cfg_attr(docsrs, doc(cfg(feature = "import")))] -impl ops::Deref for Data { - type Target = [u8]; - fn deref(&self) -> &Self::Target { - self.0.as_slice() +impl Stub for Target { + fn stub() -> Self { + Self::ArrayBuffer } } -impl<'a> Buffer<'a> { - /// Constructs a `Buffer`. - pub(crate) fn new( - document: &'a Document, - index: usize, - json: &'a json::buffer::Buffer, - ) -> Self { - Self { - document, - index, - json, - } - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - - /// Returns the buffer data source. - pub fn source(&self) -> Source<'a> { - if let Some(uri) = self.json.uri.as_deref() { - Source::Uri(uri) - } else { - Source::Bin - } - } - +/// A buffer points to binary data representing geometry, animations, or skins. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +pub struct Buffer { /// The length of the buffer in bytes. - pub fn length(&self) -> usize { - self.json.byte_length.0 as usize - } + #[serde(rename = "byteLength")] + pub length: USize64, /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(docsrs, doc(cfg(feature = "names")))] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } + pub name: Option, - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } + /// The uri of the buffer. Relative paths are relative to the .gltf file. + /// Instead of referencing an external file, the uri can also be a data-uri. + pub uri: Option, - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } + pub extras: Option, } -impl<'a> View<'a> { - /// Constructs a `View`. - pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::buffer::View) -> Self { - let parent = document.buffers().nth(json.buffer.value()).unwrap(); - Self { - document, - index, - json, - parent, - } - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - - /// Returns the parent `Buffer`. - pub fn buffer(&self) -> Buffer<'a> { - self.document - .buffers() - .nth(self.json.buffer.value()) - .unwrap() - } +/// A view into a buffer generally representing a subset of the buffer. +/// +/// +/// +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +pub struct View { + /// The parent `Buffer`. + pub buffer: Index, - /// Returns the length of the buffer view in bytes. - pub fn length(&self) -> usize { - self.json.byte_length.0 as usize - } + /// The length of the `BufferView` in bytes. + #[serde(rename = "byteLength")] + pub length: USize64, - /// Returns the offset into the parent buffer in bytes. - pub fn offset(&self) -> usize { - self.json.byte_offset.unwrap_or_default().0 as usize - } + /// Offset into the parent buffer in bytes. + #[serde(rename = "byteOffset")] + #[gltf(default)] + pub offset: USize64, - /// Returns the stride in bytes between vertex attributes or other interleavable - /// data. When `None`, data is assumed to be tightly packed. - pub fn stride(&self) -> Option { - self.json.byte_stride.and_then(|x| { - // Treat byte_stride == 0 same as not specifying stride. - // This is technically a validation error, but best way we can handle it here - if x.0 == 0 { - None - } else { - Some(x.0) - } - }) - } + /// The stride in bytes between vertex attributes or other interleavable data. + /// + /// When zero, data is assumed to be tightly packed. + #[serde(rename = "byteStride")] + #[gltf(validate = "validate_stride")] + pub stride: Option, /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(docsrs, doc(cfg(feature = "names")))] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } + pub name: Option, /// Optional target the buffer should be bound to. - pub fn target(&self) -> Option { - self.json.target.map(|target| target.unwrap()) - } + pub target: Option, - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) + /// Optional application specific data. + pub extras: Option, +} + +fn validate_stride(stride: &Option, _root: &Root, path: P, report: &mut R) +where + P: Fn() -> Path, + R: FnMut(&dyn Fn() -> Path, Error), +{ + if let Some(value) = stride { + if *value < MIN_BYTE_STRIDE || *value > MAX_BYTE_STRIDE { + report(&path, Error::Invalid); + } } +} - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras +mod tests { + #[test] + fn serialize_target() { + assert_eq!( + "34962", + serde_json::to_string(&super::Target::ArrayBuffer).unwrap(), + ); + } + + #[test] + fn deserialize_target() { + assert_eq!( + super::Target::ElementArrayBuffer, + serde_json::from_str("34963").unwrap(), + ); + + assert!(serde_json::from_str::("123").is_err()); + } + + #[test] + fn serialize_buffer() { + let user_data = serde_json::json!({ "bar": 42 }); + let example = super::Buffer { + length: 12usize.into(), + name: Some("foo".to_owned()), + uri: None, + extras: Some(serde_json::value::to_raw_value(&user_data).unwrap()), + unrecognized_extensions: Default::default(), + }; + assert_eq!( + r#"{"byteLength":12,"name":"foo","extras":{"bar":42}}"#, + serde_json::to_string(&example).unwrap(), + ); + } + + #[test] + fn deserialize_buffer() { + let json = r#"{"byteLength":12,"name":"foo","extras":{"bar":42}}"#; + let buffer = serde_json::from_str::(json).unwrap(); + assert_eq!(buffer.length, super::USize64(12)); + assert_eq!(buffer.name.as_deref(), Some("foo")); + assert_eq!(buffer.uri, None); + assert_eq!( + buffer + .extras + .as_deref() + .map(serde_json::value::RawValue::get), + Some(r#"{"bar":42}"#) + ); } } diff --git a/src/camera.rs b/src/camera.rs index a4ce37b0..f718fefb 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,208 +1,176 @@ -use crate::Document; - -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// A camera's projection. -#[derive(Clone, Debug)] -pub enum Projection<'a> { - /// Describes an orthographic projection. - Orthographic(Orthographic<'a>), - - /// Describes a perspective projection. - Perspective(Perspective<'a>), -} - -/// A camera's projection. A node can reference a camera to apply a transform to -/// place the camera in the scene. -#[derive(Clone, Debug)] -pub struct Camera<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::camera::Camera, -} - -/// Values for an orthographic camera projection. -#[derive(Clone, Debug)] -pub struct Orthographic<'a> { - /// The parent `Document` struct. - #[allow(dead_code)] - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::camera::Orthographic, -} - -/// Values for a perspective camera projection. -#[derive(Clone, Debug)] -pub struct Perspective<'a> { - /// The parent `Document` struct. - #[allow(dead_code)] - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::camera::Perspective, +use crate::validation::{Error, Validate}; +use crate::{Extras, Path, Root, Stub, UnrecognizedExtensions}; + +/// Projection matrix parameters. +#[derive(Clone, Debug, serde_derive::Deserialize, serde_derive::Serialize, gltf_derive::Wrap)] +#[serde(tag = "type", rename_all = "camelCase")] +pub enum Projection { + /// Perspective projection. + Perspective { + /// Perspective projection parameters. + perspective: Perspective, + }, + /// Orthographic projection. + Orthographic { + /// Orthographic projection parameters. + orthographic: Orthographic, + }, } -impl<'a> Camera<'a> { - /// Constructs a `Camera`. - pub(crate) fn new( - document: &'a Document, - index: usize, - json: &'a json::camera::Camera, - ) -> Self { - Self { - document, - index, - json, +impl Stub for Projection { + fn stub() -> Self { + Self::Perspective { + perspective: Stub::stub(), } } +} - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - +/// A viewpoint in the scene. +/// +/// A node can reference a camera to apply a transform to place the camera in the +/// scene. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + gltf_derive::Wrap, +)] +#[gltf(indexed)] +pub struct Camera { /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(docsrs, doc(cfg(feature = "names")))] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } + pub name: Option, - /// Returns the camera's projection. - pub fn projection(&self) -> Projection { - match self.json.type_.unwrap() { - json::camera::Type::Orthographic => { - let json = self.json.orthographic.as_ref().unwrap(); - Projection::Orthographic(Orthographic::new(self.document, json)) - } - json::camera::Type::Perspective => { - let json = self.json.perspective.as_ref().unwrap(); - Projection::Perspective(Perspective::new(self.document, json)) - } - } - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } + /// Projection matrix parameters. + #[serde(flatten)] + pub projection: Projection, - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } + pub extras: Option, } -impl<'a> Orthographic<'a> { - /// Constructs a `Orthographic` camera projection. - pub(crate) fn new(document: &'a Document, json: &'a json::camera::Orthographic) -> Self { - Self { document, json } - } +/// Values for an orthographic camera. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + gltf_derive::Wrap, +)] +pub struct Orthographic { + /// The horizontal magnification of the view. + pub xmag: f32, - /// The horizontal magnification of the view. - pub fn xmag(&self) -> f32 { - self.json.xmag - } + /// The vertical magnification of the view. + pub ymag: f32, - /// The vertical magnification of the view. - pub fn ymag(&self) -> f32 { - self.json.ymag - } + /// The distance to the far clipping plane. + pub zfar: f32, - /// The distance to the far clipping plane. - pub fn zfar(&self) -> f32 { - self.json.zfar - } + /// The distance to the near clipping plane. + pub znear: f32, - /// The distance to the near clipping plane. - pub fn znear(&self) -> f32 { - self.json.znear - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } + /// Optional application specific data. + pub extras: Option, } -impl<'a> Perspective<'a> { - /// Constructs a `Perspective` camera projection. - pub(crate) fn new(document: &'a Document, json: &'a json::camera::Perspective) -> Self { - Self { document, json } - } +/// Values for a perspective camera. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + gltf_derive::Wrap, +)] +pub struct Perspective { + /// Aspect ratio of the field of view. + pub aspect_ratio: Option, - /// Aspect ratio of the field of view. - pub fn aspect_ratio(&self) -> Option { - self.json.aspect_ratio - } + /// The vertical field of view in radians. + pub yfov: f32, - /// The vertical field of view in radians. - pub fn yfov(&self) -> f32 { - self.json.yfov - } + /// The distance to the far clipping plane. + pub zfar: Option, - /// The distance to the far clipping plane. - pub fn zfar(&self) -> Option { - self.json.zfar - } + /// The distance to the near clipping plane. + pub znear: f32, - /// The distance to the near clipping plane. - pub fn znear(&self) -> f32 { - self.json.znear - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } + /// Optional application specific data. + pub extras: Option, +} - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) +impl Validate for Projection { + fn validate(&self, root: &Root, path: P, report: &mut R) + where + P: Fn() -> Path, + R: FnMut(&dyn Fn() -> Path, Error), + { + match self { + Self::Perspective { perspective } => { + perspective.validate(root, || path().field("perspective"), report); + } + Self::Orthographic { orthographic } => { + orthographic.validate(root, || path().field("orthographic"), report); + } + } } +} - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras +#[cfg(test)] +mod tests { + #[test] + fn serialize() { + let camera = super::Camera { + name: None, + extras: None, + projection: super::Projection::Perspective { + perspective: super::Perspective { + aspect_ratio: None, + yfov: 0.785, + zfar: Some(10.0), + znear: 0.01, + extras: None, + unrecognized_extensions: Default::default(), + }, + }, + unrecognized_extensions: Default::default(), + }; + let json = serde_json::to_string(&camera).unwrap(); + assert_eq!( + r#"{"type":"perspective","perspective":{"yfov":0.785,"zfar":10.0,"znear":0.01}}"#, + json + ); + } + + #[test] + fn deserialize() { + let json = r#"{"type":"orthographic","orthographic":{"xmag":1.0,"ymag":1.0,"zfar":10.0,"znear":0.01}}"#; + let camera = serde_json::from_str::(json).unwrap(); + match camera.projection { + super::Projection::Perspective { perspective: _ } => { + panic!("expected orthographic projection") + } + super::Projection::Orthographic { orthographic } => { + assert_eq!(orthographic.xmag, 1.0); + assert_eq!(orthographic.ymag, 1.0); + assert_eq!(orthographic.zfar, 10.0); + assert_eq!(orthographic.znear, 0.01); + } + } } } diff --git a/src/image.rs b/src/image.rs index e1904dbe..8cb6b9a6 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,184 +1,43 @@ -#[allow(unused)] -use crate::{buffer, Document, Error, Result}; +use crate::validation::Validate; +use crate::{buffer, Extras, Index, UnrecognizedExtensions}; -#[cfg(feature = "import")] -#[cfg_attr(docsrs, doc(cfg(feature = "import")))] -use image_crate::DynamicImage; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// Format of image pixel data. -#[cfg(feature = "import")] -#[cfg_attr(docsrs, doc(cfg(feature = "import")))] -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum Format { - /// Red only. - R8, - - /// Red, green. - R8G8, - - /// Red, green, blue. - R8G8B8, - - /// Red, green, blue, alpha. - R8G8B8A8, - - /// Red only (16 bits). - R16, - - /// Red, green (16 bits). - R16G16, - - /// Red, green, blue (16 bits). - R16G16B16, - - /// Red, green, blue, alpha (16 bits). - R16G16B16A16, - - /// Red, green, blue (32 bits float) - R32G32B32FLOAT, - - /// Red, green, blue, alpha (32 bits float) - R32G32B32A32FLOAT, -} - -/// Describes an image data source. -#[derive(Clone, Debug)] -pub enum Source<'a> { - /// Image data is contained in a buffer view. - View { - /// The buffer view containing the encoded image data. - view: buffer::View<'a>, - - /// The image data MIME type. - mime_type: &'a str, - }, - - /// Image data is contained in an external data source. - Uri { - /// The URI of the external data source. - uri: &'a str, - - /// The image data MIME type, if provided. - mime_type: Option<&'a str>, - }, -} +/// All valid MIME types. +pub const VALID_MIME_TYPES: &[&str] = &["image/jpeg", "image/png"]; /// Image data used to create a texture. -#[derive(Clone, Debug)] -pub struct Image<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::image::Image, -} - -/// Image data belonging to an imported glTF asset. -#[cfg(feature = "import")] -#[cfg_attr(docsrs, doc(cfg(feature = "import")))] -#[derive(Clone, Debug)] -pub struct Data { - /// The image pixel data (8 bits per channel). - pub pixels: Vec, - - /// The image pixel data format. - pub format: Format, - - /// The image width in pixels. - pub width: u32, - - /// The image height in pixels. - pub height: u32, -} - -impl<'a> Image<'a> { - /// Constructs an `Image` from owned data. - pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::image::Image) -> Self { - Self { - document, - index, - json, - } - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +pub struct Image { + /// The index of the buffer view that contains the image. Use this instead of + /// the image's uri property. + pub buffer_view: Option>, + + /// The image's MIME type. + pub mime_type: Option, /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(docsrs, doc(cfg(feature = "names")))] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } + pub name: Option, - /// Returns the image data source. - pub fn source(&self) -> Source<'a> { - if let Some(index) = self.json.buffer_view.as_ref() { - let view = self.document.views().nth(index.value()).unwrap(); - let mime_type = self.json.mime_type.as_ref().map(|x| x.0.as_str()).unwrap(); - Source::View { view, mime_type } - } else { - let uri = self.json.uri.as_ref().unwrap(); - let mime_type = self.json.mime_type.as_ref().map(|x| x.0.as_str()); - Source::Uri { uri, mime_type } - } - } + /// The uri of the image. Relative paths are relative to the .gltf file. + /// Instead of referencing an external file, the uri can also be a data-uri. + /// The image format must be jpg or png. + pub uri: Option, - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } + pub extras: Option, } -#[cfg(feature = "import")] -impl Data { - /// Note: We don't implement `From` since we don't want - /// to expose such functionality to the user. - pub(crate) fn new(image: DynamicImage) -> Result { - use image_crate::GenericImageView; - let format = match image { - DynamicImage::ImageLuma8(_) => Format::R8, - DynamicImage::ImageLumaA8(_) => Format::R8G8, - DynamicImage::ImageRgb8(_) => Format::R8G8B8, - DynamicImage::ImageRgba8(_) => Format::R8G8B8A8, - DynamicImage::ImageLuma16(_) => Format::R16, - DynamicImage::ImageLumaA16(_) => Format::R16G16, - DynamicImage::ImageRgb16(_) => Format::R16G16B16, - DynamicImage::ImageRgba16(_) => Format::R16G16B16A16, - DynamicImage::ImageRgb32F(_) => Format::R32G32B32FLOAT, - DynamicImage::ImageRgba32F(_) => Format::R32G32B32A32FLOAT, - image => return Err(Error::UnsupportedImageFormat(image)), - }; - let (width, height) = image.dimensions(); - let pixels = image.into_bytes(); - Ok(Data { - format, - width, - height, - pixels, - }) - } -} +/// An image MIME type. +#[derive(Clone, Debug, serde_derive::Deserialize, serde_derive::Serialize)] +pub struct MimeType(pub String); + +impl Validate for MimeType {} diff --git a/src/import.rs b/src/import.rs index da2c541d..de12fdf4 100644 --- a/src/import.rs +++ b/src/import.rs @@ -1,14 +1,115 @@ use crate::buffer; -use crate::image; use std::borrow::Cow; use std::{fs, io}; -use crate::{Document, Error, Gltf, Result}; +use crate::{Error, Gltf, Result, Root}; +use image_crate::DynamicImage; use image_crate::ImageFormat::{Jpeg, Png}; use std::path::Path; +/// Buffer data belonging to an imported glTF asset. +#[derive(Clone)] +pub struct BufferData(pub Vec); + +/// Describes a buffer data source. +#[derive(Clone, Debug)] +pub enum BufferSource<'a> { + /// Buffer data is contained in the `BIN` section of binary glTF. + Bin, + + /// Buffer data is contained in an external data source. + Uri(&'a str), +} + +/// Image pixel format. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum PixelFormat { + /// Red only. + R = 1, + + /// Red, green. + Rg = 2, + + /// Red, green, blue, alpha. + Rgba = 4, +} + +/// Image data buffer. +#[derive(Clone)] +pub enum PixelBuffer { + /// `u8` component type. + U8(Vec), + /// `u16` component type. + U16(Vec), + /// `f32` component type. + F32(Vec), +} + +/// Image data belonging to an imported glTF asset. +#[derive(Clone)] +pub struct ImageData { + /// Pixel data. + pub pixels: PixelBuffer, + + /// The image pixel data format. + pub format: PixelFormat, + + /// The image width in pixels. + pub width: usize, + + /// The image height in pixels. + pub height: usize, +} + +impl ImageData { + pub(crate) fn new(image: DynamicImage) -> Result { + use image_crate::GenericImageView; + let (width, height) = image.dimensions(); + let (pixels, format) = match image { + DynamicImage::ImageLuma8(image) => (PixelBuffer::U8(image.into_vec()), PixelFormat::R), + DynamicImage::ImageLumaA8(image) => { + (PixelBuffer::U8(image.into_vec()), PixelFormat::Rg) + } + image @ DynamicImage::ImageRgb8(_) => ( + PixelBuffer::U8(image.to_rgba8().into_vec()), + PixelFormat::Rgba, + ), + DynamicImage::ImageRgba8(image) => { + (PixelBuffer::U8(image.into_vec()), PixelFormat::Rgba) + } + DynamicImage::ImageLuma16(image) => { + (PixelBuffer::U16(image.into_vec()), PixelFormat::R) + } + DynamicImage::ImageLumaA16(image) => { + (PixelBuffer::U16(image.into_vec()), PixelFormat::Rg) + } + image @ DynamicImage::ImageRgb16(_) => ( + PixelBuffer::U16(image.to_rgba16().into_vec()), + PixelFormat::Rgba, + ), + DynamicImage::ImageRgba16(image) => { + (PixelBuffer::U16(image.into_vec()), PixelFormat::Rgba) + } + image @ DynamicImage::ImageRgb32F(_) => ( + PixelBuffer::F32(image.to_rgba32f().into_vec()), + PixelFormat::Rgba, + ), + DynamicImage::ImageRgba32F(image) => { + (PixelBuffer::F32(image.into_vec()), PixelFormat::Rgba) + } + image => return Err(Error::UnsupportedImageFormat(image)), + }; + Ok(Self { + format, + width: width as usize, + height: height as usize, + pixels, + }) + } +} + /// Return type of `import`. -type Import = (Document, Vec, Vec); +pub type Import = (Root, Vec, Vec); /// Represents the set of URI schemes the importer supports. #[derive(Clone, Debug, Eq, Hash, PartialEq)] @@ -28,6 +129,28 @@ enum Scheme<'a> { Unsupported, } +/// Describes an image data source. +#[derive(Clone, Debug)] +pub enum ImageSource<'a> { + /// Image data is contained in a buffer view. + View { + /// The buffer view containing the encoded image data. + view: &'a buffer::View, + + /// The image data MIME type. + mime_type: &'a str, + }, + + /// Image data is contained in an external data source. + Uri { + /// The URI of the external data source. + uri: &'a str, + + /// The image data MIME type, if provided. + mime_type: Option<&'a str>, + }, +} + impl<'a> Scheme<'a> { fn parse(uri: &str) -> Scheme<'_> { if uri.contains(':') { @@ -56,35 +179,21 @@ impl<'a> Scheme<'a> { // The path may be unused in the Scheme::Data case // Example: "uri" : "data:application/octet-stream;base64,wsVHPgA...." Scheme::Data(_, base64) => base64::decode(base64).map_err(Error::Base64), - Scheme::File(path) if base.is_some() => read_to_end(path), - Scheme::Relative(path) if base.is_some() => read_to_end(base.unwrap().join(&*path)), + Scheme::File(path) if base.is_some() => std::fs::read(path).map_err(Error::Io), + Scheme::Relative(path) if base.is_some() => { + std::fs::read(base.unwrap().join(&*path)).map_err(Error::Io) + } Scheme::Unsupported => Err(Error::UnsupportedScheme), _ => Err(Error::ExternalReferenceInSliceImport), } } } -fn read_to_end

(path: P) -> Result> -where - P: AsRef, -{ - use io::Read; - let file = fs::File::open(path.as_ref()).map_err(Error::Io)?; - // Allocate one extra byte so the buffer doesn't need to grow before the - // final `read` call at the end of the file. Don't worry about `usize` - // overflow because reading will fail regardless in that case. - let length = file.metadata().map(|x| x.len() + 1).unwrap_or(0); - let mut reader = io::BufReader::new(file); - let mut data = Vec::with_capacity(length as usize); - reader.read_to_end(&mut data).map_err(Error::Io)?; - Ok(data) -} - -impl buffer::Data { +impl BufferData { /// Construct a buffer data object by reading the given source. /// If `base` is provided, then external filesystem references will /// be resolved from this directory. - pub fn from_source(source: buffer::Source<'_>, base: Option<&Path>) -> Result { + pub fn from_source(source: BufferSource<'_>, base: Option<&Path>) -> Result { Self::from_source_and_blob(source, base, &mut None) } @@ -94,18 +203,18 @@ impl buffer::Data { /// `blob` represents the `BIN` section of a binary glTF file, /// and it will be taken to fill the buffer if the `source` refers to it. pub fn from_source_and_blob( - source: buffer::Source<'_>, + source: BufferSource<'_>, base: Option<&Path>, blob: &mut Option>, ) -> Result { let mut data = match source { - buffer::Source::Uri(uri) => Scheme::read(base, uri), - buffer::Source::Bin => blob.take().ok_or(Error::MissingBlob), + BufferSource::Uri(uri) => Scheme::read(base, uri), + BufferSource::Bin => blob.take().ok_or(Error::MissingBlob), }?; while data.len() % 4 != 0 { data.push(0); } - Ok(buffer::Data(data)) + Ok(Self(data)) } } @@ -116,18 +225,23 @@ impl buffer::Data { /// This function is intended for advanced users who wish to forego loading image data. /// A typical user should call [`import`] instead. pub fn import_buffers( - document: &Document, + root: &Root, base: Option<&Path>, mut blob: Option>, -) -> Result> { +) -> Result> { let mut buffers = Vec::new(); - for buffer in document.buffers() { - let data = buffer::Data::from_source_and_blob(buffer.source(), base, &mut blob)?; - if data.len() < buffer.length() { + for (index, buffer) in root.buffers.iter().enumerate() { + let buffer_source = if let Some(uri) = buffer.uri.as_deref() { + BufferSource::Uri(uri) + } else { + BufferSource::Bin + }; + let data = BufferData::from_source_and_blob(buffer_source, base, &mut blob)?; + if data.0.len() < buffer.length.value() { return Err(Error::BufferLength { - buffer: buffer.index(), - expected: buffer.length(), - actual: data.len(), + buffer: index, + expected: buffer.length.value(), + actual: data.0.len(), }); } buffers.push(data); @@ -135,14 +249,14 @@ pub fn import_buffers( Ok(buffers) } -impl image::Data { +impl ImageData { /// Construct an image data object by reading the given source. /// If `base` is provided, then external filesystem references will /// be resolved from this directory. pub fn from_source( - source: image::Source<'_>, + source: ImageSource<'_>, base: Option<&Path>, - buffer_data: &[buffer::Data], + buffer_data: &[BufferData], ) -> Result { #[cfg(feature = "guess_mime_type")] let guess_format = |encoded_image: &[u8]| match image_crate::guess_format(encoded_image) { @@ -153,7 +267,7 @@ impl image::Data { #[cfg(not(feature = "guess_mime_type"))] let guess_format = |_encoded_image: &[u8]| None; let decoded_image = match source { - image::Source::Uri { uri, mime_type } if base.is_some() => match Scheme::parse(uri) { + ImageSource::Uri { uri, mime_type } if base.is_some() => match Scheme::parse(uri) { Scheme::Data(Some(annoying_case), base64) => { let encoded_image = base64::decode(base64).map_err(Error::Base64)?; let encoded_format = match annoying_case { @@ -189,10 +303,10 @@ impl image::Data { image_crate::load_from_memory_with_format(&encoded_image, encoded_format)? } }, - image::Source::View { view, mime_type } => { - let parent_buffer_data = &buffer_data[view.buffer().index()].0; - let begin = view.offset(); - let end = begin + view.length(); + ImageSource::View { view, mime_type } => { + let parent_buffer_data = &buffer_data[view.buffer.value()].0; + let begin = view.offset.value(); + let end = begin + view.length.value(); let encoded_image = &parent_buffer_data[begin..end]; let encoded_format = match mime_type { "image/png" => Png, @@ -207,7 +321,7 @@ impl image::Data { _ => return Err(Error::ExternalReferenceInSliceImport), }; - image::Data::new(decoded_image) + ImageData::new(decoded_image) } } @@ -218,21 +332,30 @@ impl image::Data { /// This function is intended for advanced users who wish to forego loading buffer data. /// A typical user should call [`import`] instead. pub fn import_images( - document: &Document, + root: &Root, base: Option<&Path>, - buffer_data: &[buffer::Data], -) -> Result> { + buffer_data: &[BufferData], +) -> Result> { let mut images = Vec::new(); - for image in document.images() { - images.push(image::Data::from_source(image.source(), base, buffer_data)?); + for image in &root.images { + let image_source = if let Some(index) = image.buffer_view.as_ref() { + let view = &root.buffer_views[index.value()]; + let mime_type = image.mime_type.as_ref().map(|x| x.0.as_str()).unwrap(); + ImageSource::View { view, mime_type } + } else { + let uri = image.uri.as_ref().unwrap(); + let mime_type = image.mime_type.as_ref().map(|x| x.0.as_str()); + ImageSource::Uri { uri, mime_type } + }; + images.push(ImageData::from_source(image_source, base, buffer_data)?); } Ok(images) } -fn import_impl(Gltf { document, blob }: Gltf, base: Option<&Path>) -> Result { - let buffer_data = import_buffers(&document, base, blob)?; - let image_data = import_images(&document, base, &buffer_data)?; - let import = (document, buffer_data, image_data); +fn import_impl(Gltf { root, blob }: Gltf, base: Option<&Path>) -> Result { + let buffer_data = import_buffers(&root, base, blob)?; + let image_data = import_images(&root, base, &buffer_data)?; + let import = (root, buffer_data, image_data); Ok(import) } @@ -247,7 +370,7 @@ fn import_path(path: &Path) -> Result { /// /// ``` /// # fn run() -> Result<(), gltf::Error> { -/// # let path = "examples/Box.gltf"; +/// # let path = "glTF-Sample-Assets/Models/Box/glTF/Box.gltf"; /// # #[allow(unused)] /// let (document, buffers, images) = gltf::import(path)?; /// # Ok(()) @@ -296,7 +419,7 @@ fn import_slice_impl(slice: &[u8]) -> Result { /// # use std::fs; /// # use std::io::Read; /// # fn run() -> Result<(), gltf::Error> { -/// # let path = "examples/Box.glb"; +/// # let path = "glTF-Sample-Assets/Models/Box/glTF-Binary/Box.glb"; /// # let mut file = fs::File::open(path).map_err(gltf::Error::Io)?; /// # let mut bytes = Vec::new(); /// # file.read_to_end(&mut bytes).map_err(gltf::Error::Io)?; diff --git a/src/iter.rs b/src/iter.rs deleted file mode 100644 index ffd3ef41..00000000 --- a/src/iter.rs +++ /dev/null @@ -1,626 +0,0 @@ -use std::{iter, slice}; - -use crate::accessor::Accessor; -use crate::animation::Animation; -use crate::buffer::{Buffer, View}; -use crate::camera::Camera; -use crate::image::Image; -use crate::material::Material; -use crate::mesh::Mesh; -use crate::scene::{Node, Scene}; -use crate::skin::Skin; -use crate::texture::{Sampler, Texture}; -use crate::Document; - -/// An `Iterator` that visits extension strings used by a glTF asset. -#[derive(Clone, Debug)] -pub struct ExtensionsUsed<'a>(pub(crate) slice::Iter<'a, String>); - -/// An `Iterator` that visits extension strings required by a glTF asset. -#[derive(Clone, Debug)] -pub struct ExtensionsRequired<'a>(pub(crate) slice::Iter<'a, String>); - -/// An `Iterator` that visits every accessor in a glTF asset. -#[derive(Clone, Debug)] -pub struct Accessors<'a> { - /// Internal accessor iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every animation in a glTF asset. -#[derive(Clone, Debug)] -pub struct Animations<'a> { - /// Internal animation iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every buffer in a glTF asset. -#[derive(Clone, Debug)] -pub struct Buffers<'a> { - /// Internal buffer iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every buffer view in a glTF asset. -#[derive(Clone, Debug)] -pub struct Views<'a> { - /// Internal buffer view iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every camera in a glTF asset. -#[derive(Clone, Debug)] -pub struct Cameras<'a> { - /// Internal buffer view iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every pre-loaded image in a glTF asset. -#[derive(Clone, Debug)] -pub struct Images<'a> { - /// Internal image iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every light in a glTF asset. -#[cfg(feature = "KHR_lights_punctual")] -#[derive(Clone, Debug)] -pub struct Lights<'a> { - /// Internal image iterator. - pub(crate) iter: - iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every variant in a glTF asset. -#[cfg(feature = "KHR_materials_variants")] -#[derive(Clone, Debug)] -pub struct Variants<'a> { - /// Internal variant iterator. - pub(crate) iter: - iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every material in a glTF asset. -#[derive(Clone, Debug)] -pub struct Materials<'a> { - /// Internal material iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every mesh in a glTF asset. -#[derive(Clone, Debug)] -pub struct Meshes<'a> { - /// Internal mesh iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every node in a glTF asset. -#[derive(Clone, Debug)] -pub struct Nodes<'a> { - /// Internal node iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every sampler in a glTF asset. -#[derive(Clone, Debug)] -pub struct Samplers<'a> { - /// Internal sampler iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every scene in a glTF asset. -#[derive(Clone, Debug)] -pub struct Scenes<'a> { - /// Internal scene iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every skin in a glTF asset. -#[derive(Clone, Debug)] -pub struct Skins<'a> { - /// Internal skin iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -/// An `Iterator` that visits every texture in a glTF asset. -#[derive(Clone, Debug)] -pub struct Textures<'a> { - /// Internal texture iterator. - pub(crate) iter: iter::Enumerate>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -impl<'a> ExactSizeIterator for Accessors<'a> {} -impl<'a> Iterator for Accessors<'a> { - type Item = Accessor<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Accessor::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Accessor::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Accessor::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for Animations<'a> {} -impl<'a> Iterator for Animations<'a> { - type Item = Animation<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Animation::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Animation::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Animation::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for Buffers<'a> {} -impl<'a> Iterator for Buffers<'a> { - type Item = Buffer<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Buffer::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Buffer::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Buffer::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for ExtensionsUsed<'a> {} -impl<'a> Iterator for ExtensionsUsed<'a> { - type Item = &'a str; - fn next(&mut self) -> Option { - self.0.next().map(String::as_str) - } - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } - fn count(self) -> usize { - self.0.count() - } - fn last(self) -> Option { - self.0.last().map(String::as_str) - } - fn nth(&mut self, n: usize) -> Option { - self.0.nth(n).map(String::as_str) - } -} - -impl<'a> ExactSizeIterator for ExtensionsRequired<'a> {} -impl<'a> Iterator for ExtensionsRequired<'a> { - type Item = &'a str; - fn next(&mut self) -> Option { - self.0.next().map(String::as_str) - } - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } - fn count(self) -> usize { - self.0.count() - } - fn last(self) -> Option { - self.0.last().map(String::as_str) - } - fn nth(&mut self, n: usize) -> Option { - self.0.nth(n).map(String::as_str) - } -} - -impl<'a> ExactSizeIterator for Views<'a> {} -impl<'a> Iterator for Views<'a> { - type Item = View<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| View::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| View::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| View::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for Cameras<'a> {} -impl<'a> Iterator for Cameras<'a> { - type Item = Camera<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Camera::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Camera::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Camera::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for Images<'a> {} -impl<'a> Iterator for Images<'a> { - type Item = Image<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Image::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Image::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Image::new(self.document, index, json)) - } -} - -#[cfg(feature = "KHR_lights_punctual")] -impl<'a> ExactSizeIterator for Lights<'a> {} - -#[cfg(feature = "KHR_lights_punctual")] -impl<'a> Iterator for Lights<'a> { - type Item = crate::khr_lights_punctual::Light<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| crate::khr_lights_punctual::Light::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| crate::khr_lights_punctual::Light::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| crate::khr_lights_punctual::Light::new(self.document, index, json)) - } -} - -#[cfg(feature = "KHR_materials_variants")] -impl<'a> ExactSizeIterator for Variants<'a> {} - -#[cfg(feature = "KHR_materials_variants")] -impl<'a> Iterator for Variants<'a> { - type Item = crate::khr_materials_variants::Variant<'a>; - fn next(&mut self) -> Option { - self.iter.next().map(|(index, json)| { - crate::khr_materials_variants::Variant::new(self.document, index, json) - }) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| crate::khr_materials_variants::Variant::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter.nth(n).map(|(index, json)| { - crate::khr_materials_variants::Variant::new(self.document, index, json) - }) - } -} - -impl<'a> ExactSizeIterator for Materials<'a> {} -impl<'a> Iterator for Materials<'a> { - type Item = Material<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Material::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Material::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Material::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for Meshes<'a> {} -impl<'a> Iterator for Meshes<'a> { - type Item = Mesh<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Mesh::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Mesh::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Mesh::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for Nodes<'a> {} -impl<'a> Iterator for Nodes<'a> { - type Item = Node<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Node::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Node::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Node::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for Samplers<'a> {} -impl<'a> Iterator for Samplers<'a> { - type Item = Sampler<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Sampler::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Sampler::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Sampler::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for Scenes<'a> {} -impl<'a> Iterator for Scenes<'a> { - type Item = Scene<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Scene::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Scene::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Scene::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for Skins<'a> {} -impl<'a> Iterator for Skins<'a> { - type Item = Skin<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Skin::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Skin::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Skin::new(self.document, index, json)) - } -} - -impl<'a> ExactSizeIterator for Textures<'a> {} -impl<'a> Iterator for Textures<'a> { - type Item = Texture<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Texture::new(self.document, index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|(index, json)| Texture::new(document, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Texture::new(self.document, index, json)) - } -} diff --git a/src/khr_lights_punctual.rs b/src/khr_lights_punctual.rs deleted file mode 100644 index 2145673a..00000000 --- a/src/khr_lights_punctual.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::Document; -use gltf_json::Extras; - -/// A light in the scene. -pub struct Light<'a> { - /// The parent `Document` struct. - #[allow(dead_code)] - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::extensions::scene::khr_lights_punctual::Light, -} - -impl<'a> Light<'a> { - /// Constructs a `Light`. - pub(crate) fn new( - document: &'a Document, - index: usize, - json: &'a json::extensions::scene::khr_lights_punctual::Light, - ) -> Self { - Self { - document, - index, - json, - } - } - - /// Color of the light source. - pub fn color(&self) -> [f32; 3] { - self.json.color - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a Extras { - &self.json.extras - } - - /// Intensity of the light source. `point` and `spot` lights use luminous intensity - /// in candela (lm/sr) while `directional` lights use illuminance in lux (lm/m^2). - pub fn intensity(&self) -> f32 { - self.json.intensity - } - - /// A distance cutoff at which the light's intensity may be considered to have reached - /// zero. - pub fn range(&self) -> Option { - self.json.range - } - - /// Specifies the light subcategory. - pub fn kind(&self) -> Kind { - use json::extensions::scene::khr_lights_punctual::Type; - match self.json.type_.unwrap() { - Type::Directional => Kind::Directional, - Type::Point => Kind::Point, - Type::Spot => { - let args = self.json.spot.as_ref().unwrap(); - Kind::Spot { - inner_cone_angle: args.inner_cone_angle, - outer_cone_angle: args.outer_cone_angle, - } - } - } - } -} - -/// Light subcategory. -pub enum Kind { - /// Directional lights are light sources that act as though they are infinitely far away - /// and emit light in the direction of the local -z axis. This light type inherits the - /// orientation of the node that it belongs to; position and scale are ignored except for - /// their effect on the inherited node orientation. Because it is at an infinite distance, - /// the light is not attenuated. Its intensity is defined in lumens per metre squared, or - /// lux (lm/m2). - Directional, - - /// Point lights emit light in all directions from their position in space; rotation and - /// scale are ignored except for their effect on the inherited node position. The - /// brightness of the light attenuates in a physically correct manner as distance - /// increases from the light's position (i.e. brightness goes like the inverse square of - /// the distance). Point light intensity is defined in candela, which is lumens per square - /// radian (lm/sr). - Point, - - /// Spot lights emit light in a cone in the direction of the local -z axis. The angle and - /// falloff of the cone is defined using two numbers, the `inner_cone_angle` and - /// `outer_cone_angle`. As with point lights, the brightness also attenuates in a - /// physically correct manner as distance increases from the light's position (i.e. - /// brightness goes like the inverse square of the distance). Spot light intensity refers - /// to the brightness inside the `inner_cone_angle` (and at the location of the light) and - /// is defined in candela, which is lumens per square radian (lm/sr). Engines that don't - /// support two angles for spotlights should use `outer_cone_angle` as the spotlight angle - /// (leaving `inner_cone_angle` to implicitly be 0). - /// - /// A spot light's position and orientation are inherited from its node transform. - /// Inherited scale does not affect cone shape, and is ignored except for its effect on - /// position and orientation. - Spot { - /// Angle in radians from centre of spotlight where falloff begins. - inner_cone_angle: f32, - - /// Angle in radians from centre of spotlight where falloff ends. - outer_cone_angle: f32, - }, -} diff --git a/src/khr_materials_variants.rs b/src/khr_materials_variants.rs deleted file mode 100644 index 682a03d9..00000000 --- a/src/khr_materials_variants.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::{Document, Material}; - -/// A variant. -pub struct Variant<'a> { - /// The parent `Document` struct. - #[allow(dead_code)] - document: &'a Document, - - /// The corresponding JSON index. - #[allow(dead_code)] - index: usize, - - /// The corresponding JSON struct. - json: &'a json::extensions::scene::khr_materials_variants::Variant, -} - -impl<'a> Variant<'a> { - /// Constructs a `Variant`. - pub(crate) fn new( - document: &'a Document, - index: usize, - json: &'a json::extensions::scene::khr_materials_variants::Variant, - ) -> Self { - Self { - document, - index, - json, - } - } - - /// Name of the variant. - pub fn name(&self) -> &'a str { - &self.json.name - } -} - -/// A mapping. -pub struct Mapping<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::extensions::mesh::Mapping, -} - -impl<'a> Mapping<'a> { - /// Constructs a `Mapping`. - pub(crate) fn new(document: &'a Document, json: &'a json::extensions::mesh::Mapping) -> Self { - Self { document, json } - } - - /// Get the variant indices that use this material. - pub fn variants(&self) -> &'a [u32] { - &self.json.variants - } - - /// Get the corresponding material. - pub fn material(&self) -> Material<'a> { - self.document - .materials() - .nth(self.json.material as usize) - .unwrap_or_else(|| Material::default(self.document)) - } -} diff --git a/src/lib.rs b/src/lib.rs index 33f30406..695a13af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,189 +8,143 @@ //! efficient runtime transmission of 3D scenes. The crate aims to provide //! rustic utilities that make working with glTF simple and intuitive. //! -//! # Installation -//! -//! Add `gltf` to your `Cargo.toml`: -//! -//! ```toml -//! [dependencies.gltf] -//! version = "1" -//! ``` -//! -//! # Examples -//! -//! ## Basic usage -//! -//! Walking the node hierarchy. -//! -//! ``` -//! # fn run() -> Result<(), Box> { -//! # use gltf::Gltf; -//! let gltf = Gltf::open("examples/Box.gltf")?; -//! for scene in gltf.scenes() { -//! for node in scene.nodes() { -//! println!( -//! "Node #{} has {} children", -//! node.index(), -//! node.children().count(), -//! ); -//! } -//! } -//! # Ok(()) -//! # } -//! # fn main() { -//! # let _ = run().expect("runtime error"); -//! # } -//! ``` -//! -//! ## Import function -//! -//! Reading a glTF document plus its buffers and images from the -//! file system. -//! -//! ``` -//! # fn run() -> Result<(), Box> { -//! let (document, buffers, images) = gltf::import("examples/Box.gltf")?; -//! assert_eq!(buffers.len(), document.buffers().count()); -//! assert_eq!(images.len(), document.images().count()); -//! # Ok(()) -//! # } -//! # fn main() { -//! # let _ = run().expect("runtime error"); -//! # } -//! ``` -//! -//! ### Note -//! -//! This function is provided as a convenience for loading glTF and associated -//! resources from the file system. It is suitable for real world use but may -//! not be suitable for all real world use cases. More complex import scenarios -//! such downloading from web URLs are not handled by this function. These -//! scenarios are delegated to the user. -//! -//! You can read glTF without loading resources by constructing the [`Gltf`] -//! (standard glTF) or [`Glb`] (binary glTF) data structures explicitly. Buffer -//! and image data can then be imported separately using [`import_buffers`] and -//! [`import_images`] respectively. -//! //! [glTF 2.0]: https://www.khronos.org/gltf -//! [`Gltf`]: struct.Gltf.html -//! [`Glb`]: struct.Glb.html -//! [`Node`]: struct.Node.html -//! [`Scene`]: struct.Scene.html - -#[cfg(test)] -#[macro_use] -extern crate approx; -#[cfg(feature = "import")] -extern crate image as image_crate; -#[macro_use] -extern crate lazy_static; -/// Contains (de)serializable data structures that match the glTF JSON text. -pub extern crate gltf_json as json; +use std::{fs, io, ops, result}; -/// Accessors for reading vertex attributes from buffer views. +/// Typed views into binary buffer data. pub mod accessor; -/// Animations, their channels, targets, and samplers. +/// Animation tracks and samplers. pub mod animation; -/// Primitives for working with binary glTF. +/// Asset metadata. +pub mod asset; + +/// Binary glTF. pub mod binary; -/// Buffers and buffer views. +/// Binary buffer data declarations. pub mod buffer; -/// Cameras and their projections. +/// Scene viewpoints. pub mod camera; -/// Images that may be used by textures. +/// Binary image data declarations. pub mod image; -/// The reference importer. +/// Reference importer implementation. #[cfg(feature = "import")] -#[cfg_attr(docsrs, doc(cfg(feature = "import")))] -mod import; - -/// Iterators for walking the glTF node hierarchy. -pub mod iter; - -/// Support for the `KHR_lights_punctual` extension. -#[cfg(feature = "KHR_lights_punctual")] -#[cfg_attr(docsrs, doc(cfg(feature = "KHR_lights_punctual")))] -pub mod khr_lights_punctual; - -/// Support for the `KHR_materials_variants` extension. -#[cfg(feature = "KHR_materials_variants")] -#[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_variants")))] -pub mod khr_materials_variants; +pub mod import; -/// Material properties of primitives. +/// Material properties for rendering primitives. pub mod material; -/// For internal use. -mod math; - -/// Meshes and their primitives. +/// Renderable geometry. pub mod mesh; -/// The glTF node heirarchy. +/// JSON paths. +pub mod path; + +/// The root glTF data structure. +pub mod root; + +/// Scene graph structure. pub mod scene; -/// Mesh skinning primitives. +/// Skeletal animations. pub mod skin; -/// Textures and their samplers. +/// Image sampling techniques. pub mod texture; -#[cfg(feature = "extensions")] -use json::Value; -#[cfg(feature = "extensions")] -use serde_json::Map; +/// Validation implementation details. +pub mod validation; + +/// Wrapper implementation details. +pub mod wrapper; #[doc(inline)] -pub use self::accessor::Accessor; +pub use accessor::Accessor; #[doc(inline)] -pub use self::animation::Animation; +pub use animation::Animation; #[doc(inline)] -pub use self::binary::Glb; +pub use asset::Asset; #[doc(inline)] -pub use self::buffer::Buffer; +pub use buffer::Buffer; #[doc(inline)] -pub use self::camera::Camera; +pub use camera::Camera; #[doc(inline)] -pub use self::image::Image; -#[cfg(feature = "import")] +pub use image::Image; #[doc(inline)] -pub use self::import::import; #[cfg(feature = "import")] +pub use import::{import, import_slice}; #[doc(inline)] -pub use self::import::import_buffers; -#[cfg(feature = "import")] +pub use material::Material; #[doc(inline)] -pub use self::import::import_images; -#[cfg(feature = "import")] +pub use mesh::Mesh; +#[doc(inline)] +pub use scene::Node; #[doc(inline)] -pub use self::import::import_slice; +pub use scene::Scene; #[doc(inline)] -pub use self::material::Material; +pub use skin::Skin; #[doc(inline)] -pub use self::mesh::{Attribute, Mesh, Primitive, Semantic}; +pub use texture::Texture; + #[doc(inline)] -pub use self::scene::{Node, Scene}; +pub use self::path::Path; #[doc(inline)] -pub use self::skin::Skin; +pub use self::root::Index; #[doc(inline)] -pub use self::texture::Texture; +pub use self::root::Root; -use std::path::Path; -use std::{fs, io, ops, result}; +#[doc(inline)] +pub use serde_json::Value; + +pub(crate) use wrapper::Wrap; + +/// Untyped extension object. +pub type UnrecognizedExtensions = serde_json::Map; -pub(crate) trait Normalize { - fn normalize(self) -> T; +/// Data type of the `extras` attribute on all glTF objects. +pub type Extras = std::boxed::Box; + +/// Provides a type with a well-defined but not necessarily valid default value. +/// +/// If a type implements `Default` then its stub value will simply be its default value. +pub trait Stub { + /// Stub value for this type. + fn stub() -> Self; +} + +impl Stub for T { + fn stub() -> Self { + T::default() + } } +impl Stub for Index { + fn stub() -> Self { + Index::new(u32::MAX) + } +} + +macro_rules! trivial_impl_wrap { + ($($ty:ty),*) => { + $( + impl<'a> crate::Wrap<'a> for $ty { + type Wrapped = $ty; + fn wrap(&'a self, _root: &'a crate::Root) -> Self::Wrapped { + *self + } + } + )* + }; +} + +trivial_impl_wrap!(i8, i16, i32, i64, isize, f32, f64, u8, u16, u32, u64, usize); + /// Result type for convenience. pub type Result = result::Result; @@ -220,7 +174,7 @@ pub enum Error { }, /// JSON deserialization error. - Deserialize(json::Error), + Deserialize(serde_json::Error), /// Standard I/O error. Io(std::io::Error), @@ -256,28 +210,36 @@ pub enum Error { UnsupportedScheme, /// glTF validation error. - Validation(Vec<(json::Path, json::validation::Error)>), + Validation(Vec<(Path, validation::Error)>), } -/// glTF JSON wrapper plus binary payload. +fn validate(root: &Root) -> Result<()> { + let mut errors = Vec::new(); + validation::Validate::validate(root, root, Path::new, &mut |path, error| { + errors.push((path(), error)) + }); + if errors.is_empty() { + Ok(()) + } else { + Err(Error::Validation(errors)) + } +} + +/// glTF JSON plus binary payload. #[derive(Clone, Debug)] pub struct Gltf { /// The glTF JSON wrapper. - pub document: Document, + pub root: Root, /// The glTF binary payload in the case of binary glTF. pub blob: Option>, } -/// glTF JSON wrapper. -#[derive(Clone, Debug)] -pub struct Document(json::Root); - impl Gltf { /// Convenience function that loads glTF from the file system. pub fn open

(path: P) -> Result where - P: AsRef, + P: AsRef, { let file = fs::File::open(path)?; let reader = io::BufReader::new(file); @@ -293,18 +255,17 @@ impl Gltf { let mut magic = [0u8; 4]; reader.read_exact(&mut magic)?; reader.seek(io::SeekFrom::Current(-4))?; - let (json, blob): (json::Root, Option>); + let (root, blob): (Root, Option>); if magic.starts_with(b"glTF") { let mut glb = binary::Glb::from_reader(reader)?; // TODO: use `json::from_reader` instead of `json::from_slice` - json = json::deserialize::from_slice(&glb.json)?; + root = serde_json::from_slice(&glb.json)?; blob = glb.bin.take().map(|x| x.into_owned()); } else { - json = json::deserialize::from_reader(reader)?; + root = serde_json::from_reader(reader)?; blob = None; }; - let document = Document::from_json_without_validation(json); - Ok(Gltf { document, blob }) + Ok(Gltf { root, blob }) } /// Loads glTF from a reader. @@ -313,265 +274,43 @@ impl Gltf { R: io::Read + io::Seek, { let gltf = Self::from_reader_without_validation(reader)?; - gltf.document.validate()?; + validate(&gltf.root)?; Ok(gltf) } /// Loads glTF from a slice of bytes without performing validation /// checks. pub fn from_slice_without_validation(slice: &[u8]) -> Result { - let (json, blob): (json::Root, Option>); + let (root, blob): (Root, Option>); if slice.starts_with(b"glTF") { let mut glb = binary::Glb::from_slice(slice)?; - json = json::deserialize::from_slice(&glb.json)?; + root = serde_json::from_slice(&glb.json)?; blob = glb.bin.take().map(|x| x.into_owned()); } else { - json = json::deserialize::from_slice(slice)?; + root = serde_json::from_slice(slice)?; blob = None; }; - let document = Document::from_json_without_validation(json); - Ok(Gltf { document, blob }) + Ok(Gltf { root, blob }) } /// Loads glTF from a slice of bytes. pub fn from_slice(slice: &[u8]) -> Result { let gltf = Self::from_slice_without_validation(slice)?; - gltf.document.validate()?; + validate(&gltf.root)?; Ok(gltf) } } impl ops::Deref for Gltf { - type Target = Document; + type Target = Root; fn deref(&self) -> &Self::Target { - &self.document + &self.root } } impl ops::DerefMut for Gltf { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.document - } -} - -impl Document { - /// Loads glTF from pre-deserialized JSON. - pub fn from_json(json: json::Root) -> Result { - let document = Self::from_json_without_validation(json); - document.validate()?; - Ok(document) - } - - /// Loads glTF from pre-deserialized JSON without performing - /// validation checks. - pub fn from_json_without_validation(json: json::Root) -> Self { - Document(json) - } - - /// Unwraps the glTF document. - pub fn into_json(self) -> json::Root { - self.0 - } - - /// Unwraps the glTF document, without consuming it. - pub fn as_json(&self) -> &json::Root { - &self.0 - } - - /// Perform validation checks on loaded glTF. - pub(crate) fn validate(&self) -> Result<()> { - use json::validation::Validate; - let mut errors = Vec::new(); - self.0 - .validate(&self.0, json::Path::new, &mut |path, error| { - errors.push((path(), error)) - }); - if errors.is_empty() { - Ok(()) - } else { - Err(Error::Validation(errors)) - } - } - - /// Returns an `Iterator` that visits the accessors of the glTF asset. - pub fn accessors(&self) -> iter::Accessors { - iter::Accessors { - iter: self.0.accessors.iter().enumerate(), - document: self, - } - } - - /// Returns an `Iterator` that visits the animations of the glTF asset. - pub fn animations(&self) -> iter::Animations { - iter::Animations { - iter: self.0.animations.iter().enumerate(), - document: self, - } - } - - /// Returns an `Iterator` that visits the pre-loaded buffers of the glTF asset. - pub fn buffers(&self) -> iter::Buffers { - iter::Buffers { - iter: self.0.buffers.iter().enumerate(), - document: self, - } - } - - /// Returns an `Iterator` that visits the cameras of the glTF asset. - pub fn cameras(&self) -> iter::Cameras { - iter::Cameras { - iter: self.0.cameras.iter().enumerate(), - document: self, - } - } - - /// Returns the default scene, if provided. - pub fn default_scene(&self) -> Option { - self.0 - .scene - .as_ref() - .map(|index| self.scenes().nth(index.value()).unwrap()) - } - - /// Returns the extensions referenced in this .document file. - pub fn extensions_used(&self) -> iter::ExtensionsUsed { - iter::ExtensionsUsed(self.0.extensions_used.iter()) - } - - /// Returns the extensions required to load and render this asset. - pub fn extensions_required(&self) -> iter::ExtensionsRequired { - iter::ExtensionsRequired(self.0.extensions_required.iter()) - } - - /// Returns an `Iterator` that visits the pre-loaded images of the glTF asset. - pub fn images(&self) -> iter::Images { - iter::Images { - iter: self.0.images.iter().enumerate(), - document: self, - } - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let root = self.0.extensions.as_ref()?; - Some(&root.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let root = self.0.extensions.as_ref()?; - root.others.get(ext_name) - } - - /// Returns an `Iterator` that visits the lights of the glTF asset as defined by the - /// `KHR_lights_punctual` extension. - #[cfg(feature = "KHR_lights_punctual")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_lights_punctual")))] - pub fn lights(&self) -> Option { - let iter = self - .0 - .extensions - .as_ref()? - .khr_lights_punctual - .as_ref()? - .lights - .iter() - .enumerate(); - - Some(iter::Lights { - iter, - document: self, - }) - } - - /// Returns an `Iterator` that visits the variants of the glTF asset as defined by the - /// `KHR_materials_variants` extension. - #[cfg(feature = "KHR_materials_variants")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_variants")))] - pub fn variants(&self) -> Option { - let iter = self - .0 - .extensions - .as_ref()? - .khr_materials_variants - .as_ref()? - .variants - .iter() - .enumerate(); - - Some(iter::Variants { - iter, - document: self, - }) - } - - /// Returns an `Iterator` that visits the materials of the glTF asset. - pub fn materials(&self) -> iter::Materials { - iter::Materials { - iter: self.0.materials.iter().enumerate(), - document: self, - } - } - - /// Returns an `Iterator` that visits the meshes of the glTF asset. - pub fn meshes(&self) -> iter::Meshes { - iter::Meshes { - iter: self.0.meshes.iter().enumerate(), - document: self, - } - } - - /// Returns an `Iterator` that visits the nodes of the glTF asset. - pub fn nodes(&self) -> iter::Nodes { - iter::Nodes { - iter: self.0.nodes.iter().enumerate(), - document: self, - } - } - - /// Returns an `Iterator` that visits the samplers of the glTF asset. - pub fn samplers(&self) -> iter::Samplers { - iter::Samplers { - iter: self.0.samplers.iter().enumerate(), - document: self, - } - } - - /// Returns an `Iterator` that visits the scenes of the glTF asset. - pub fn scenes(&self) -> iter::Scenes { - iter::Scenes { - iter: self.0.scenes.iter().enumerate(), - document: self, - } - } - - /// Returns an `Iterator` that visits the skins of the glTF asset. - pub fn skins(&self) -> iter::Skins { - iter::Skins { - iter: self.0.skins.iter().enumerate(), - document: self, - } - } - - /// Returns an `Iterator` that visits the textures of the glTF asset. - pub fn textures(&self) -> iter::Textures { - iter::Textures { - iter: self.0.textures.iter().enumerate(), - document: self, - } - } - - /// Returns an `Iterator` that visits the pre-loaded buffer views of the glTF - /// asset. - pub fn views(&self) -> iter::Views { - iter::Views { - iter: self.0.buffer_views.iter().enumerate(), - document: self, - } + &mut self.root } } @@ -643,200 +382,27 @@ impl From for Error { } } -impl From for Error { - fn from(err: json::Error) -> Self { +impl From for Error { + fn from(err: serde_json::Error) -> Self { Error::Deserialize(err) } } -impl From> for Error { - fn from(errs: Vec<(json::Path, json::validation::Error)>) -> Self { +impl From> for Error { + fn from(errs: Vec<(Path, validation::Error)>) -> Self { Error::Validation(errs) } } -impl Normalize for i8 { - fn normalize(self) -> i8 { - self - } -} - -impl Normalize for i8 { - fn normalize(self) -> u8 { - self.max(0) as u8 * 2 - } -} - -impl Normalize for i8 { - fn normalize(self) -> i16 { - self as i16 * 0x100 - } -} - -impl Normalize for i8 { - fn normalize(self) -> u16 { - self.max(0) as u16 * 0x200 - } -} - -impl Normalize for i8 { - fn normalize(self) -> f32 { - (self as f32 * 127.0_f32.recip()).max(-1.0) - } -} - -impl Normalize for u8 { - fn normalize(self) -> i8 { - (self / 2) as i8 - } -} - -impl Normalize for u8 { - fn normalize(self) -> u8 { - self - } -} - -impl Normalize for u8 { - fn normalize(self) -> i16 { - self as i16 * 0x80 - } -} - -impl Normalize for u8 { - fn normalize(self) -> u16 { - self as u16 * 0x100 - } -} - -impl Normalize for u8 { - fn normalize(self) -> f32 { - self as f32 * 255.0_f32.recip() - } -} - -impl Normalize for i16 { - fn normalize(self) -> i8 { - (self / 0x100) as i8 - } -} - -impl Normalize for i16 { - fn normalize(self) -> u8 { - (self.max(0) / 0x80) as u8 - } -} - -impl Normalize for i16 { - fn normalize(self) -> i16 { - self - } -} - -impl Normalize for i16 { - fn normalize(self) -> u16 { - self.max(0) as u16 * 2 - } -} - -impl Normalize for i16 { - fn normalize(self) -> f32 { - (self as f32 * 32767.0_f32.recip()).max(-1.0) - } -} - -impl Normalize for u16 { - fn normalize(self) -> i8 { - (self / 0x200) as i8 - } -} - -impl Normalize for u16 { - fn normalize(self) -> u8 { - (self / 0x100) as u8 - } -} - -impl Normalize for u16 { - fn normalize(self) -> i16 { - (self / 2) as i16 - } -} - -impl Normalize for u16 { - fn normalize(self) -> u16 { - self - } -} - -impl Normalize for u16 { - fn normalize(self) -> f32 { - self as f32 * 65535.0_f32.recip() - } -} - -impl Normalize for f32 { - fn normalize(self) -> i8 { - (self * 127.0) as i8 - } -} - -impl Normalize for f32 { - fn normalize(self) -> u8 { - (self.max(0.0) * 255.0) as u8 - } -} - -impl Normalize for f32 { - fn normalize(self) -> i16 { - (self * 32767.0) as i16 - } -} - -impl Normalize for f32 { - fn normalize(self) -> u16 { - (self.max(0.0) * 65535.0) as u16 - } -} - -impl Normalize for f32 { - fn normalize(self) -> f32 { - self - } -} - -impl Normalize<[T; 2]> for [U; 2] -where - U: Normalize + Copy, -{ - fn normalize(self) -> [T; 2] { - [self[0].normalize(), self[1].normalize()] - } -} - -impl Normalize<[T; 3]> for [U; 3] -where - U: Normalize + Copy, -{ - fn normalize(self) -> [T; 3] { - [ - self[0].normalize(), - self[1].normalize(), - self[2].normalize(), - ] - } -} - -impl Normalize<[T; 4]> for [U; 4] -where - U: Normalize + Copy, -{ - fn normalize(self) -> [T; 4] { - [ - self[0].normalize(), - self[1].normalize(), - self[2].normalize(), - self[3].normalize(), - ] - } -} +/// Names of glTF 2.0 extensions supported by the library. +pub const SUPPORTED_EXTENSIONS: &[&str] = &[ + "KHR_animation_pointer", + "KHR_lights_punctual", + "KHR_materials_emissive_strength", + "KHR_materials_ior", + "KHR_materials_pbrSpecularGlossiness", + "KHR_materials_unlit", + "KHR_materials_transmission", + "KHR_texture_basisu", + "KHR_texture_transform", +]; diff --git a/src/material.rs b/src/material.rs index b0f28992..8bbb175a 100644 --- a/src/material.rs +++ b/src/material.rs @@ -1,700 +1,511 @@ -use crate::{texture, Document}; +use crate::validation::Validate; +use crate::{texture, Extras, Index, UnrecognizedExtensions}; -pub use json::material::AlphaMode; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -lazy_static! { - static ref DEFAULT_MATERIAL: json::material::Material = Default::default(); +/// Support for the `KHR_materials_pbrSpecularGlossiness` extension. +pub mod khr_materials_pbr_specular_glossiness { + /// A set of parameter values that are used to define the specular-glossiness + /// material model from Physically-Based Rendering (PBR) methodology. + /// + /// This model supports more materials than metallic-roughness, at the cost of + /// increased memory use. When both are available, specular-glossiness should be + /// preferred. + #[derive( + Clone, + Debug, + gltf_derive::Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct PbrSpecularGlossiness { + /// The material's diffuse factor. + /// + /// The RGBA components of the reflected diffuse color of the + /// material. Metals have a diffuse value of `[0.0, 0.0, 0.0]`. The fourth + /// component (A) is the alpha coverage of the material. The `alphaMode` + /// property specifies how alpha is interpreted. The values are linear. + #[gltf(default = [1.0; 4])] + pub diffuse_factor: [f32; 4], + + /// The diffuse texture. + /// + /// This texture contains RGB(A) components of the reflected diffuse color + /// of the material in sRGB color space. If the fourth component (A) is + /// present, it represents the alpha coverage of the material. Otherwise, an + /// alpha of 1.0 is assumed. The `alphaMode` property specifies how alpha is + /// interpreted. The stored texels must not be premultiplied. + pub diffuse_texture: Option, + + /// The material's specular factor. + #[gltf(default = [1.0; 3])] + pub specular_factor: [f32; 3], + + /// The glossiness or smoothness of the material. + /// + /// A value of 1.0 means the material has full glossiness or is perfectly + /// smooth. A value of 0.0 means the material has no glossiness or is + /// completely rough. This value is linear. + #[gltf(default = 1.0)] + pub glossiness_factor: f32, + + /// The specular-glossiness texture. + /// + /// A RGBA texture, containing the specular color of the material (RGB + /// components) and its glossiness (A component). The values are in sRGB + /// space. + pub specular_glossiness_texture: Option, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } } -/// The material appearance of a primitive. -#[derive(Clone, Debug)] -pub struct Material<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON index - `None` when the default material. - index: Option, +/// Support for the `KHR_materials_unlit` extension. +pub mod khr_materials_unlit { + /// Empty struct that should be present for primitives which should not be shaded with the PBR shading model. + #[derive( + Clone, + Debug, + Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct Unlit { + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } +} - /// The corresponding JSON struct. - json: &'a json::material::Material, +/// Support for the `KHR_materials_transmission` extension. +pub mod khr_materials_transmission { + /// Describes the optical transmission of a material. + #[derive( + Clone, + Debug, + Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct Transmission { + /// The base percentage of light that is transmitted through the surface. + /// + /// The amount of light that is transmitted by the surface rather than diffusely re-emitted. + /// This is a percentage of all the light that penetrates a surface (i.e. isn’t specularly reflected) + /// rather than a percentage of the total light that hits a surface. + /// A value of 1.0 means that 100% of the light that penetrates the surface is transmitted through. + #[gltf(default)] + pub transmission_factor: f32, + + /// The transmission texture. + /// + /// The R channel of this texture defines the amount of light that is transmitted by the surface + /// rather than diffusely re-emitted. A value of 1.0 in the red channel means that 100% of the light + /// that penetrates the surface (i.e. isn’t specularly reflected) is transmitted through. + /// The value is linear and is multiplied by the transmissionFactor to determine the total transmission value. + pub transmission_texture: Option, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } } -impl<'a> Material<'a> { - /// Constructs a `Material`. - pub(crate) fn new( - document: &'a Document, - index: usize, - json: &'a json::material::Material, - ) -> Self { - Self { - document, - index: Some(index), - json, - } +/// Support for the `KHR_materials_ior` extension. +pub mod khr_materials_ior { + /// Defines the index of refraction for a material. + #[derive( + Clone, + Debug, + gltf_derive::Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct Ior { + /// The index of refraction. + /// + /// Typical values for the index of refraction range from 1 to 2. + /// In rare cases, values greater than 2 are possible. + /// For example, the ior of water is 1.33, and diamond is 2.42. + #[gltf(default = 1.5)] + pub ior: f32, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, } +} - /// Constructs the default `Material`. - pub(crate) fn default(document: &'a Document) -> Self { - Self { - document, - index: None, - json: &DEFAULT_MATERIAL, - } +/// Support for the `KHR_materials_emissive_strength` extension. +pub mod khr_materials_emissive_strength { + /// Allows the strength of an emissive material to be adjusted. + #[derive( + Clone, + Debug, + gltf_derive::Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct EmissiveStrength { + /// The factor by which to scale the emissive factor or emissive texture. + #[gltf(default = 1.0)] + pub emissive_strength: f32, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, } +} - /// Returns the internal JSON index if this `Material` was explicity defined. - /// - /// This function returns `None` if the `Material` is the default material. - pub fn index(&self) -> Option { - self.index +/// Support for the `KHR_materials_volume` extension. +pub mod khr_materials_volume { + /// Volumetric material properties. + #[derive( + Clone, + Debug, + gltf_derive::Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct Volume { + /// The thickness of the volume beneath the surface. The value is + /// given in the coordinate space of the mesh. If the value is 0 + /// the material is thin-walled. Otherwise the material is a + /// volume boundary. The `doubleSided` property has no effect on + /// volume boundaries. Range is [0, +inf). + #[gltf(default)] + pub thickness_factor: f32, + + /// A texture that defines the thickness, stored in the G channel. + /// This will be multiplied by `thickness_factor`. Range is [0, 1]. + pub thickness_texture: Option, + + /// Density of the medium given as the average distance that light + /// travels in the medium before interacting with a particle. The + /// value is given in world space. Range is (0, +inf). + #[gltf(default = f32::INFINITY)] + pub attenuation_distance: f32, + + /// The color that white light turns into due to absorption when + /// reaching the attenuation distance. + #[gltf(default = [1.0; 3])] + pub attenuation_color: [f32; 3], + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, } +} - /// The optional alpha cutoff value of the material. - pub fn alpha_cutoff(&self) -> Option { - self.json.alpha_cutoff.map(|value| value.0) +/// Support for the `KHR_materials_volume` extension. +pub mod khr_materials_specular { + /// Allows the strength of specular reflections to be adjusted. + #[derive( + Clone, + Debug, + gltf_derive::Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct Specular { + /// The strength of the specular reflection. + #[gltf(default = 1.0)] + pub specular_factor: f32, + + /// A texture that defines the strength of the specular reflection, + /// stored in the alpha (`A`) channel. This will be multiplied by + /// `specular_factor`. + pub specular_texture: Option, + + /// The F0 (linear RGB) color of the specular reflection. + #[gltf(default = [1.0; 3])] + pub specular_color_factor: [f32; 3], + + /// A texture that defines the F0 color of the specular reflection, + /// stored in the `RGB` channels and encoded in sRGB. This texture + /// will be multiplied by `specular_color_factor`. + pub specular_color_texture: Option, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, } +} + +/// The alpha rendering mode of a material. +#[derive( + Clone, Copy, Debug, Default, serde_derive::Deserialize, Eq, PartialEq, serde_derive::Serialize, +)] +pub enum AlphaMode { + /// The alpha value is ignored and the rendered output is fully opaque. + #[default] + #[serde(rename = "OPAQUE")] + Opaque = 1, + + /// The rendered output is either fully opaque or fully transparent depending on + /// the alpha value and the specified alpha cutoff value. + #[serde(rename = "MASK")] + Mask, + + /// The alpha value is used, to determine the transparency of the rendered output. + /// The alpha cutoff value is ignored. + #[serde(rename = "BLEND")] + Blend, +} - /// The alpha rendering mode of the material. The material's alpha rendering - /// mode enumeration specifying the interpretation of the alpha value of the main - /// factor and texture. +impl Validate for AlphaMode {} + +/// The material appearance of a primitive. +#[derive( + Clone, + Debug, + gltf_derive::Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, +)] +pub struct Material { + /// The alpha cutoff value of the material. + #[gltf(default = 0.5)] + pub alpha_cutoff: f32, + + /// The alpha rendering mode of the material. + /// + /// The material's alpha rendering mode enumeration specifying the + /// interpretation of the alpha value of the main factor and texture. + /// + /// * In `Opaque` mode (default) the alpha value is ignored and the rendered + /// output is fully opaque. + /// + /// * In `Mask` mode, the rendered output is either fully opaque or fully + /// transparent depending on the alpha value and the specified alpha cutoff + /// value. /// - /// * In `Opaque` mode (default) the alpha value is ignored - /// and the rendered output is fully opaque. - /// * In `Mask` mode, the rendered - /// output is either fully opaque or fully transparent depending on the alpha - /// value and the specified alpha cutoff value. /// * In `Blend` mode, the alpha value is used to composite the source and - /// destination areas and the rendered output is combined with the background - /// using the normal painting operation (i.e. the Porter and Duff over - /// operator). - pub fn alpha_mode(&self) -> AlphaMode { - self.json.alpha_mode.unwrap() - } + /// destination areas and the rendered output is combined with the + /// background using the normal painting operation (i.e. the Porter and + /// Duff over operator). + #[gltf(default)] + pub alpha_mode: AlphaMode, /// Specifies whether the material is double-sided. /// /// * When this value is false, back-face culling is enabled. + /// /// * When this value is true, back-face culling is disabled and double sided - /// lighting is enabled. The back-face must have its normals reversed before - /// the lighting equation is evaluated. - pub fn double_sided(&self) -> bool { - self.json.double_sided - } + /// lighting is enabled. + /// + /// The back-face must have its normals reversed before the lighting + /// equation is evaluated. + pub double_sided: bool, /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(docsrs, doc(cfg(feature = "names")))] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } + pub name: Option, + + /// A set of parameter values that are used to define the metallic-roughness + /// material model from Physically-Based Rendering (PBR) methodology. When not + /// specified, all the default values of `pbrMetallicRoughness` apply. + pub pbr_metallic_roughness: Option, + + /// A tangent space normal map. The texture contains RGB components in linear + /// space. Each texel represents the XYZ components of a normal vector in + /// tangent space. Red [0 to 255] maps to X [-1 to 1]. Green [0 to 255] maps to + /// Y [-1 to 1]. Blue [128 to 255] maps to Z [1/255 to 1]. The normal vectors + /// use OpenGL conventions where +X is right and +Y is up. +Z points toward the + /// viewer. + pub normal_texture: Option, + + /// The occlusion map texture. The occlusion values are sampled from the R + /// channel. Higher values indicate areas that should receive full indirect + /// lighting and lower values indicate no indirect lighting. These values are + /// linear. If other channels are present (GBA), they are ignored for occlusion + /// calculations. + pub occlusion_texture: Option, - /// Parameter values that define the metallic-roughness material model from - /// Physically-Based Rendering (PBR) methodology. - pub fn pbr_metallic_roughness(&self) -> PbrMetallicRoughness<'a> { - PbrMetallicRoughness::new(self.document, &self.json.pbr_metallic_roughness) - } + /// The emissive map controls the color and intensity of the light being emitted + /// by the material. This texture contains RGB components in sRGB color space. + /// If a fourth component (A) is present, it is ignored. + pub emissive_texture: Option, - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } + /// The emissive color of the material. + #[gltf(default)] + pub emissive_factor: [f32; 3], - /// Get the value of an extension based on the name of the extension - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, key: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(key) - } + /// Support for the `KHR_materials_pbrSpecularGlossiness` extension. + #[gltf(extension = "KHR_materials_pbrSpecularGlossiness")] + pub pbr_specular_glossiness: + Option, - /// Parameter values that define the specular-glossiness material model from - /// Physically-Based Rendering (PBR) methodology. - #[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_pbrSpecularGlossiness")))] - pub fn pbr_specular_glossiness(&self) -> Option> { - self.json - .extensions - .as_ref()? - .pbr_specular_glossiness - .as_ref() - .map(|x| PbrSpecularGlossiness::new(self.document, x)) - } + /// Support for the `KHR_materials_unlit` extension. + #[gltf(extension = "KHR_materials_unlit")] + pub unlit: Option, - /// Parameter values that define the transmission of light through the material - #[cfg(feature = "KHR_materials_transmission")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_transmission")))] - pub fn transmission(&self) -> Option> { - self.json - .extensions - .as_ref()? - .transmission - .as_ref() - .map(|x| Transmission::new(self.document, x)) - } + /// Support for the `KHR_materials_transmission` extension. + #[gltf(extension = "KHR_materials_transmission")] + pub transmission: Option, - /// Parameter values that define the index of refraction of the material - #[cfg(feature = "KHR_materials_ior")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_ior")))] - pub fn ior(&self) -> Option { - self.json.extensions.as_ref()?.ior.as_ref().map(|x| x.ior.0) - } + /// Support for the `KHR_materials_volume` extension. + #[gltf(extension = "KHR_materials_volume")] + pub volume: Option, - /// Parameter value that adjusts the strength of emissive material properties - #[cfg(feature = "KHR_materials_emissive_strength")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_emissive_strength")))] - pub fn emissive_strength(&self) -> Option { - self.json - .extensions - .as_ref()? - .emissive_strength - .as_ref() - .map(|x| x.emissive_strength.0) - } + /// Support for the `KHR_materials_specular` extension. + #[gltf(extension = "KHR_materials_specular")] + pub specular: Option, - /// Parameter values that define a volume for the transmission of light through the material - #[cfg(feature = "KHR_materials_volume")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_volume")))] - pub fn volume(&self) -> Option> { - self.json - .extensions - .as_ref()? - .volume - .as_ref() - .map(|x| Volume::new(self.document, x)) - } + /// Support for the `KHR_materials_ior` extension. + #[gltf(extension = "KHR_materials_ior")] + pub ior: Option, - /// Parameter values that define the strength and colour of the specular reflection of the material - #[cfg(feature = "KHR_materials_specular")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_specular")))] - pub fn specular(&self) -> Option> { - self.json - .extensions - .as_ref()? - .specular - .as_ref() - .map(|x| Specular::new(self.document, x)) - } + /// Support for the `KHR_materials_emissive_strength` extension. + #[gltf(extension = "KHR_materials_emissive_strength")] + pub emissive_strength: Option, - /// A tangent space normal map. - /// - /// The texture contains RGB components in linear space. Each texel represents - /// the XYZ components of a normal vector in tangent space. - /// - /// * Red [0 to 255] maps to X [-1 to 1]. - /// * Green [0 to 255] maps to Y [-1 to 1]. - /// * Blue [128 to 255] maps to Z [1/255 to 1]. - /// - /// The normal vectors use OpenGL conventions where +X is right, +Y is up, and - /// +Z points toward the viewer. - pub fn normal_texture(&self) -> Option> { - self.json.normal_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - NormalTexture::new(texture, json) - }) - } - - /// The occlusion map texture. - /// - /// The occlusion values are sampled from the R channel. Higher values indicate - /// areas that should receive full indirect lighting and lower values indicate - /// no indirect lighting. These values are linear. - /// - /// If other channels are present (GBA), they are ignored for occlusion - /// calculations. - pub fn occlusion_texture(&self) -> Option> { - self.json.occlusion_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - OcclusionTexture::new(texture, json) - }) - } - - /// The emissive map texture. - /// - /// The emissive map controls the color and intensity of the light being - /// emitted by the material. - /// - /// This texture contains RGB components in sRGB color space. If a fourth - /// component (A) is present, it is ignored. - pub fn emissive_texture(&self) -> Option> { - self.json.emissive_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - texture::Info::new(texture, json) - }) - } - - /// The emissive color of the material. - /// - /// The default value is `[0.0, 0.0, 0.0]`. - pub fn emissive_factor(&self) -> [f32; 3] { - self.json.emissive_factor.0 - } - - /// Specifies whether the material is unlit. - /// - /// Returns `true` if the [`KHR_materials_unlit`] property was specified, in which - /// case the renderer should prefer to ignore all PBR values except `baseColor`. - /// - /// [`KHR_materials_unlit`]: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit#overview - #[cfg(feature = "KHR_materials_unlit")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_unlit")))] - pub fn unlit(&self) -> bool { - self.json - .extensions - .as_ref() - .map_or(false, |extensions| extensions.unlit.is_some()) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } + pub extras: Option, } /// A set of parameter values that are used to define the metallic-roughness /// material model from Physically-Based Rendering (PBR) methodology. -pub struct PbrMetallicRoughness<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::material::PbrMetallicRoughness, -} - -impl<'a> PbrMetallicRoughness<'a> { - /// Constructs `PbrMetallicRoughness`. - pub(crate) fn new( - document: &'a Document, - json: &'a json::material::PbrMetallicRoughness, - ) -> Self { - Self { document, json } - } - - /// Returns the material's base color factor. - /// - /// The default value is `[1.0, 1.0, 1.0, 1.0]`. - pub fn base_color_factor(&self) -> [f32; 4] { - self.json.base_color_factor.0 - } - - /// Returns the base color texture. The texture contains RGB(A) components - /// in sRGB color space. - pub fn base_color_texture(&self) -> Option> { - self.json.base_color_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - texture::Info::new(texture, json) - }) - } - - /// Returns the metalness factor of the material. - /// - /// The default value is `1.0`. - pub fn metallic_factor(&self) -> f32 { - self.json.metallic_factor.0 - } - - /// Returns the roughness factor of the material. +#[derive( + Clone, + Debug, + gltf_derive::Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, +)] +pub struct PbrMetallicRoughness { + /// The material's base color factor. + #[gltf(default = [1.0, 1.0, 1.0, 1.0])] + pub base_color_factor: [f32; 4], + + /// The base color texture. + pub base_color_texture: Option, + + /// The metalness of the material. + #[gltf(default = 1.0)] + pub metallic_factor: f32, + + /// The roughness of the material. /// /// * A value of 1.0 means the material is completely rough. /// * A value of 0.0 means the material is completely smooth. - /// - /// The default value is `1.0`. - pub fn roughness_factor(&self) -> f32 { - self.json.roughness_factor.0 - } + #[gltf(default = 1.0)] + pub roughness_factor: f32, /// The metallic-roughness texture. /// + /// This texture has two components: + /// /// The metalness values are sampled from the B channel. /// The roughness values are sampled from the G channel. /// These values are linear. If other channels are present (R or A), /// they are ignored for metallic-roughness calculations. - pub fn metallic_roughness_texture(&self) -> Option> { - self.json.metallic_roughness_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - texture::Info::new(texture, json) - }) - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Get the value of an extension based on the name of the extension - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, key: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(key) - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } -} - -/// A set of parameter values that are used to define the transmissions -/// factor of the material -#[cfg(feature = "KHR_materials_transmission")] -#[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_transmission")))] -pub struct Transmission<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::extensions::material::Transmission, -} - -#[cfg(feature = "KHR_materials_transmission")] -#[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_transmission")))] -impl<'a> Transmission<'a> { - /// Constructs `Ior`. - pub(crate) fn new( - document: &'a Document, - json: &'a json::extensions::material::Transmission, - ) -> Self { - Self { document, json } - } - - /// Returns the material's transmission factor. - /// - /// The default value is `0.0`. - pub fn transmission_factor(&self) -> f32 { - self.json.transmission_factor.0 - } + pub metallic_roughness_texture: Option, - /// Returns the transmission texture. - pub fn transmission_texture(&self) -> Option> { - self.json.transmission_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - texture::Info::new(texture, json) - }) - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } -} - -/// Parameter values that define a volume for the transmission of light through the material -#[cfg(feature = "KHR_materials_volume")] -#[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_volume")))] -pub struct Volume<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::extensions::material::Volume, -} - -#[cfg(feature = "KHR_materials_volume")] -#[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_volume")))] -impl<'a> Volume<'a> { - /// Constructs `Volume`. - pub(crate) fn new( - document: &'a Document, - json: &'a json::extensions::material::Volume, - ) -> Self { - Self { document, json } - } - - /// The thickness of the volume beneath the surface. The value is - /// given in the coordinate space of the mesh. If the value is 0 - /// the material is thin-walled. Otherwise the material is a - /// volume boundary. The `doubleSided` property has no effect on - /// volume boundaries. Range is [0, +inf). - pub fn thickness_factor(&self) -> f32 { - self.json.thickness_factor.0 - } - - /// A texture that defines the thickness, stored in the G channel. - /// This will be multiplied by `thickness_factor`. Range is [0, 1]. - pub fn thickness_texture(&self) -> Option> { - self.json.thickness_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - texture::Info::new(texture, json) - }) - } - - /// Density of the medium given as the average distance that light - /// travels in the medium before interacting with a particle. The - /// value is given in world space. Range is (0, +inf). - pub fn attenuation_distance(&self) -> f32 { - self.json.attenuation_distance.0 - } - - /// The color that white light turns into due to absorption when - /// reaching the attenuation distance. - pub fn attenuation_color(&self) -> [f32; 3] { - self.json.attenuation_color.0 - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } -} - -/// Parameter values that define the strength and colour of the specular reflection of the material -#[cfg(feature = "KHR_materials_specular")] -#[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_specular")))] -pub struct Specular<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::extensions::material::Specular, -} - -#[cfg(feature = "KHR_materials_specular")] -#[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_specular")))] -impl<'a> Specular<'a> { - /// Constructs `Volume`. - pub(crate) fn new( - document: &'a Document, - json: &'a json::extensions::material::Specular, - ) -> Self { - Self { document, json } - } - - /// The strength of the specular reflection. - pub fn specular_factor(&self) -> f32 { - self.json.specular_factor.0 - } - - /// A texture that defines the strength of the specular reflection, - /// stored in the alpha (`A`) channel. This will be multiplied by - /// `specular_factor`. - pub fn specular_texture(&self) -> Option> { - self.json.specular_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - texture::Info::new(texture, json) - }) - } - - /// The F0 color of the specular reflection (linear RGB). - pub fn specular_color_factor(&self) -> [f32; 3] { - self.json.specular_color_factor.0 - } - - /// A texture that defines the F0 color of the specular reflection, - /// stored in the `RGB` channels and encoded in sRGB. This texture - /// will be multiplied by `specular_color_factor`. - pub fn specular_color_texture(&self) -> Option> { - self.json.specular_color_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - texture::Info::new(texture, json) - }) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } -} - -/// A set of parameter values that are used to define the specular-glossiness -/// material model from Physically-Based Rendering (PBR) methodology. -#[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] -#[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_pbrSpecularGlossiness")))] -pub struct PbrSpecularGlossiness<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON struct. - json: &'a json::extensions::material::PbrSpecularGlossiness, -} - -#[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] -#[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_pbrSpecularGlossiness")))] -impl<'a> PbrSpecularGlossiness<'a> { - /// Constructs `PbrSpecularGlossiness`. - pub(crate) fn new( - document: &'a Document, - json: &'a json::extensions::material::PbrSpecularGlossiness, - ) -> Self { - Self { document, json } - } - - /// Returns the material's base color factor. - /// - /// The default value is `[1.0, 1.0, 1.0, 1.0]`. - pub fn diffuse_factor(&self) -> [f32; 4] { - self.json.diffuse_factor.0 - } - - /// Returns the base color texture. - pub fn diffuse_texture(&self) -> Option> { - self.json.diffuse_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - texture::Info::new(texture, json) - }) - } - - /// Returns the specular factor of the material. - /// - /// The default value is `[1.0, 1.0, 1.0]`. - pub fn specular_factor(&self) -> [f32; 3] { - self.json.specular_factor.0 - } - - /// Returns the glossiness factor of the material. - /// - /// A value of 1.0 means the material has full glossiness or is perfectly - /// smooth. A value of 0.0 means the material has no glossiness or is - /// completely rough. This value is linear. - /// - /// The default value is `1.0`. - pub fn glossiness_factor(&self) -> f32 { - self.json.glossiness_factor.0 - } - - /// The specular-glossiness texture. - /// - /// A RGBA texture, containing the specular color of the material (RGB - /// components) and its glossiness (A component). The color values are in - /// sRGB space. - pub fn specular_glossiness_texture(&self) -> Option> { - self.json.specular_glossiness_texture.as_ref().map(|json| { - let texture = self.document.textures().nth(json.index.value()).unwrap(); - texture::Info::new(texture, json) - }) - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } + pub extras: Option, } /// Defines the normal texture of a material. -pub struct NormalTexture<'a> { - /// The parent `Texture` struct. - texture: texture::Texture<'a>, - - /// The corresponding JSON struct. - json: &'a json::material::NormalTexture, -} - -impl<'a> NormalTexture<'a> { - /// Constructs a `NormalTexture`. - pub(crate) fn new( - texture: texture::Texture<'a>, - json: &'a json::material::NormalTexture, - ) -> Self { - Self { texture, json } - } - - /// Returns the scalar multiplier applied to each normal vector of the texture. - pub fn scale(&self) -> f32 { - self.json.scale - } +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +pub struct NormalTexture { + /// The index of the texture. + pub index: Index, + + /// The scalar multiplier applied to each normal vector of the texture. + /// + /// This value is ignored if normalTexture is not specified. + #[gltf(default = 1.0)] + pub scale: f32, /// The set index of the texture's `TEXCOORD` attribute. - pub fn tex_coord(&self) -> u32 { - self.json.tex_coord - } + #[gltf(default)] + pub tex_coord: u32, - /// Returns the referenced texture. - pub fn texture(&self) -> texture::Texture<'a> { - self.texture.clone() - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Get the value of an extension based on the name of the extension - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, key: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(key) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } + pub extras: Option, } /// Defines the occlusion texture of a material. -pub struct OcclusionTexture<'a> { - /// The parent `Texture` struct. - texture: texture::Texture<'a>, - - /// The corresponding JSON struct. - json: &'a json::material::OcclusionTexture, -} - -impl<'a> OcclusionTexture<'a> { - /// Constructs a `OcclusionTexture`. - pub(crate) fn new( - texture: texture::Texture<'a>, - json: &'a json::material::OcclusionTexture, - ) -> Self { - Self { texture, json } - } - - /// Returns the scalar multiplier controlling the amount of occlusion applied. - pub fn strength(&self) -> f32 { - self.json.strength.0 - } +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +pub struct OcclusionTexture { + /// The index of the texture. + pub index: Index, + + /// The scalar multiplier controlling the amount of occlusion applied. + #[gltf(default = 1.0)] + pub strength: f32, - /// Returns the set index of the texture's `TEXCOORD` attribute. - pub fn tex_coord(&self) -> u32 { - self.json.tex_coord - } - - /// Returns the referenced texture. - pub fn texture(&self) -> texture::Texture<'a> { - self.texture.clone() - } + /// The set index of the texture's `TEXCOORD` attribute. + #[gltf(default)] + pub tex_coord: u32, - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Get the value of an extension based on the name of the extension - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, key: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(key) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } -} - -impl<'a> AsRef> for NormalTexture<'a> { - fn as_ref(&self) -> &texture::Texture<'a> { - &self.texture - } + pub extras: Option, } -impl<'a> AsRef> for OcclusionTexture<'a> { - fn as_ref(&self) -> &texture::Texture<'a> { - &self.texture +#[cfg(test)] +mod tests { + #[test] + fn material_default() { + let m: super::Material = Default::default(); + assert_eq!(m.alpha_cutoff, 0.5); + assert_eq!(m.alpha_mode, super::AlphaMode::Opaque); + assert!(!m.double_sided); + assert!(m.name.is_none()); + assert!(m.pbr_metallic_roughness.is_none()); + assert!(m.normal_texture.is_none()); + assert!(m.occlusion_texture.is_none()); + assert!(m.emissive_texture.is_none()); + assert_eq!(m.emissive_factor, [0.0, 0.0, 0.0]); + assert!(m.extras.is_none()); } } diff --git a/src/mesh/iter.rs b/src/mesh/iter.rs deleted file mode 100644 index 6cf16909..00000000 --- a/src/mesh/iter.rs +++ /dev/null @@ -1,177 +0,0 @@ -use std::{collections, iter, slice}; - -use super::{Attribute, Mesh, MorphTarget, Primitive}; -use crate::Document; - -/// An `Iterator` that visits the morph targets of a `Primitive`. -#[derive(Clone, Debug)] -pub struct MorphTargets<'a> { - /// The parent `Document` struct. - pub(crate) document: &'a Document, - - /// The internal JSON iterator. - pub(crate) iter: slice::Iter<'a, json::mesh::MorphTarget>, -} - -/// An `Iterator` that visits the attributes of a `Primitive`. -#[derive(Clone, Debug)] -pub struct Attributes<'a> { - /// The parent `Document` struct. - pub(crate) document: &'a Document, - - /// The parent `Primitive` struct. - #[allow(dead_code)] - pub(crate) prim: Primitive<'a>, - - /// The internal attribute iterator. - pub(crate) iter: collections::btree_map::Iter< - 'a, - json::validation::Checked, - json::Index, - >, -} - -/// An `Iterator` that visits the primitives of a `Mesh`. -#[derive(Clone, Debug)] -pub struct Primitives<'a> { - /// The parent `Mesh` struct. - pub(crate) mesh: Mesh<'a>, - - /// The internal JSON primitive iterator. - pub(crate) iter: iter::Enumerate>, -} - -impl<'a> ExactSizeIterator for Attributes<'a> {} -impl<'a> Iterator for Attributes<'a> { - type Item = Attribute<'a>; - fn next(&mut self) -> Option { - self.iter.next().map(|(key, index)| { - let semantic = key.as_ref().unwrap().clone(); - let accessor = self.document.accessors().nth(index.value()).unwrap(); - (semantic, accessor) - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a> ExactSizeIterator for Primitives<'a> {} -impl<'a> Iterator for Primitives<'a> { - type Item = Primitive<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(index, json)| Primitive::new(self.mesh.clone(), index, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let mesh = self.mesh; - self.iter - .last() - .map(|(index, json)| Primitive::new(mesh, index, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|(index, json)| Primitive::new(self.mesh.clone(), index, json)) - } -} - -fn map_morph_target<'a>( - document: &'a crate::Document, - json: &json::mesh::MorphTarget, -) -> MorphTarget<'a> { - let positions = json - .positions - .as_ref() - .map(|index| document.accessors().nth(index.value()).unwrap()); - let normals = json - .normals - .as_ref() - .map(|index| document.accessors().nth(index.value()).unwrap()); - let tangents = json - .tangents - .as_ref() - .map(|index| document.accessors().nth(index.value()).unwrap()); - MorphTarget { - positions, - normals, - tangents, - } -} - -impl<'a> ExactSizeIterator for MorphTargets<'a> {} -impl<'a> Iterator for MorphTargets<'a> { - type Item = MorphTarget<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|json| map_morph_target(self.document, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|json| map_morph_target(document, json)) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|json| map_morph_target(self.document, json)) - } -} - -/// An `Iterator` that visits the variant mappings of a `Mesh`. -#[cfg(feature = "KHR_materials_variants")] -#[derive(Clone, Debug)] -pub struct Mappings<'a> { - /// Internal mapping iterator. - pub(crate) iter: slice::Iter<'a, json::extensions::mesh::Mapping>, - - /// The internal root glTF object. - pub(crate) document: &'a Document, -} - -#[cfg(feature = "KHR_materials_variants")] -impl<'a> ExactSizeIterator for Mappings<'a> {} -#[cfg(feature = "KHR_materials_variants")] -impl<'a> Iterator for Mappings<'a> { - type Item = crate::khr_materials_variants::Mapping<'a>; - fn next(&mut self) -> Option { - let document = self.document; - self.iter - .next() - .map(|json| crate::khr_materials_variants::Mapping::new(document, json)) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|json| crate::khr_materials_variants::Mapping::new(document, json)) - } - fn nth(&mut self, n: usize) -> Option { - let document = self.document; - self.iter - .nth(n) - .map(|json| crate::khr_materials_variants::Mapping::new(document, json)) - } -} diff --git a/src/mesh/mod.rs b/src/mesh/mod.rs index bbf6b2ff..d3bc6234 100644 --- a/src/mesh/mod.rs +++ b/src/mesh/mod.rs @@ -1,73 +1,61 @@ -//! # Basic usage -//! -//! Listing the attributes of each mesh primitive in a glTF asset. -//! -//! ``` -//! # fn run() -> Result<(), Box> { -//! # let gltf = gltf::Gltf::open("examples/Box.gltf")?; -//! for mesh in gltf.meshes() { -//! println!("Mesh #{}", mesh.index()); -//! for primitive in mesh.primitives() { -//! println!("- Primitive #{}", primitive.index()); -//! for (semantic, _) in primitive.attributes() { -//! println!("-- {:?}", semantic); -//! } -//! } -//! } -//! # Ok(()) -//! # } -//! # fn main() { -//! # let _ = run().expect("runtime error"); -//! # } -//! ``` -//! -//! # Reader utility -//! -//! Printing the vertex positions of each primitive of each mesh in -//! a glTF asset. -//! -//! ``` -//! # fn run() -> Result<(), Box> { -//! let (gltf, buffers, _) = gltf::import("examples/Box.gltf")?; -//! for mesh in gltf.meshes() { -//! println!("Mesh #{}", mesh.index()); -//! 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); -//! } -//! } -//! } -//! } -//! # Ok(()) -//! # } -//! # fn main() { -//! # let _ = run().expect("runtime error"); -//! # } -//! ``` - -/// Iterators. -pub mod iter; - /// Utility functions. #[cfg(feature = "utils")] #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] pub mod util; -use crate::{Accessor, Buffer, Document, Material}; - -#[cfg(feature = "utils")] -use crate::accessor; - -pub use json::mesh::{Mode, Semantic}; -use json::validation::Checked; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -/// Vertex attribute data. -pub type Attribute<'a> = (Semantic, Accessor<'a>); +use crate::validation::{Error, Validate}; +use crate::{accessor, material, Extras, Index, Root, UnrecognizedExtensions}; +use serde::ser; +use serde_json::from_value; +use std::collections::BTreeMap; + +/// Support for the `KHR_materials_variants` extension. +pub mod khr_materials_variants { + use crate::{Extras, Index, Material, UnrecognizedExtensions}; + + /// Identifies all material variants applicable to a particular primitive. + #[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + )] + pub struct Variants { + /// Applicable material variant mappings. + pub mappings: Vec, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } + + /// Identifies a single material variant applicable to a particular primitive. + #[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + )] + pub struct Mapping { + /// Base material index. + pub material: Index, + + /// Applicable material variants. + pub variants: Vec>, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } +} /// Vertex position bounding box. pub type BoundingBox = Bounds<[f32; 3]>; @@ -82,371 +70,345 @@ pub struct Bounds { pub max: T, } -/// A set of primitives to be rendered. -#[derive(Clone, Debug)] -pub struct Mesh<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::mesh::Mesh, -} - -/// A single morph target for a mesh primitive. -#[derive(Clone, Debug)] -pub struct MorphTarget<'a> { - /// XYZ vertex position displacements. - positions: Option>, - - /// XYZ vertex normal displacements. - normals: Option>, - - /// XYZ vertex tangent displacements. - tangents: Option>, -} - -/// Geometry to be rendered with the given material. -#[derive(Clone, Debug)] -pub struct Primitive<'a> { - /// The parent `Mesh` struct. - mesh: Mesh<'a>, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::mesh::Primitive, -} - -/// Mesh primitive reader. -#[derive(Clone, Debug)] -pub struct Reader<'a, 's, F> -where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, -{ - #[allow(dead_code)] - pub(crate) primitive: &'a Primitive<'a>, - #[allow(dead_code)] - pub(crate) get_buffer_data: F, +/// The type of primitives to render. +#[derive( + Clone, + Copy, + Debug, + Default, + serde_repr::Deserialize_repr, + Eq, + PartialEq, + serde_repr::Serialize_repr, +)] +#[repr(u32)] +pub enum Mode { + /// Corresponds to `GL_POINTS`. + Points = 0, + + /// Corresponds to `GL_LINES`. + Lines = 1, + + /// Corresponds to `GL_LINE_LOOP`. + LineLoop = 2, + + /// Corresponds to `GL_LINE_STRIP`. + LineStrip = 3, + + /// Corresponds to `GL_TRIANGLES`. + #[default] + Triangles = 4, + + /// Corresponds to `GL_TRIANGLE_STRIP`. + TriangleStrip = 5, + + /// Corresponds to `GL_TRIANGLE_FAN`. + TriangleFan = 6, } -impl<'a> Mesh<'a> { - /// Constructs a `Mesh`. - pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::mesh::Mesh) -> Self { - Self { - document, - index, - json, - } - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } +impl Validate for Mode {} - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras +impl Mode { + /// Returns the equivalent `GLenum`. + pub fn as_gl_enum(self) -> u32 { + self as u32 } +} +/// A set of primitives to be rendered. +/// +/// A node can contain one or more meshes and its transform places the meshes in +/// the scene. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +pub struct Mesh { /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(docsrs, doc(cfg(feature = "names")))] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } + pub name: Option, /// Defines the geometry to be renderered with a material. - pub fn primitives(&self) -> iter::Primitives<'a> { - iter::Primitives { - mesh: self.clone(), - iter: self.json.primitives.iter().enumerate(), - } - } + pub primitives: Vec, /// Defines the weights to be applied to the morph targets. - pub fn weights(&self) -> Option<&'a [f32]> { - self.json.weights.as_deref() - } -} + pub weights: Vec, -impl<'a> Primitive<'a> { - /// Constructs a `Primitive`. - pub(crate) fn new(mesh: Mesh<'a>, index: usize, json: &'a json::mesh::Primitive) -> Self { - Self { mesh, index, json } - } - - /// Returns the bounds of the `POSITION` vertex attribute. - pub fn bounding_box(&self) -> BoundingBox { - // NOTE: cannot panic if validated "minimally" - let pos_accessor_index = self - .json - .attributes - .get(&Checked::Valid(Semantic::Positions)) - .unwrap(); - let pos_accessor = self - .mesh - .document - .accessors() - .nth(pos_accessor_index.value()) - .unwrap(); - let min: [f32; 3] = json::deserialize::from_value(pos_accessor.min().unwrap()).unwrap(); - let max: [f32; 3] = json::deserialize::from_value(pos_accessor.max().unwrap()).unwrap(); - Bounds { min, max } - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } + pub extras: Option, +} - /// Return the accessor with the given semantic. - pub fn get(&self, semantic: &Semantic) -> Option> { - self.json - .attributes - .get(&json::validation::Checked::Valid(semantic.clone())) - .map(|index| self.mesh.document.accessors().nth(index.value()).unwrap()) - } +/// Geometry to be rendered with the given material. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +#[gltf(validate = "validate_primitive")] +pub struct Primitive { + /// Maps attribute semantic names to the `Accessor`s containing the + /// corresponding attribute data. + pub attributes: BTreeMap>, + + /// The index of the accessor that contains the indices. + pub indices: Option>, + + /// The index of the material to apply to this primitive when rendering + pub material: Option>, - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } + /// The type of primitives to render. + #[gltf(default)] + pub mode: Mode, - /// Returns the accessor containing the primitive indices, if provided. - pub fn indices(&self) -> Option> { - self.json - .indices - .as_ref() - .map(|index| self.mesh.document.accessors().nth(index.value()).unwrap()) - } + /// An array of Morph Targets, each Morph Target is a dictionary mapping + /// attributes (only `POSITION`, `NORMAL`, and `TANGENT` supported) to their + /// deviations in the Morph Target. + pub targets: Vec, - /// Returns an `Iterator` that visits the vertex attributes. - pub fn attributes(&self) -> iter::Attributes<'a> { - iter::Attributes { - document: self.mesh.document, - prim: self.clone(), - iter: self.json.attributes.iter(), - } - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, - /// Returns the material to apply to this primitive when rendering - pub fn material(&self) -> Material<'a> { - self.json - .material - .as_ref() - .map(|index| self.mesh.document.materials().nth(index.value()).unwrap()) - .unwrap_or_else(|| Material::default(self.mesh.document)) - } + /// Optional application specific data. + pub extras: Option, - /// The type of primitives to render. - pub fn mode(&self) -> Mode { - self.json.mode.unwrap() - } + /// Support for the `KHR_materials_variants` extension. + #[gltf(extension = "KHR_materials_variants")] + pub variants: Option, +} - /// Returns an `Iterator` that visits the morph targets of the primitive. - pub fn morph_targets(&self) -> iter::MorphTargets<'a> { - if let Some(slice) = self.json.targets.as_ref() { - iter::MorphTargets { - document: self.mesh.document, - iter: slice.iter(), +fn validate_primitive(primitive: &Primitive, root: &crate::Root, path: P, report: &mut R) +where + P: Fn() -> crate::Path, + R: FnMut(&dyn Fn() -> crate::Path, crate::validation::Error), +{ + let position_path = &|| path().field("attributes").key("POSITION"); + if let Some(pos_accessor_index) = primitive.attributes.get(&Semantic::Positions) { + // spec: POSITION accessor **must** have `min` and `max` properties defined. + let pos_accessor = &root.accessors[pos_accessor_index.value()]; + + let min_path = &|| position_path().field("min"); + if let Some(ref min) = pos_accessor.min { + if from_value::<[f32; 3]>(min.clone()).is_err() { + report(min_path, Error::Invalid); } } else { - iter::MorphTargets { - document: self.mesh.document, - iter: ([]).iter(), + report(min_path, Error::Missing); + } + + let max_path = &|| position_path().field("max"); + if let Some(ref max) = pos_accessor.max { + if from_value::<[f32; 3]>(max.clone()).is_err() { + report(max_path, Error::Invalid); } + } else { + report(max_path, Error::Missing); } + } else { + report(position_path, Error::Missing); } +} - /// Get the material variants. - #[cfg(feature = "KHR_materials_variants")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_materials_variants")))] - pub fn mappings(&self) -> iter::Mappings<'a> { - let iter = self - .json - .extensions - .as_ref() - .and_then(|extensions| extensions.khr_materials_variants.as_ref()) - .map(|variants| variants.mappings.iter()) - .unwrap_or_else(|| ([]).iter()); - - iter::Mappings { - document: self.mesh.document, - iter, - } +#[cfg(feature = "utils")] +impl Primitive { + /// Returns the bounds of the `POSITION` vertex attribute. + #[cfg(feature = "utils")] + #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] + pub fn bounding_box(&self, root: &Root) -> BoundingBox { + let index = self + .attributes + .get(&Semantic::Positions) + .expect("primitive has no POSITION attribute"); + let accessor = root.get(*index).expect("index out of range"); + let min: [f32; 3] = + serde_json::from_value(accessor.min.clone().expect("accessor.min missing")) + .expect("failed to parse accessor.min"); + let max: [f32; 3] = + serde_json::from_value(accessor.max.clone().expect("accessor.max missing")) + .expect("failed to parse accessor.max"); + Bounds { min, max } } /// Constructs the primitive reader. #[cfg(feature = "utils")] #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] - pub fn reader<'s, F>(&'a self, get_buffer_data: F) -> Reader<'a, 's, F> + pub fn reader<'a, 's, F>(&'a self, root: &'a Root, get_buffer_data: F) -> Reader<'a, 's, F> where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, + F: Clone + Fn(Index) -> Option<&'s [u8]>, { Reader { + root, primitive: self, get_buffer_data, } } } +/// Mesh primitive reader. +#[cfg(feature = "utils")] +#[derive(Clone, Debug)] +pub struct Reader<'a, 's, F> +where + F: Clone + Fn(Index) -> Option<&'s [u8]>, +{ + pub(crate) root: &'a Root, + pub(crate) primitive: &'a Primitive, + pub(crate) get_buffer_data: F, +} + #[cfg(feature = "utils")] impl<'a, 's, F> Reader<'a, 's, F> where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, + F: Clone + Fn(Index) -> Option<&'s [u8]>, { /// Visits the vertex positions of a primitive. pub fn read_positions(&self) -> Option> { self.primitive + .attributes .get(&Semantic::Positions) - .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) + .and_then(|accessor| { + accessor::Iter::new(self.root, *accessor, self.get_buffer_data.clone()) + }) } /// Visits the vertex normals of a primitive. pub fn read_normals(&self) -> Option> { self.primitive + .attributes .get(&Semantic::Normals) - .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) + .and_then(|accessor| { + accessor::Iter::new(self.root, *accessor, self.get_buffer_data.clone()) + }) } /// Visits the vertex tangents of a primitive. pub fn read_tangents(&self) -> Option> { self.primitive + .attributes .get(&Semantic::Tangents) - .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) + .and_then(|accessor| { + accessor::Iter::new(self.root, *accessor, self.get_buffer_data.clone()) + }) } /// Visits the vertex colors of a primitive. pub fn read_colors(&self, set: u32) -> Option> { use self::util::ReadColors; - use accessor::DataType::{F32, U16, U8}; - use accessor::Dimensions::{Vec3, Vec4}; - self.primitive - .get(&Semantic::Colors(set)) - .and_then( - |accessor| match (accessor.data_type(), accessor.dimensions()) { - (U8, Vec3) => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadColors::RgbU8), - (U16, Vec3) => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadColors::RgbU16), - (F32, Vec3) => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadColors::RgbF32), - (U8, Vec4) => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadColors::RgbaU8), - (U16, Vec4) => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadColors::RgbaU16), - (F32, Vec4) => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadColors::RgbaF32), - _ => unreachable!(), - }, - ) + use accessor::AttributeType::{Vec3, Vec4}; + use accessor::ComponentType::{F32, U16, U8}; + let index = self.primitive.attributes.get(&Semantic::Colors(set))?; + let accessor = self.root.get(*index)?; + match (accessor.component_type, accessor.attribute_type) { + (U8, Vec3) => accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadColors::RgbU8), + (U16, Vec3) => accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadColors::RgbU16), + (F32, Vec3) => accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadColors::RgbF32), + (U8, Vec4) => accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadColors::RgbaU8), + (U16, Vec4) => accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadColors::RgbaU16), + (F32, Vec4) => accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadColors::RgbaF32), + _ => unreachable!(), + } } /// Visits the vertex draw sequence of a primitive. pub fn read_indices(&self) -> Option> { use self::util::ReadIndices; - use accessor::DataType; - self.primitive - .indices() - .and_then(|accessor| match accessor.data_type() { - DataType::U8 => { - accessor::Iter::new(accessor, self.get_buffer_data.clone()).map(ReadIndices::U8) - } - DataType::U16 => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadIndices::U16), - DataType::U32 => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadIndices::U32), - _ => unreachable!(), - }) + use accessor::ComponentType; + let index = self.primitive.indices?; + let accessor = self.root.get(index)?; + match accessor.component_type { + ComponentType::U8 => { + accessor::Iter::new(self.root, index, self.get_buffer_data.clone()) + .map(ReadIndices::U8) + } + ComponentType::U16 => { + accessor::Iter::new(self.root, index, self.get_buffer_data.clone()) + .map(ReadIndices::U16) + } + ComponentType::U32 => { + accessor::Iter::new(self.root, index, self.get_buffer_data.clone()) + .map(ReadIndices::U32) + } + _ => unreachable!(), + } } /// Visits the joint indices of the primitive. pub fn read_joints(&self, set: u32) -> Option> { use self::util::ReadJoints; - use accessor::DataType; - self.primitive - .get(&Semantic::Joints(set)) - .and_then(|accessor| match accessor.data_type() { - DataType::U8 => { - accessor::Iter::new(accessor, self.get_buffer_data.clone()).map(ReadJoints::U8) - } - DataType::U16 => { - accessor::Iter::new(accessor, self.get_buffer_data.clone()).map(ReadJoints::U16) - } - _ => unreachable!(), - }) + use accessor::ComponentType; + let index = self.primitive.attributes.get(&Semantic::Joints(set))?; + let accessor = self.root.get(*index)?; + match accessor.component_type { + ComponentType::U8 => { + accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadJoints::U8) + } + ComponentType::U16 => { + accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadJoints::U16) + } + _ => unreachable!(), + } } /// Visits the vertex texture co-ordinates of a primitive. pub fn read_tex_coords(&self, set: u32) -> Option> { use self::util::ReadTexCoords; - use accessor::DataType; - self.primitive - .get(&Semantic::TexCoords(set)) - .and_then(|accessor| match accessor.data_type() { - DataType::U8 => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadTexCoords::U8), - DataType::U16 => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadTexCoords::U16), - DataType::F32 => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadTexCoords::F32), - _ => unreachable!(), - }) + use accessor::ComponentType; + let index = self.primitive.attributes.get(&Semantic::TexCoords(set))?; + let accessor = self.root.get(*index)?; + match accessor.component_type { + ComponentType::U8 => { + accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadTexCoords::U8) + } + ComponentType::U16 => { + accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadTexCoords::U16) + } + ComponentType::F32 => { + accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadTexCoords::F32) + } + _ => unreachable!(), + } } /// Visits the joint weights of the primitive. pub fn read_weights(&self, set: u32) -> Option> { - use self::accessor::DataType; + use self::accessor::ComponentType; use self::util::ReadWeights; - self.primitive - .get(&Semantic::Weights(set)) - .and_then(|accessor| match accessor.data_type() { - DataType::U8 => { - accessor::Iter::new(accessor, self.get_buffer_data.clone()).map(ReadWeights::U8) - } - DataType::U16 => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadWeights::U16), - DataType::F32 => accessor::Iter::new(accessor, self.get_buffer_data.clone()) - .map(ReadWeights::F32), - _ => unreachable!(), - }) + let index = self.primitive.attributes.get(&Semantic::Weights(set))?; + let accessor = self.root.get(*index)?; + match accessor.component_type { + ComponentType::U8 => { + accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadWeights::U8) + } + ComponentType::U16 => { + accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadWeights::U16) + } + ComponentType::F32 => { + accessor::Iter::new(self.root, *index, self.get_buffer_data.clone()) + .map(ReadWeights::F32) + } + _ => unreachable!(), + } } /// Visits the morph targets of the primitive. @@ -458,19 +420,133 @@ where } } -impl<'a> MorphTarget<'a> { - /// Returns the XYZ vertex position displacements. - pub fn positions(&self) -> Option> { - self.positions.clone() +/// A dictionary mapping attributes to their deviations in the Morph Target. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +pub struct MorphTarget { + /// XYZ vertex position displacements of type `[f32; 3]`. + #[serde(rename = "POSITION")] + pub positions: Option>, + + /// XYZ vertex normal displacements of type `[f32; 3]`. + #[serde(rename = "NORMAL")] + pub normals: Option>, + + /// XYZ vertex tangent displacements of type `[f32; 3]`. + #[serde(rename = "TANGENT")] + pub tangents: Option>, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, +} + +/// Vertex attribute semantic name. +#[derive(Clone, Debug, serde_with::DeserializeFromStr, Eq, Hash, PartialEq, Ord, PartialOrd)] +pub enum Semantic { + /// Extra attribute name. + Extras(String), + + /// Extension attribute name. + Extensions(String), + + /// XYZ vertex positions. + Positions, + + /// XYZ vertex normals. + Normals, + + /// XYZW vertex tangents where the `w` component is a sign value indicating the + /// handedness of the tangent basis. + Tangents, + + /// RGB or RGBA vertex color. + Colors(u32), + + /// UV texture co-ordinates. + TexCoords(u32), + + /// Joint indices. + Joints(u32), + + /// Joint weights. + Weights(u32), +} + +impl Validate for Semantic {} + +impl std::str::FromStr for Semantic { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + match s { + "NORMAL" => Ok(Self::Normals), + "POSITION" => Ok(Self::Positions), + "TANGENT" => Ok(Self::Tangents), + _ if s.starts_with("COLOR_") => s["COLOR_".len()..].parse().map(Self::Colors), + _ if s.starts_with("TEXCOORD_") => s["TEXCOORD_".len()..].parse().map(Self::TexCoords), + _ if s.starts_with("JOINTS_") => s["JOINTS_".len()..].parse().map(Self::Joints), + _ if s.starts_with("WEIGHTS_") => s["WEIGHTS_".len()..].parse().map(Self::Weights), + _ if s.starts_with('_') => Ok(Self::Extras(s[1..].to_owned())), + _ => Ok(Self::Extensions(s.to_owned())), + } } +} - /// Returns the XYZ vertex normal displacements. - pub fn normals(&self) -> Option> { - self.normals.clone() +impl ser::Serialize for Semantic { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_str(&self.to_string()) } +} - /// Returns the XYZ vertex tangent displacements. - pub fn tangents(&self) -> Option> { - self.tangents.clone() +impl ToString for Semantic { + fn to_string(&self) -> String { + match *self { + Self::Positions => "POSITION".into(), + Self::Normals => "NORMAL".into(), + Self::Tangents => "TANGENT".into(), + Self::Colors(set) => format!("COLOR_{}", set), + Self::TexCoords(set) => format!("TEXCOORD_{}", set), + Self::Joints(set) => format!("JOINTS_{}", set), + Self::Weights(set) => format!("WEIGHTS_{}", set), + Self::Extras(ref name) => format!("_{name}"), + Self::Extensions(ref name) => name.clone(), + } + } +} + +#[cfg(test)] +mod tests { + use super::Semantic; + + #[test] + fn semantic() { + let test_cases = [ + ("POSITION", Semantic::Positions), + ("NORMAL", Semantic::Normals), + ("TANGENT", Semantic::Tangents), + ("COLOR_0", Semantic::Colors(0)), + ("TEXCOORD_1", Semantic::TexCoords(1)), + ("JOINTS_2", Semantic::Joints(2)), + ("WEIGHTS_3", Semantic::Weights(3)), + ("_EXTRA", Semantic::Extras("EXTRA".to_string())), + ("EXTENSION", Semantic::Extensions("EXTENSION".to_string())), + ]; + + for (name, semantic) in test_cases { + assert_eq!(Ok(semantic.clone()), name.parse()); + assert_eq!(name, &semantic.to_string()); + } } } diff --git a/src/mesh/util/colors.rs b/src/mesh/util/colors.rs index 91aebb3b..855aa139 100644 --- a/src/mesh/util/colors.rs +++ b/src/mesh/util/colors.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use crate::Normalize; +use super::normalize::Normalize; use super::ReadColors; diff --git a/src/mesh/util/mod.rs b/src/mesh/util/mod.rs index 206373f5..f28716b7 100644 --- a/src/mesh/util/mod.rs +++ b/src/mesh/util/mod.rs @@ -13,7 +13,10 @@ pub mod tex_coords; /// Casting iterator adapters for node weights. pub mod weights; -use crate::mesh; +/// Conversions for normalized integers. +pub(crate) mod normalize; + +use crate::{mesh, Index}; use crate::accessor::Iter; use crate::Buffer; @@ -104,20 +107,20 @@ pub enum ReadWeights<'a> { #[derive(Clone, Debug)] pub struct ReadMorphTargets<'a, 's, F> where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, + F: Clone + Fn(Index) -> Option<&'s [u8]>, { pub(crate) index: usize, pub(crate) reader: mesh::Reader<'a, 's, F>, } impl<'a, 's, F> ExactSizeIterator for ReadMorphTargets<'a, 's, F> where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]> + F: Clone + Fn(Index) -> Option<&'s [u8]> { } impl<'a, 's, F> Iterator for ReadMorphTargets<'a, 's, F> where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, + F: Clone + Fn(Index) -> Option<&'s [u8]>, { type Item = ( Option>, @@ -128,24 +131,36 @@ where self.index += 1; self.reader .primitive - .morph_targets() - .nth(self.index - 1) + .targets + .get(self.index - 1) .map(|morph_target| { - let positions = morph_target - .positions() - .and_then(|accessor| Iter::new(accessor, self.reader.get_buffer_data.clone())); - let normals = morph_target - .normals() - .and_then(|accessor| Iter::new(accessor, self.reader.get_buffer_data.clone())); - let tangents = morph_target - .tangents() - .and_then(|accessor| Iter::new(accessor, self.reader.get_buffer_data.clone())); + let positions = morph_target.positions.and_then(|accessor| { + Iter::new( + self.reader.root, + accessor, + self.reader.get_buffer_data.clone(), + ) + }); + let normals = morph_target.normals.and_then(|accessor| { + Iter::new( + self.reader.root, + accessor, + self.reader.get_buffer_data.clone(), + ) + }); + let tangents = morph_target.tangents.and_then(|accessor| { + Iter::new( + self.reader.root, + accessor, + self.reader.get_buffer_data.clone(), + ) + }); (positions, normals, tangents) }) } fn size_hint(&self) -> (usize, Option) { - self.reader.primitive.morph_targets().size_hint() + self.reader.primitive.targets.iter().size_hint() } } diff --git a/src/mesh/util/normalize.rs b/src/mesh/util/normalize.rs new file mode 100644 index 00000000..29f18818 --- /dev/null +++ b/src/mesh/util/normalize.rs @@ -0,0 +1,189 @@ +pub(crate) trait Normalize { + fn normalize(self) -> T; +} + +impl Normalize for i8 { + fn normalize(self) -> i8 { + self + } +} + +impl Normalize for i8 { + fn normalize(self) -> u8 { + self.max(0) as u8 * 2 + } +} + +impl Normalize for i8 { + fn normalize(self) -> i16 { + self as i16 * 0x100 + } +} + +impl Normalize for i8 { + fn normalize(self) -> u16 { + self.max(0) as u16 * 0x200 + } +} + +impl Normalize for i8 { + fn normalize(self) -> f32 { + (self as f32 * 127.0_f32.recip()).max(-1.0) + } +} + +impl Normalize for u8 { + fn normalize(self) -> i8 { + (self / 2) as i8 + } +} + +impl Normalize for u8 { + fn normalize(self) -> u8 { + self + } +} + +impl Normalize for u8 { + fn normalize(self) -> i16 { + self as i16 * 0x80 + } +} + +impl Normalize for u8 { + fn normalize(self) -> u16 { + self as u16 * 0x100 + } +} + +impl Normalize for u8 { + fn normalize(self) -> f32 { + self as f32 * 255.0_f32.recip() + } +} + +impl Normalize for i16 { + fn normalize(self) -> i8 { + (self / 0x100) as i8 + } +} + +impl Normalize for i16 { + fn normalize(self) -> u8 { + (self.max(0) / 0x80) as u8 + } +} + +impl Normalize for i16 { + fn normalize(self) -> i16 { + self + } +} + +impl Normalize for i16 { + fn normalize(self) -> u16 { + self.max(0) as u16 * 2 + } +} + +impl Normalize for i16 { + fn normalize(self) -> f32 { + (self as f32 * 32767.0_f32.recip()).max(-1.0) + } +} + +impl Normalize for u16 { + fn normalize(self) -> i8 { + (self / 0x200) as i8 + } +} + +impl Normalize for u16 { + fn normalize(self) -> u8 { + (self / 0x100) as u8 + } +} + +impl Normalize for u16 { + fn normalize(self) -> i16 { + (self / 2) as i16 + } +} + +impl Normalize for u16 { + fn normalize(self) -> u16 { + self + } +} + +impl Normalize for u16 { + fn normalize(self) -> f32 { + self as f32 * 65535.0_f32.recip() + } +} + +impl Normalize for f32 { + fn normalize(self) -> i8 { + (self * 127.0) as i8 + } +} + +impl Normalize for f32 { + fn normalize(self) -> u8 { + (self.max(0.0) * 255.0) as u8 + } +} + +impl Normalize for f32 { + fn normalize(self) -> i16 { + (self * 32767.0) as i16 + } +} + +impl Normalize for f32 { + fn normalize(self) -> u16 { + (self.max(0.0) * 65535.0) as u16 + } +} + +impl Normalize for f32 { + fn normalize(self) -> f32 { + self + } +} + +impl Normalize<[T; 2]> for [U; 2] +where + U: Normalize + Copy, +{ + fn normalize(self) -> [T; 2] { + [self[0].normalize(), self[1].normalize()] + } +} + +impl Normalize<[T; 3]> for [U; 3] +where + U: Normalize + Copy, +{ + fn normalize(self) -> [T; 3] { + [ + self[0].normalize(), + self[1].normalize(), + self[2].normalize(), + ] + } +} + +impl Normalize<[T; 4]> for [U; 4] +where + U: Normalize + Copy, +{ + fn normalize(self) -> [T; 4] { + [ + self[0].normalize(), + self[1].normalize(), + self[2].normalize(), + self[3].normalize(), + ] + } +} diff --git a/src/mesh/util/tex_coords.rs b/src/mesh/util/tex_coords.rs index 92f68a50..a348b678 100644 --- a/src/mesh/util/tex_coords.rs +++ b/src/mesh/util/tex_coords.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use crate::Normalize; +use super::normalize::Normalize; use super::ReadTexCoords; diff --git a/src/mesh/util/weights.rs b/src/mesh/util/weights.rs index 1edf4b21..51a4a096 100644 --- a/src/mesh/util/weights.rs +++ b/src/mesh/util/weights.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use crate::Normalize; +use super::normalize::Normalize; use super::ReadWeights; diff --git a/gltf-json/src/path.rs b/src/path.rs similarity index 93% rename from gltf-json/src/path.rs rename to src/path.rs index 9323d197..b7c010c3 100644 --- a/gltf-json/src/path.rs +++ b/src/path.rs @@ -12,7 +12,7 @@ impl Path { /// Basic usage /// /// ```rust - /// # use gltf_json::Path; + /// # use gltf::Path; /// let path = Path::new(); /// assert_eq!("", path.as_str()); /// ``` @@ -27,7 +27,7 @@ impl Path { /// Basic usage /// /// ```rust - /// # use gltf_json::Path; + /// # use gltf::Path; /// let path = Path::new().field("foo"); /// assert_eq!("foo", path.as_str()); /// assert_eq!("foo.bar", path.field("bar").as_str()); @@ -47,7 +47,7 @@ impl Path { /// Basic usage /// /// ```rust - /// # use gltf_json::Path; + /// # use gltf::Path; /// let path = Path::new().field("foo"); /// assert_eq!("foo[123]", path.index(123).as_str()); /// ``` @@ -62,7 +62,7 @@ impl Path { /// Basic usage /// /// ```rust - /// # use gltf_json::Path; + /// # use gltf::Path; /// let path = Path::new().field("foo"); /// assert_eq!("foo[\"bar\"]", path.key("bar").as_str()); /// ``` @@ -77,7 +77,7 @@ impl Path { /// Basic usage /// /// ```rust - /// # use gltf_json::Path; + /// # use gltf::Path; /// let path = Path::new().field("foo").index(0).value_str("baz"); /// assert_eq!("foo[0] = \"baz\"", path.as_str()); /// ``` diff --git a/gltf-json/src/root.rs b/src/root.rs similarity index 62% rename from gltf-json/src/root.rs rename to src/root.rs index 493271de..62207ff8 100644 --- a/gltf-json/src/root.rs +++ b/src/root.rs @@ -1,24 +1,79 @@ use crate::buffer; -use crate::extensions; use crate::texture; use crate::validation; -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; -use std::{self, fmt, io, marker}; +use std::{fmt, marker}; use crate::path::Path; use crate::{ - Accessor, Animation, Asset, Buffer, Camera, Error, Extras, Image, Material, Mesh, Node, Scene, - Skin, Texture, Value, + Accessor, Animation, Asset, Buffer, Camera, Extras, Image, Material, Mesh, Node, Scene, Skin, + Texture, UnrecognizedExtensions, Wrap, }; use validation::Validate; -// TODO: As a breaking change, simplify by replacing uses of `Get` with `AsRef<[T]>`. +/// Support for the `KHR_lights_punctual` extension. +pub mod khr_lights_punctual { + /// Defines a set of lights that can be placed into a scene. + #[derive( + Clone, + Debug, + Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct Lights { + /// An array of punctual light definitions. + pub lights: Vec, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } +} -/// Helper trait for retrieving top-level objects by a universal identifier. -pub trait Get { - /// Retrieves a single value at the given index. - fn get(&self, id: Index) -> Option<&T>; +/// Support for the `KHR_materials_variants` extension. +pub mod khr_materials_variants { + /// Defines an alternative material that may be applied to a mesh primitive. + #[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + )] + pub struct Variant { + /// The name of the material variant. + pub name: String, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } + + /// Defines a set of alternative materials that may be applied to mesh primitives. + #[derive( + Clone, + Debug, + Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct Variants { + /// The available material variants. + pub variants: Vec, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } } /// Represents an offset into a vector of type `T` owned by the root glTF object. @@ -29,136 +84,85 @@ pub trait Get { /// * [`Root::push()`] to add new objects to [`Root`]. pub struct Index(u32, marker::PhantomData T>); -impl Index { - /// Given a vector of glTF objects, call [`Vec::push()`] to insert it into the vector, - /// then return an [`Index`] for it. - /// - /// This allows you to easily obtain [`Index`] values with the correct index and type when - /// creating a glTF asset. Note that for [`Root`], you can call [`Root::push()`] without - /// needing to retrieve the correct vector first. - /// - /// # Panics - /// - /// Panics if the vector has [`u32::MAX`] or more elements, in which case an `Index` cannot be - /// created. - pub fn push(vec: &mut Vec, value: T) -> Index { - let len = vec.len(); - let Ok(index): Result = len.try_into() else { - panic!( - "glTF vector of {ty} has {len} elements, which exceeds the Index limit", - ty = std::any::type_name::(), - ); - }; - - vec.push(value); - Index::new(index) - } -} - /// The root object of a glTF 2.0 asset. -#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] -#[gltf(validate_hook = "root_validate_hook")] +#[derive( + Clone, Debug, Default, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate, +)] +#[gltf(validate = "validate_root")] pub struct Root { /// An array of accessors. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub accessors: Vec, /// An array of keyframe animations. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub animations: Vec, /// Metadata about the glTF asset. pub asset: Asset, /// An array of buffers. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub buffers: Vec, /// An array of buffer views. - #[serde(default, rename = "bufferViews")] - #[serde(skip_serializing_if = "Vec::is_empty")] pub buffer_views: Vec, /// The default scene. - #[serde(skip_serializing_if = "Option::is_none")] pub scene: Option>, - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - /// Names of glTF extensions used somewhere in this asset. - #[serde(default, rename = "extensionsUsed")] - #[serde(skip_serializing_if = "Vec::is_empty")] pub extensions_used: Vec, /// Names of glTF extensions required to properly load this asset. - #[serde(default, rename = "extensionsRequired")] - #[serde(skip_serializing_if = "Vec::is_empty")] pub extensions_required: Vec, /// An array of cameras. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub cameras: Vec, /// An array of images. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub images: Vec, /// An array of materials. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub materials: Vec, /// An array of meshes. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub meshes: Vec, /// An array of nodes. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub nodes: Vec, /// An array of samplers. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub samplers: Vec, /// An array of scenes. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub scenes: Vec, /// An array of skins. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub skins: Vec, /// An array of textures. - #[serde(default)] - #[serde(skip_serializing_if = "Vec::is_empty")] pub textures: Vec, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + + /// Support for the `KHR_lights_punctual` extension. + #[gltf(extension = "KHR_lights_punctual")] + pub lights: Option, + + /// Support for the `KHR_materials_variants` extension. + #[gltf(extension = "KHR_materials_variants")] + pub variants: Option, } -fn root_validate_hook(root: &Root, _also_root: &Root, path: P, report: &mut R) +fn validate_root(root: &Root, _also_root: &Root, path: P, report: &mut R) where P: Fn() -> Path, R: FnMut(&dyn Fn() -> Path, crate::validation::Error), { for (i, ext) in root.extensions_required.iter().enumerate() { - if !crate::extensions::ENABLED_EXTENSIONS.contains(&ext.as_str()) { + if !crate::SUPPORTED_EXTENSIONS.contains(&ext.as_str()) { report( &|| { path() @@ -172,13 +176,35 @@ where } } +impl std::ops::Index> for Root +where + Root: AsRef<[T]>, +{ + type Output = T; + + fn index(&self, index: Index) -> &Self::Output { + let slice: &[T] = self.as_ref(); + &slice[index.value()] + } +} + +impl std::ops::IndexMut> for Root +where + Root: AsRef<[T]> + AsMut>, +{ + fn index_mut(&mut self, index: Index) -> &mut Self::Output { + let slice: &mut Vec = self.as_mut(); + &mut slice[index.value()] + } +} + impl Root { /// Returns a single item from the root object. pub fn get(&self, index: Index) -> Option<&T> where - Self: Get, + Self: AsRef<[T]>, { - (self as &dyn Get).get(index) + self.as_ref().get(index.value()) } /// Insert the given value into this (as via [`Vec::push()`]), then return the [`Index`] to it. @@ -200,66 +226,6 @@ impl Root { { Index::push(self.as_mut(), value) } - - /// Deserialize from a JSON string slice. - #[allow(clippy::should_implement_trait)] - pub fn from_str(str_: &str) -> Result { - serde_json::from_str(str_) - } - - /// Deserialize from a JSON byte slice. - pub fn from_slice(slice: &[u8]) -> Result { - serde_json::from_slice(slice) - } - - /// Deserialize from a stream of JSON. - pub fn from_reader(reader: R) -> Result - where - R: io::Read, - { - serde_json::from_reader(reader) - } - - /// Serialize as a `String` of JSON. - pub fn to_string(&self) -> Result { - serde_json::to_string(self) - } - - /// Serialize as a pretty-printed `String` of JSON. - pub fn to_string_pretty(&self) -> Result { - serde_json::to_string_pretty(self) - } - - /// Serialize as a generic JSON value. - pub fn to_value(&self) -> Result { - serde_json::to_value(self) - } - - /// Serialize as a JSON byte vector. - pub fn to_vec(&self) -> Result, Error> { - serde_json::to_vec(self) - } - - /// Serialize as a pretty-printed JSON byte vector. - pub fn to_vec_pretty(&self) -> Result, Error> { - serde_json::to_vec_pretty(self) - } - - /// Serialize as a JSON byte writertor. - pub fn to_writer(&self, writer: W) -> Result<(), Error> - where - W: io::Write, - { - serde_json::to_writer(writer, self) - } - - /// Serialize as a pretty-printed JSON byte writertor. - pub fn to_writer_pretty(&self, writer: W) -> Result<(), Error> - where - W: io::Write, - { - serde_json::to_writer_pretty(writer, self) - } } impl Index { @@ -268,6 +234,29 @@ impl Index { Index(value, std::marker::PhantomData) } + /// Given a vector of glTF objects, call [`Vec::push()`] to insert it into the vector, + /// then return an [`Index`] for it. + /// + /// This allows you to easily obtain [`Index`] values with the correct index and type when + /// creating a glTF asset. Note that for [`Root`], you can call [`Root::push()`] without + /// needing to retrieve the correct vector first. + /// + /// # Panics + /// + /// Panics if the vector has [`u32::MAX`] or more elements, in which case an `Index` cannot be + /// created. + pub fn push(vec: &mut Vec, value: T) -> Index { + let len = vec.len(); + let Ok(index): Result = len.try_into() else { + panic!( + "glTF vector of {ty} has {len} elements, which exceeds the Index limit", + ty = std::any::type_name::(), + ); + }; + vec.push(value); + Index::new(index) + } + /// Returns the internal offset value. pub fn value(&self) -> usize { self.0 as usize @@ -353,7 +342,7 @@ impl fmt::Display for Index { impl Validate for Index where - Root: Get, + Root: AsRef<[T]>, { fn validate(&self, root: &Root, path: P, report: &mut R) where @@ -366,39 +355,95 @@ where } } -macro_rules! impl_get { - ($ty:ty, $field:ident) => { - impl<'a> Get<$ty> for Root { - fn get(&self, index: Index<$ty>) -> Option<&$ty> { - self.$field.get(index.value()) - } - } +impl<'a, T> Wrap<'a> for Index +where + T: 'a + Wrap<'a>, + Root: AsRef<[T]>, +{ + type Wrapped = >::Wrapped; + + fn wrap(&'a self, root: &'a Root) -> Self::Wrapped { + let items = root.as_ref(); + items[self.value()].wrap_indexed(root, self.value()) + } +} + +impl<'a, T> Wrap<'a> for std::boxed::Box +where + T: 'a + Wrap<'a>, +{ + type Wrapped = >::Wrapped; + + fn wrap(&'a self, root: &'a Root) -> Self::Wrapped { + use std::ops::Deref; + self.deref().wrap(root) + } +} + +impl<'a, K: 'a, V: 'a> Wrap<'a> for serde_json::Map { + type Wrapped = &'a Self; + + fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { + self + } +} + +impl<'a> Wrap<'a> for serde_json::Value { + type Wrapped = &'a Self; + + fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { + self + } +} + +macro_rules! impl_as_ref { + ($field:ident, $ty:ty) => { impl AsRef<[$ty]> for Root { fn as_ref(&self) -> &[$ty] { &self.$field } } + impl AsMut> for Root { fn as_mut(&mut self) -> &mut Vec<$ty> { &mut self.$field } } }; + + ($extension:ident, $field:ident, $ty:ty) => { + impl AsRef<[$ty]> for Root { + fn as_ref(&self) -> &[$ty] { + self.$extension + .as_ref() + .map(|extension| extension.$field.as_slice()) + .unwrap_or(&[]) + } + } + + impl AsMut> for Root { + fn as_mut(&mut self) -> &mut Vec<$ty> { + &mut self.$extension.get_or_insert_with(Default::default).$field + } + } + }; } -impl_get!(Accessor, accessors); -impl_get!(Animation, animations); -impl_get!(Buffer, buffers); -impl_get!(buffer::View, buffer_views); -impl_get!(Camera, cameras); -impl_get!(Image, images); -impl_get!(Material, materials); -impl_get!(Mesh, meshes); -impl_get!(Node, nodes); -impl_get!(texture::Sampler, samplers); -impl_get!(Scene, scenes); -impl_get!(Skin, skins); -impl_get!(Texture, textures); +impl_as_ref!(accessors, Accessor); +impl_as_ref!(animations, Animation); +impl_as_ref!(buffers, Buffer); +impl_as_ref!(buffer_views, buffer::View); +impl_as_ref!(cameras, Camera); +impl_as_ref!(images, Image); +impl_as_ref!(materials, Material); +impl_as_ref!(meshes, Mesh); +impl_as_ref!(nodes, Node); +impl_as_ref!(samplers, texture::Sampler); +impl_as_ref!(scenes, Scene); +impl_as_ref!(skins, Skin); +impl_as_ref!(textures, Texture); +impl_as_ref!(lights, lights, crate::scene::khr_lights_punctual::Light); +impl_as_ref!(variants, variants, khr_materials_variants::Variant); #[cfg(test)] mod tests { @@ -442,12 +487,11 @@ mod tests { #[test] fn root_push() { let some_object = Buffer { - byte_length: validation::USize64(1), - #[cfg(feature = "names")] + length: validation::USize64(1), name: None, uri: None, - extensions: None, extras: Default::default(), + unrecognized_extensions: Default::default(), }; let mut root = Root::default(); @@ -469,22 +513,7 @@ mod tests { root.validate(&root, Path::new, &mut |path, error| { errors.push((path(), error)); }); - - #[cfg(feature = "KHR_lights_punctual")] - { - assert!(errors.is_empty()); - } - - #[cfg(not(feature = "KHR_lights_punctual"))] - { - assert_eq!(1, errors.len()); - let (path, error) = errors.get(0).unwrap(); - assert_eq!( - path.as_str(), - "extensionsRequired[0] = \"KHR_lights_punctual\"" - ); - assert_eq!(*error, Error::Unsupported); - } + assert!(errors.is_empty()); root.extensions_required = vec!["KHR_mesh_quantization".to_owned()]; errors.clear(); diff --git a/src/scene.rs b/src/scene.rs new file mode 100644 index 00000000..a48e21ad --- /dev/null +++ b/src/scene.rs @@ -0,0 +1,224 @@ +use crate::{camera, mesh, scene, skin, Extras, Index, UnrecognizedExtensions}; + +/// Support for the `KHR_lights_punctual` extension. +pub mod khr_lights_punctual { + use crate::validation::{Error, Validate}; + use crate::{Index, Path, Root}; + + /// Introduces a light source to a scene node. + #[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + )] + pub struct LightInstance { + /// The index of the light referenced by this node. + pub light: Index, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } + + /// Specifies the light type. + #[derive( + Clone, Copy, Debug, serde_derive::Deserialize, Eq, Hash, PartialEq, serde_derive::Serialize, + )] + pub enum Type { + /// Directional lights act as though they are infinitely far away and emit light in + /// the direction of the local -z axis. This light type inherits the orientation of + /// the node that it belongs to; position and scale are ignored except for their + /// effect on the inherited node orientation. Because it is at an infinite distance, + /// the light is not attenuated. Its intensity is defined in lumens per metre squared, + /// or lux (lm/m^2). + #[serde(rename = "directional")] + Directional = 1, + + /// Point lights emit light in all directions from their position in space; rotation + /// and scale are ignored except for their effect on the inherited node position. The + /// brightness of the light attenuates in a physically correct manner as distance + /// increases from the light's position (i.e. brightness goes like the inverse square + /// of the distance). Point light intensity is defined in candela, which is lumens per + /// square radian (lm/sr)." + #[serde(rename = "point")] + Point, + + /// Spot lights emit light in a cone in the direction of the local -z axis. The angle + /// and falloff of the cone is defined using two numbers, the innerConeAngle and outer + /// ConeAngle. As with point lights, the brightness also attenuates in a physically + /// correct manner as distance increases from the light's position (i.e. brightness + /// goes like the inverse square of the distance). Spot light intensity refers to the + /// brightness inside the innerConeAngle (and at the location of the light) and is + /// defined in candela, which is lumens per square radian (lm/sr). Engines that don't + /// support two angles for spotlights should use outerConeAngle as the spotlight angle + /// (leaving innerConeAngle to implicitly be 0). + #[serde(rename = "spot")] + Spot, + } + + impl Validate for Type {} + + impl crate::Stub for Type { + fn stub() -> Self { + Self::Directional + } + } + + /// A directional, point, or spot light placeable within a scene. + #[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Stub)] + pub struct Light { + /// Color of the light source. + #[gltf(default = [1.0, 1.0, 1.0])] + pub color: [f32; 3], + /// Intensity of the light source. `point` and `spot` lights use luminous intensity + /// in candela (lm/sr) while `directional` lights use illuminance in lux (lm/m^2). + #[gltf(default = 1.0)] + pub intensity: f32, + + /// Optional user-defined name for this object. + pub name: Option, + + /// A distance cutoff at which the light's intensity may be considered to have reached + /// zero. + pub range: Option, + + /// Spot light parameters. + pub spot: Option, + + /// Specifies the light type. + #[serde(rename = "type")] + pub type_: Type, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } + + impl Validate for Light { + fn validate(&self, root: &Root, path: P, report: &mut R) + where + P: Fn() -> Path, + R: FnMut(&dyn Fn() -> Path, Error), + { + if self.type_ == Type::Spot && self.spot.is_none() { + report(&|| path().field("spot"), Error::Missing); + } + + self.type_.validate(root, || path().field("type"), report); + } + } + + /// Spot light parameters. + #[derive( + Clone, + Debug, + gltf_derive::Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct Spot { + /// Angle in radians from centre of spotlight where falloff begins. + #[gltf(default)] + pub inner_cone_angle: f32, + + /// Angle in radians from centre of spotlight where falloff ends. + #[gltf(default = std::f32::consts::FRAC_PI_4)] + pub outer_cone_angle: f32, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } +} + +/// A node in the node hierarchy. +/// +/// When the node contains `skin`, all +/// `mesh.primitives` must contain `JOINTS_0` and `WEIGHTS_0` attributes. +/// A node can have either a `matrix` or any combination of +/// `translation`/`rotation`/`scale` (TRS) properties. TRS properties are converted +/// to matrices and postmultiplied in the `T * R * S` order to compose the +/// transformation matrix; first the scale is applied to the vertices, then the +/// rotation, and then the translation. If none are provided, the transform is the +/// identity. When a node is targeted for animation (referenced by an +/// animation.channel.target), only TRS properties may be present; `matrix` will not +/// be present. +#[derive( + Clone, Debug, Default, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate, +)] +pub struct Node { + /// The index of the camera referenced by this node. + pub camera: Option>, + + /// The indices of this node's children. + pub children: Vec>, + + /// 4x4 column-major transformation matrix. + pub matrix: Option<[f32; 16]>, + + /// The index of the mesh in this node. + pub mesh: Option>, + + /// Optional user-defined name for this object. + pub name: Option, + + /// The node's unit quaternion rotation in the order `[x, y, z, w]`, where `w` is + /// the scalar component. + pub rotation: Option<[f32; 4]>, + + /// The node's non-uniform scale. + pub scale: Option<[f32; 3]>, + + /// The node's translation. + pub translation: Option<[f32; 3]>, + + /// The index of the skin referenced by this node. + pub skin: Option>, + + /// The weights of the instantiated Morph Target. Number of elements must match + /// the number of Morph Targets of used mesh. + pub weights: Vec, + + /// Support for the `KHR_lights_punctual` extension. + #[gltf(extension = "KHR_lights_punctual")] + pub light: Option, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, +} + +/// The root `Node`s of a scene. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +pub struct Scene { + /// Optional user-defined name for this object. + pub name: Option, + + /// The indices of each root node. + pub nodes: Vec>, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, +} diff --git a/src/scene/iter.rs b/src/scene/iter.rs deleted file mode 100644 index bd6431e4..00000000 --- a/src/scene/iter.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::slice; - -use crate::{Document, Node}; - -/// An `Iterator` that visits the nodes in a scene. -#[derive(Clone, Debug)] -pub struct Nodes<'a> { - /// The parent `Document` struct. - pub(crate) document: &'a Document, - - /// The internal node index iterator. - pub(crate) iter: slice::Iter<'a, json::Index>, -} - -/// An `Iterator` that visits the children of a node. -#[derive(Clone, Debug)] -pub struct Children<'a> { - /// The parent `Document` struct. - pub(crate) document: &'a Document, - - /// The internal node index iterator. - pub(crate) iter: slice::Iter<'a, json::Index>, -} - -impl<'a> ExactSizeIterator for Nodes<'a> {} -impl<'a> Iterator for Nodes<'a> { - type Item = Node<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|index| self.document.nodes().nth(index.value()).unwrap()) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a> ExactSizeIterator for Children<'a> {} -impl<'a> Iterator for Children<'a> { - type Item = Node<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|index| self.document.nodes().nth(index.value()).unwrap()) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|index| document.nodes().nth(index.value()).unwrap()) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|index| self.document.nodes().nth(index.value()).unwrap()) - } -} diff --git a/src/scene/mod.rs b/src/scene/mod.rs deleted file mode 100644 index 71db9f91..00000000 --- a/src/scene/mod.rs +++ /dev/null @@ -1,629 +0,0 @@ -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -use crate::math::*; -use crate::{Camera, Document, Mesh, Skin}; - -/// Iterators. -pub mod iter; - -/// The transform for a `Node`. -#[derive(Clone, Debug)] -pub enum Transform { - /// 4x4 transformation matrix in column-major order. - Matrix { - /// 4x4 matrix. - matrix: [[f32; 4]; 4], - }, - - /// Decomposed TRS properties. - Decomposed { - /// `[x, y, z]` vector. - translation: [f32; 3], - - /// `[x, y, z, w]` quaternion, where `w` is the scalar. - rotation: [f32; 4], - - /// `[x, y, z]` vector. - scale: [f32; 3], - }, -} - -impl Transform { - /// Returns the matrix representation of this transform. - /// - /// If the transform is `Decomposed`, then the matrix is generated with the - /// equation `matrix = translation * rotation * scale`. - pub fn matrix(self) -> [[f32; 4]; 4] { - match self { - Transform::Matrix { matrix } => matrix, - Transform::Decomposed { - translation: t, - rotation: r, - scale: s, - } => { - let t = Matrix4::from_translation(Vector3::new(t[0], t[1], t[2])); - let r = Matrix4::from_quaternion(Quaternion::new(r[3], r[0], r[1], r[2])); - let s = Matrix4::from_nonuniform_scale(s[0], s[1], s[2]); - (t * r * s).as_array() - } - } - } - - /// Returns a decomposed representation of this transform. - /// - /// If the transform is `Matrix`, then the decomposition is extracted from the - /// matrix. - pub fn decomposed(self) -> ([f32; 3], [f32; 4], [f32; 3]) { - match self { - Transform::Matrix { matrix: m } => { - let translation = [m[3][0], m[3][1], m[3][2]]; - #[rustfmt::skip] - let mut i = Matrix3::new( - m[0][0], m[0][1], m[0][2], - m[1][0], m[1][1], m[1][2], - m[2][0], m[2][1], m[2][2], - ); - let sx = i.x.magnitude(); - let sy = i.y.magnitude(); - let sz = i.determinant().signum() * i.z.magnitude(); - let scale = [sx, sy, sz]; - i.x.multiply(1.0 / sx); - i.y.multiply(1.0 / sy); - i.z.multiply(1.0 / sz); - let r = Quaternion::from_matrix(i); - let rotation = [r.v.x, r.v.y, r.v.z, r.s]; - (translation, rotation, scale) - } - Transform::Decomposed { - translation, - rotation, - scale, - } => (translation, rotation, scale), - } - } -} - -/// A node in the node hierarchy. -/// -/// When a node contains a skin, all its meshes contain `JOINTS_0` and `WEIGHTS_0` -/// attributes. -#[derive(Clone, Debug)] -pub struct Node<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::scene::Node, -} - -/// The root nodes of a scene. -#[derive(Clone, Debug)] -pub struct Scene<'a> { - /// The parent `Document` struct. - #[allow(dead_code)] - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::scene::Scene, -} - -impl<'a> Node<'a> { - /// Constructs a `Node`. - pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::scene::Node) -> Self { - Self { - document, - index, - json, - } - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - - /// Returns the camera referenced by this node. - pub fn camera(&self) -> Option> { - self.json - .camera - .as_ref() - .map(|index| self.document.cameras().nth(index.value()).unwrap()) - } - - /// Returns an `Iterator` that visits the node's children. - pub fn children(&self) -> iter::Children<'a> { - iter::Children { - document: self.document, - iter: self.json.children.as_ref().map_or([].iter(), |x| x.iter()), - } - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } - - /// Returns the light at this node as defined by the `KHR_lights_punctual` extension. - #[cfg(feature = "KHR_lights_punctual")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_lights_punctual")))] - pub fn light(&self) -> Option> { - if let Some(extensions) = self.json.extensions.as_ref() { - if let Some(khr_lights_punctual) = extensions.khr_lights_punctual.as_ref() { - let mut lights = self.document.lights().unwrap(); - Some(lights.nth(khr_lights_punctual.light.value()).unwrap()) - } else { - None - } - } else { - None - } - } - - /// Returns the mesh referenced by this node. - pub fn mesh(&self) -> Option> { - self.json - .mesh - .as_ref() - .map(|index| self.document.meshes().nth(index.value()).unwrap()) - } - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } - - /// Returns the node's transform. - pub fn transform(&self) -> Transform { - if let Some(m) = self.json.matrix { - Transform::Matrix { - matrix: [ - [m[0], m[1], m[2], m[3]], - [m[4], m[5], m[6], m[7]], - [m[8], m[9], m[10], m[11]], - [m[12], m[13], m[14], m[15]], - ], - } - } else { - Transform::Decomposed { - translation: self.json.translation.unwrap_or([0.0, 0.0, 0.0]), - rotation: self.json.rotation.unwrap_or_default().0, - scale: self.json.scale.unwrap_or([1.0, 1.0, 1.0]), - } - } - } - - /// Returns the skin referenced by this node. - pub fn skin(&self) -> Option> { - self.json - .skin - .as_ref() - .map(|index| self.document.skins().nth(index.value()).unwrap()) - } - - /// Returns the weights of the instantiated morph target. - pub fn weights(&self) -> Option<&'a [f32]> { - self.json.weights.as_deref() - } -} - -impl<'a> Scene<'a> { - /// Constructs a `Scene`. - pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::scene::Scene) -> Self { - Self { - document, - index, - json, - } - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } - - /// Returns an `Iterator` that visits each root node of the scene. - pub fn nodes(&self) -> iter::Nodes<'a> { - iter::Nodes { - document: self.document, - iter: self.json.nodes.iter(), - } - } -} - -#[cfg(test)] -mod tests { - use crate::math::*; - use crate::scene::Transform; - use std::f32::consts::PI; - - fn rotate(x: f32, y: f32, z: f32, r: f32) -> [f32; 4] { - let r = Quaternion::from_axis_angle(Vector3::new(x, y, z).normalize(), r); - [r.v.x, r.v.y, r.v.z, r.s] - } - - fn test_decompose(translation: [f32; 3], rotation: [f32; 4], scale: [f32; 3]) { - let matrix = Transform::Decomposed { - translation, - rotation, - scale, - } - .matrix(); - let (translation, rotation, scale) = Transform::Matrix { matrix }.decomposed(); - let check = Transform::Decomposed { - translation, - rotation, - scale, - } - .matrix(); - assert_relative_eq!( - Matrix4::from_array(check), - Matrix4::from_array(matrix), - epsilon = 0.05 - ); - } - - fn test_decompose_rotation(rotation: [f32; 4]) { - let translation = [1.0, -2.0, 3.0]; - let scale = [1.0, 1.0, 1.0]; - test_decompose(translation, rotation, scale); - } - - fn test_decompose_scale(scale: [f32; 3]) { - let translation = [1.0, 2.0, 3.0]; - let rotation = rotate(1.0, 0.0, 0.0, PI / 2.0); - test_decompose(translation, rotation, scale); - } - - fn test_decompose_translation(translation: [f32; 3]) { - let rotation = [0.0, 0.0, 0.0, 1.0]; - let scale = [1.0, 1.0, 1.0]; - test_decompose(translation, rotation, scale); - } - - #[test] - fn decompose_identity() { - let translation = [0.0, 0.0, 0.0]; - let rotation = [0.0, 0.0, 0.0, 1.0]; - let scale = [1.0, 1.0, 1.0]; - test_decompose(translation, rotation, scale); - } - - #[test] - fn decompose_translation_unit_x() { - let translation = [1.0, 0.0, 0.0]; - test_decompose_translation(translation); - } - - #[test] - fn decompose_translation_unit_y() { - let translation = [0.0, 1.0, 0.0]; - test_decompose_translation(translation); - } - - #[test] - fn decompose_translation_unit_z() { - let translation = [0.0, 0.0, 1.0]; - test_decompose_translation(translation); - } - - #[test] - fn decompose_translation_random0() { - let translation = [1.0, -1.0, 1.0]; - test_decompose_translation(translation); - } - - #[test] - fn decompose_translation_random1() { - let translation = [-1.0, -1.0, -1.0]; - test_decompose_translation(translation); - } - - #[test] - fn decompose_translation_random2() { - let translation = [-10.0, 100000.0, -0.0001]; - test_decompose_translation(translation); - } - - #[test] - fn decompose_rotation_xaxis() { - let rotation = rotate(1.0, 0.0, 0.0, PI / 2.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_yaxis() { - let rotation = rotate(0.0, 1.0, 0.0, PI / 2.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_zaxis() { - let rotation = rotate(0.0, 0.0, 1.0, PI / 2.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_negative_xaxis() { - let rotation = rotate(-1.0, 0.0, 0.0, PI / 2.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_negative_yaxis() { - let rotation = rotate(0.0, -1.0, 0.0, PI / 2.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_negative_zaxis() { - let rotation = rotate(0.0, 0.0, -1.0, PI / 2.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_eighth_turn() { - let rotation = rotate(1.0, 0.0, 0.0, PI / 4.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_negative_quarter_turn() { - let rotation = rotate(0.0, 1.0, 0.0, -PI / 2.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_half_turn() { - let rotation = rotate(0.0, 0.0, 1.0, PI); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_zero_turn_xaxis() { - let rotation = rotate(1.0, 0.0, 0.0, 0.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_zero_turn_yaxis() { - let rotation = rotate(0.0, 1.0, 0.0, 0.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_zero_turn_zaxis() { - let rotation = rotate(0.0, 0.0, 1.0, 0.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_full_turn() { - let rotation = rotate(1.0, 0.0, 0.0, 2.0 * PI); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_random0() { - let rotation = rotate(1.0, 1.0, 1.0, PI / 3.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_rotation_random1() { - let rotation = rotate(1.0, -1.0, 1.0, -PI / 6.0); - test_decompose_rotation(rotation); - } - - #[test] - fn decompose_uniform_scale_up() { - let scale = [100.0, 100.0, 100.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_uniform_scale_down() { - let scale = [0.01, 0.01, 0.01]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_xscale_up() { - let scale = [100.0, 1.0, 1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_xscale_down() { - let scale = [0.001, 1.0, 1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_yscale_up() { - let scale = [1.0, 100.0, 1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_yscale_down() { - let scale = [1.0, 0.001, 1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_zscale_up() { - let scale = [1.0, 1.0, 100.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_zscale_down() { - let scale = [1.0, 1.0, 0.001]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_negative_xscale_unit() { - let scale = [-1.0, 1.0, 1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_negative_xscale_up() { - let scale = [-10.0, 1.0, 1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_negative_xscale_down() { - let scale = [-0.1, 1.0, 1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_negative_yscale_unit() { - let scale = [1.0, -1.0, 1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_negative_yscale_up() { - let scale = [1.0, -10.0, 1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_negative_yscale_down() { - let scale = [1.0, -0.1, 1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_negative_zscale_unit() { - let scale = [1.0, 1.0, -1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_negative_zscale_up() { - let scale = [1.0, 1.0, -10.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_negative_zscale_down() { - let scale = [1.0, 1.0, -0.1]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_nonuniform_scale_up_sml() { - let scale = [10.0, 100.0, 1000.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_nonuniform_scale_up_mls() { - let scale = [100.0, 1000.0, 10.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_nonuniform_scale_up_lsm() { - let scale = [1000.0, 10.0, 100.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_nonuniform_scale_down_sml() { - let scale = [0.01, 0.001, 0.0001]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_nonuniform_scale_down_mls() { - let scale = [0.001, 0.0001, 0.01]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_nonuniform_scale_down_lsm() { - let scale = [0.0001, 0.01, 0.01]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_nonuniform_scale_unit_ls() { - let scale = [1.0, 100000.0, 0.000001]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_nonuniform_scale_ms_negative_unit() { - let scale = [10.0, 0.1, -1.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_nonuniform_scale_ms_negative_up() { - let scale = [10.0, 0.1, -10.0]; - test_decompose_scale(scale); - } - - #[test] - fn decompose_nonuniform_scale_ms_negative_down() { - let scale = [10.0, 0.1, -0.1]; - test_decompose_scale(scale); - } -} diff --git a/gltf-json/src/skin.rs b/src/skin.rs similarity index 51% rename from gltf-json/src/skin.rs rename to src/skin.rs index 4bc7c389..56d1dc40 100644 --- a/gltf-json/src/skin.rs +++ b/src/skin.rs @@ -1,43 +1,38 @@ -use crate::{accessor, extensions, scene, Extras, Index}; -use gltf_derive::Validate; -use serde_derive::{Deserialize, Serialize}; +use crate::{accessor, scene, Extras, Index, UnrecognizedExtensions}; /// Joints and matrices defining a skin. -#[derive(Clone, Debug, Deserialize, Serialize, Validate)] +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] pub struct Skin { - /// Extension specific data. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extensions: Option, - - /// Optional application specific data. - #[serde(default)] - #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] - #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] - pub extras: Extras, - /// The index of the accessor containing the 4x4 inverse-bind matrices. /// /// When `None`,each matrix is assumed to be the 4x4 identity matrix /// which implies that the inverse-bind matrices were pre-applied. - #[serde(rename = "inverseBindMatrices")] - #[serde(skip_serializing_if = "Option::is_none")] pub inverse_bind_matrices: Option>, /// Indices of skeleton nodes used as joints in this skin. /// /// The array length must be the same as the `count` property of the /// `inverse_bind_matrices` `Accessor` (when defined). - #[serde(skip_serializing_if = "Vec::is_empty")] pub joints: Vec>, /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] pub name: Option, /// The index of the node used as a skeleton root. /// /// When `None`, joints transforms resolve to scene root. - #[serde(skip_serializing_if = "Option::is_none")] pub skeleton: Option>, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, } diff --git a/src/skin/iter.rs b/src/skin/iter.rs deleted file mode 100644 index 6f374158..00000000 --- a/src/skin/iter.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::slice; - -use crate::{Document, Node}; - -/// An `Iterator` that visits the joints of a `Skin`. -#[derive(Clone, Debug)] -pub struct Joints<'a> { - /// The parent `Document` struct. - pub(crate) document: &'a Document, - - /// The internal node index iterator. - pub(crate) iter: slice::Iter<'a, json::Index>, -} - -impl<'a> ExactSizeIterator for Joints<'a> {} -impl<'a> Iterator for Joints<'a> { - type Item = Node<'a>; - fn next(&mut self) -> Option { - self.iter - .next() - .map(|index| self.document.nodes().nth(index.value()).unwrap()) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn count(self) -> usize { - self.iter.count() - } - fn last(self) -> Option { - let document = self.document; - self.iter - .last() - .map(|index| document.nodes().nth(index.value()).unwrap()) - } - fn nth(&mut self, n: usize) -> Option { - self.iter - .nth(n) - .map(|index| self.document.nodes().nth(index.value()).unwrap()) - } -} diff --git a/src/skin/mod.rs b/src/skin/mod.rs deleted file mode 100644 index 0298cde9..00000000 --- a/src/skin/mod.rs +++ /dev/null @@ -1,118 +0,0 @@ -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; - -use crate::{Accessor, Document, Node}; - -#[cfg(feature = "utils")] -use crate::Buffer; - -/// Iterators. -pub mod iter; - -/// Utility functions. -#[cfg(feature = "utils")] -#[cfg_attr(docsrs, doc(cfg(feature = "utils")))] -pub mod util; - -#[cfg(feature = "utils")] -#[doc(inline)] -pub use self::util::Reader; - -/// Joints and matrices defining a skin. -#[derive(Clone, Debug)] -pub struct Skin<'a> { - /// The parent `Document` struct. - document: &'a Document, - - /// The corresponding JSON index. - index: usize, - - /// The corresponding JSON struct. - json: &'a json::skin::Skin, -} - -impl<'a> Skin<'a> { - /// Constructs a `Skin`. - pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::skin::Skin) -> Self { - Self { - document, - index, - json, - } - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } - - /// Returns the accessor containing the 4x4 inverse-bind matrices. - /// - /// When `None`, each matrix is assumed to be the 4x4 identity matrix which - /// implies that the inverse-bind matrices were pre-applied. - pub fn inverse_bind_matrices(&self) -> Option> { - self.json - .inverse_bind_matrices - .as_ref() - .map(|index| self.document.accessors().nth(index.value()).unwrap()) - } - - /// Constructs a skin reader. - #[cfg(feature = "utils")] - #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] - pub fn reader<'s, F>(&'a self, get_buffer_data: F) -> Reader<'a, 's, F> - where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, - { - Reader { - skin: self.clone(), - get_buffer_data, - } - } - - /// Returns an `Iterator` that visits the skeleton nodes used as joints in - /// this skin. - pub fn joints(&self) -> iter::Joints<'a> { - iter::Joints { - document: self.document, - iter: self.json.joints.iter(), - } - } - - /// Optional user-defined name for this object. - #[cfg(feature = "names")] - #[cfg_attr(docsrs, doc(cfg(feature = "names")))] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } - - /// Returns the node used as the skeleton root. When `None`, joints - /// transforms resolve to scene root. - pub fn skeleton(&self) -> Option> { - self.json - .skeleton - .as_ref() - .map(|index| self.document.nodes().nth(index.value()).unwrap()) - } -} diff --git a/src/skin/util.rs b/src/skin/util.rs deleted file mode 100644 index ea278a63..00000000 --- a/src/skin/util.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::accessor; - -use crate::{Buffer, Skin}; - -/// Inverse Bind Matrices of type `[[f32; 4]; 4]`. -pub type ReadInverseBindMatrices<'a> = accessor::Iter<'a, [[f32; 4]; 4]>; - -/// Skin reader. -#[derive(Clone, Debug)] -pub struct Reader<'a, 's, F> -where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, -{ - pub(crate) skin: Skin<'a>, - pub(crate) get_buffer_data: F, -} - -impl<'a, 's, F> Reader<'a, 's, F> -where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, -{ - /// Returns an `Iterator` that reads the inverse bind matrices of - /// the skin. - pub fn read_inverse_bind_matrices(&self) -> Option> { - self.skin - .inverse_bind_matrices() - .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) - } -} diff --git a/src/texture.rs b/src/texture.rs index bd99b024..8fd2cbe7 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -1,304 +1,243 @@ -use crate::{image, Document}; +use crate::validation::Validate; +use crate::{image, Extras, Index, UnrecognizedExtensions}; + +/// Support for the `KHR_texture_basisu` extension. +pub mod khr_texture_basisu { + /// Specifies a texture that uses a KTX v2 image with basis universal compression. + #[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, + )] + pub struct Basisu { + /// The index of the KTX v2 image with basis universal compression. + pub source: crate::Index, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } +} -pub use json::texture::{MagFilter, MinFilter, WrappingMode}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; +/// Support for the `KHR_texture_transform` extension. +pub mod khr_texture_transform { + /// Many techniques can be used to optimize resource usage for a 3d scene. + /// Chief among them is the ability to minimize the number of textures the GPU must load. + /// To achieve this, many engines encourage packing many objects' low-resolution textures into a single large texture atlas. + /// The region of the resulting atlas that corresponds with each object is then defined by vertical and horizontal offsets, + /// and the width and height of the region. + /// + /// To support this use case, this extension adds `offset`, `rotation`, and `scale` properties to textureInfo structures. + /// These properties would typically be implemented as an affine transform on the UV coordinates. + #[derive( + Clone, + Debug, + gltf_derive::Default, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + )] + pub struct Transform { + /// The offset of the UV coordinate origin as a factor of the texture dimensions. + #[gltf(default)] + pub offset: [f32; 2], + + /// Rotate the UVs by this many radians counter-clockwise around the origin. + /// This is equivalent to a similar rotation of the image clockwise. + #[gltf(default)] + pub rotation: f32, + + /// The scale factor applied to the components of the UV coordinates. + #[gltf(default = [1.0; 2])] + pub scale: [f32; 2], + + /// Overrides the textureInfo texCoord value if supplied, and if this extension is supported. + pub tex_coord: Option, + + /// Unrecognized extension data. + pub unrecognized_extensions: crate::UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + } +} -lazy_static! { - static ref DEFAULT_SAMPLER: json::texture::Sampler = Default::default(); +/// Magnification filter. +#[derive( + Clone, Copy, Debug, serde_repr::Deserialize_repr, Eq, PartialEq, serde_repr::Serialize_repr, +)] +#[repr(u32)] +pub enum MagFilter { + /// Corresponds to `GL_NEAREST`. + Nearest = 9728, + + /// Corresponds to `GL_LINEAR`. + Linear = 9729, } -/// A reference to a `Texture`. -#[derive(Clone, Debug)] -pub struct Info<'a> { - /// The parent `Texture` struct. - texture: Texture<'a>, +impl Validate for MagFilter {} - /// The corresponding JSON struct. - json: &'a json::texture::Info, +impl MagFilter { + /// OpenGL enum + pub fn as_gl_enum(self) -> u32 { + self as u32 + } } -/// Texture sampler properties for filtering and wrapping modes. -#[derive(Clone, Debug)] -pub struct Sampler<'a> { - /// The parent `Document` struct. - #[allow(dead_code)] - document: &'a Document, +/// Minification filter. +#[derive( + Clone, Copy, Debug, serde_repr::Deserialize_repr, Eq, PartialEq, serde_repr::Serialize_repr, +)] +#[repr(u32)] +pub enum MinFilter { + /// Corresponds to `GL_NEAREST`. + Nearest = MagFilter::Nearest as u32, - /// The corresponding JSON index - `None` when the default sampler. - index: Option, + /// Corresponds to `GL_LINEAR`. + Linear = MagFilter::Linear as u32, - /// The corresponding JSON struct. - json: &'a json::texture::Sampler, -} + /// Corresponds to `GL_NEAREST_MIPMAP_NEAREST`. + NearestMipmapNearest = 9984, -/// A texture and its sampler. -#[derive(Clone, Debug)] -pub struct Texture<'a> { - /// The parent `Document` struct. - document: &'a Document, + /// Corresponds to `GL_LINEAR_MIPMAP_NEAREST`. + LinearMipmapNearest = 9985, - /// The corresponding JSON index. - index: usize, + /// Corresponds to `GL_NEAREST_MIPMAP_LINEAR`. + NearestMipmapLinear = 9986, - /// The corresponding JSON struct. - json: &'a json::texture::Texture, + /// Corresponds to `GL_LINEAR_MIPMAP_LINEAR`. + LinearMipmapLinear = 9987, } -impl<'a> Sampler<'a> { - /// Constructs a `Sampler`. - pub(crate) fn new( - document: &'a Document, - index: usize, - json: &'a json::texture::Sampler, - ) -> Self { - Self { - document, - index: Some(index), - json, - } - } +impl Validate for MinFilter {} - /// Constructs the default `Sampler`. - pub(crate) fn default(document: &'a Document) -> Self { - Self { - document, - index: None, - json: &DEFAULT_SAMPLER, - } +impl MinFilter { + /// Returns the corresponding OpenGL enum value. + pub fn as_gl_enum(self) -> u32 { + self as u32 } +} - /// Returns the internal JSON index if this `Sampler` was explicity defined. - /// - /// This function returns `None` if the `Sampler` is the default sampler. - pub fn index(&self) -> Option { - self.index +/// Texture co-ordinate wrapping mode. +#[derive( + Clone, + Copy, + Debug, + Default, + serde_repr::Deserialize_repr, + Eq, + PartialEq, + serde_repr::Serialize_repr, +)] +#[repr(u32)] +pub enum WrappingMode { + /// Corresponds to `GL_CLAMP_TO_EDGE`. + ClampToEdge = 33_071, + + /// Corresponds to `GL_MIRRORED_REPEAT`. + MirroredRepeat = 33_648, + + /// Corresponds to `GL_REPEAT`. + #[default] + Repeat = 10_497, +} + +impl Validate for WrappingMode {} + +impl WrappingMode { + /// Returns the corresponding OpenGL enum value. + pub fn as_gl_enum(self) -> u32 { + self as u32 } +} +/// Texture sampler properties for filtering and wrapping modes. +#[derive( + Clone, Debug, Default, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate, +)] +pub struct Sampler { /// Magnification filter. - pub fn mag_filter(&self) -> Option { - self.json.mag_filter.map(|filter| filter.unwrap()) - } + pub mag_filter: Option, /// Minification filter. - pub fn min_filter(&self) -> Option { - self.json.min_filter.map(|filter| filter.unwrap()) - } + pub min_filter: Option, /// Optional user-defined name for this object. - #[cfg(feature = "names")] - pub fn name(&self) -> Option<&str> { - self.json.name.as_deref() - } + pub name: Option, /// `s` wrapping mode. - pub fn wrap_s(&self) -> WrappingMode { - self.json.wrap_s.unwrap() - } + #[gltf(default)] + pub wrap_s: WrappingMode, /// `t` wrapping mode. - pub fn wrap_t(&self) -> WrappingMode { - self.json.wrap_t.unwrap() - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } + #[gltf(default)] + pub wrap_t: WrappingMode, - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &json::Extras { - &self.json.extras - } + pub extras: Option, } -impl<'a> Texture<'a> { - /// Constructs a `Texture`. - pub(crate) fn new( - document: &'a Document, - index: usize, - json: &'a json::texture::Texture, - ) -> Self { - Self { - document, - index, - json, - } - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - +/// A texture and its sampler. +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +pub struct Texture { /// Optional user-defined name for this object. - #[cfg(feature = "names")] - pub fn name(&self) -> Option<&str> { - self.json.name.as_deref() - } + pub name: Option, - /// Returns the sampler used by this texture. - pub fn sampler(&self) -> Sampler<'a> { - self.json - .sampler - .as_ref() - .map(|index| self.document.samplers().nth(index.value()).unwrap()) - .unwrap_or_else(|| Sampler::default(self.document)) - } + /// The index of the sampler used by this texture. + pub sampler: Option>, - /// Returns the image used by this texture. - #[cfg(feature = "allow_empty_texture")] - pub fn source(&self) -> Option> { - let index = self.json.source.value(); - if index == u32::MAX as usize { - None - } else { - Some(self.document.images().nth(index).unwrap()) - } - } + /// The index of the image used by this texture. + pub source: Option>, - /// Returns the image used by this texture. - #[cfg(not(feature = "allow_empty_texture"))] - pub fn source(&self) -> image::Image<'a> { - self.document - .images() - .nth(self.json.source.value()) - .unwrap() - } + /// The KTX v2 image with basis universal compression used by this texture. + pub basisu: Option, - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &json::Extras { - &self.json.extras - } + pub extras: Option, } -impl<'a> Info<'a> { - /// Constructs a reference to a `Texture`. - pub(crate) fn new(texture: Texture<'a>, json: &'a json::texture::Info) -> Self { - Self { texture, json } - } +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] +/// Reference to a `Texture`. +pub struct Info { + /// The index of the texture. + pub index: Index, /// The set index of the texture's `TEXCOORD` attribute. - pub fn tex_coord(&self) -> u32 { - self.json.tex_coord - } - - /// Returns the referenced `Texture`. - pub fn texture(&self) -> Texture<'a> { - self.texture.clone() - } + #[gltf(default)] + pub tex_coord: u32, - /// Returns texture transform information - #[cfg(feature = "KHR_texture_transform")] - #[cfg_attr(docsrs, doc(cfg(feature = "KHR_texture_transform")))] - pub fn texture_transform(&self) -> Option> { - self.json - .extensions - .as_ref()? - .texture_transform - .as_ref() - .map(TextureTransform::new) - } - - /// Returns extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extensions(&self) -> Option<&Map> { - let ext = self.json.extensions.as_ref()?; - Some(&ext.others) - } - - /// Queries extension data unknown to this crate version. - #[cfg(feature = "extensions")] - #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] - pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { - let ext = self.json.extensions.as_ref()?; - ext.others.get(ext_name) - } + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, /// Optional application specific data. - pub fn extras(&self) -> &json::Extras { - &self.json.extras - } -} - -impl<'a> AsRef> for Info<'a> { - fn as_ref(&self) -> &Texture<'a> { - &self.texture - } -} - -/// Many techniques can be used to optimize resource usage for a 3d scene. -/// Chief among them is the ability to minimize the number of textures the GPU must load. -/// To achieve this, many engines encourage packing many objects' low-resolution textures into a single large texture atlas. -/// The region of the resulting atlas that corresponds with each object is then defined by vertical and horizontal offsets, -/// and the width and height of the region. -/// -/// To support this use case, this extension adds `offset`, `rotation`, and `scale` properties to textureInfo structures. -/// These properties would typically be implemented as an affine transform on the UV coordinates. -#[cfg(feature = "KHR_texture_transform")] -pub struct TextureTransform<'a> { - /// The corresponding JSON struct. - json: &'a json::extensions::texture::TextureTransform, -} - -#[cfg(feature = "KHR_texture_transform")] -impl<'a> TextureTransform<'a> { - /// Constructs `TextureTransform` - pub(crate) fn new(json: &'a json::extensions::texture::TextureTransform) -> Self { - Self { json } - } - - /// The offset of the UV coordinate origin as a factor of the texture dimensions. - pub fn offset(&self) -> [f32; 2] { - self.json.offset.0 - } + pub extras: Option, - /// Rotate the UVs by this many radians counter-clockwise around the origin. - /// This is equivalent to a similar rotation of the image clockwise. - pub fn rotation(&self) -> f32 { - self.json.rotation.0 - } - - /// The scale factor applied to the components of the UV coordinates. - pub fn scale(&self) -> [f32; 2] { - self.json.scale.0 - } - - /// Overrides the textureInfo texCoord value if supplied, and if this extension is supported. - pub fn tex_coord(&self) -> Option { - self.json.tex_coord - } - - /// Optional application specific data. - pub fn extras(&self) -> &json::Extras { - &self.json.extras - } + /// Support for the `KHR_texture_transform` extension. + #[gltf(extension = "KHR_texture_transform")] + pub transform: Option, } diff --git a/gltf-json/src/validation.rs b/src/validation.rs similarity index 62% rename from gltf-json/src/validation.rs rename to src/validation.rs index 24f322aa..fbb79805 100644 --- a/gltf-json/src/validation.rs +++ b/src/validation.rs @@ -1,4 +1,3 @@ -use serde::{ser, Serialize, Serializer}; use std::collections::BTreeMap; use std::hash::Hash; @@ -35,80 +34,6 @@ pub enum Error { Unsupported, } -/// Specifies a type that has been pre-validated during deserialization or otherwise. -#[derive(Debug, Eq, Hash, PartialEq, Ord, PartialOrd)] -pub enum Checked { - /// The item is valid. - Valid(T), - - /// The item is invalid. - Invalid, -} - -impl Checked { - /// Converts from `Checked` to `Checked<&T>`. - pub fn as_ref(&self) -> Checked<&T> { - match *self { - Checked::Valid(ref item) => Checked::Valid(item), - Checked::Invalid => Checked::Invalid, - } - } - - /// Takes ownership of the contained item if it is `Valid`. - /// - /// # Panics - /// - /// Panics if called on an `Invalid` item. - pub fn unwrap(self) -> T { - match self { - Checked::Valid(item) => item, - Checked::Invalid => panic!("attempted to unwrap an invalid item"), - } - } -} - -impl Serialize for Checked { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match *self { - Checked::Valid(ref item) => item.serialize(serializer), - Checked::Invalid => Err(ser::Error::custom("invalid item")), - } - } -} - -impl Clone for Checked { - fn clone(&self) -> Self { - match *self { - Checked::Valid(ref item) => Checked::Valid(item.clone()), - Checked::Invalid => Checked::Invalid, - } - } -} - -impl Copy for Checked {} - -impl Default for Checked { - fn default() -> Self { - Checked::Valid(T::default()) - } -} - -impl Validate for Checked { - fn validate(&self, _root: &Root, path: P, report: &mut R) - where - P: Fn() -> Path, - R: FnMut(&dyn Fn() -> Path, Error), - { - match *self { - Checked::Valid(_) => {} - Checked::Invalid => report(&path, Error::Invalid), - } - } -} - /// Validates the suitability of 64-bit byte offsets/sizes on 32-bit systems. #[derive( Clone, @@ -123,6 +48,13 @@ impl Validate for Checked { )] pub struct USize64(pub u64); +impl USize64 { + /// Widens the value to `usize`. + pub fn value(&self) -> usize { + self.0 as usize + } +} + impl From for USize64 { fn from(value: u64) -> Self { Self(value) @@ -160,19 +92,6 @@ impl Validate for BTreeMap { } } -impl Validate for serde_json::Map { - fn validate(&self, root: &Root, path: P, report: &mut R) - where - P: Fn() -> Path, - R: FnMut(&dyn Fn() -> Path, Error), - { - for (key, value) in self.iter() { - key.validate(root, || path().key(&key.to_string()), report); - value.validate(root, || path().key(&key.to_string()), report); - } - } -} - impl Validate for Option { fn validate(&self, root: &Root, path: P, report: &mut R) where @@ -197,13 +116,14 @@ impl Validate for Vec { } } -impl Validate for std::boxed::Box { - fn validate(&self, _: &Root, _: P, _: &mut R) +impl Validate for std::boxed::Box { + fn validate(&self, root: &Root, path: P, report: &mut R) where P: Fn() -> Path, R: FnMut(&dyn Fn() -> Path, Error), { - // nop + use std::ops::Deref; + self.deref().validate(root, path, report); } } @@ -228,11 +148,15 @@ impl std::fmt::Display for Error { // These types are assumed to be always valid. impl Validate for bool {} impl Validate for u32 {} +impl Validate for usize {} impl Validate for i32 {} impl Validate for f32 {} +impl Validate for [f32; 2] {} impl Validate for [f32; 3] {} impl Validate for [f32; 4] {} impl Validate for [f32; 16] {} impl Validate for () {} impl Validate for String {} +impl Validate for serde_json::Map {} impl Validate for serde_json::Value {} +impl Validate for std::boxed::Box {} diff --git a/src/wrapper.rs b/src/wrapper.rs new file mode 100644 index 00000000..cad2c8be --- /dev/null +++ b/src/wrapper.rs @@ -0,0 +1,116 @@ +use crate::Root; + +/// Wrapper type. +pub trait Wrap<'a> { + /// The wrapper type. + type Wrapped; + + /// Creates a wrapper type that can resolve references to other + /// types in the glTF hierarchy. + fn wrap(&'a self, root: &'a Root) -> Self::Wrapped; + + /// Creates a wrapper type associated with an index. + fn wrap_indexed(&'a self, root: &'a Root, index: usize) -> Self::Wrapped { + let _ = index; + self.wrap(root) + } +} + +impl<'a, T> Wrap<'a> for &'a T +where + T: 'a + Wrap<'a>, +{ + type Wrapped = >::Wrapped; + + fn wrap(&'a self, root: &'a Root) -> Self::Wrapped { + (*self).wrap(root) + } +} + +impl<'a, T: Copy, const N: usize> Wrap<'a> for [T; N] { + type Wrapped = Self; + + fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { + *self + } +} + +impl<'a, T> Wrap<'a> for Option +where + T: 'a + Wrap<'a>, +{ + type Wrapped = Option<>::Wrapped>; + + fn wrap(&'a self, root: &'a Root) -> Self::Wrapped { + self.as_ref().map(|item| item.wrap(root)) + } +} + +impl<'a> Wrap<'a> for String { + type Wrapped = &'a str; + + fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { + self + } +} + +impl<'a> Wrap<'a> for std::boxed::Box { + type Wrapped = &'a serde_json::value::RawValue; + + fn wrap(&'a self, _root: &'a Root) -> Self::Wrapped { + use std::ops::Deref; + self.deref() + } +} + +/// Iterator over maps of wrapper data. +#[derive(Clone, Debug)] +pub struct BTreeMapIter<'a, K: Wrap<'a>, V: Wrap<'a>>( + &'a Root, + std::collections::btree_map::Iter<'a, K, V>, +); + +impl<'a, K: 'a + Wrap<'a>, V: 'a + Wrap<'a>> Iterator for BTreeMapIter<'a, K, V> { + type Item = (>::Wrapped, >::Wrapped); + + fn next(&mut self) -> Option { + self.1.next().map(|(k, v)| (k.wrap(self.0), v.wrap(self.0))) + } +} + +/// Iterator over maps of wrapper data. +#[derive(Clone, Debug)] +pub struct MapIter<'a, K: Wrap<'a>, V: Wrap<'a>>( + &'a Root, + std::collections::hash_map::Iter<'a, K, V>, +); + +impl<'a, K: 'a + Wrap<'a>, V: 'a + Wrap<'a>> Iterator for MapIter<'a, K, V> { + type Item = (>::Wrapped, >::Wrapped); + + fn next(&mut self) -> Option { + self.1.next().map(|(k, v)| (k.wrap(self.0), v.wrap(self.0))) + } +} + +/// Iterator over slices of wrappable data. +#[derive(Clone, Debug)] +pub struct SliceIter<'a, T: Wrap<'a>>(&'a Root, std::iter::Enumerate>); + +impl<'a, T: Wrap<'a>> Iterator for SliceIter<'a, T> { + type Item = >::Wrapped; + + fn next(&mut self) -> Option { + self.1 + .next() + .map(|(index, item)| item.wrap_indexed(self.0, index)) + } +} + +impl<'a, T: Wrap<'a> + 'a> Wrap<'a> for Vec { + type Wrapped = SliceIter<'a, T>; + + fn wrap(&'a self, root: &'a Root) -> Self::Wrapped { + SliceIter(root, self.iter().enumerate()) + } +} diff --git a/tests/import_sample_models.rs b/tests/import_sample_models.rs index ead823eb..778b1453 100644 --- a/tests/import_sample_models.rs +++ b/tests/import_sample_models.rs @@ -5,32 +5,31 @@ const SAMPLE_MODELS_DIRECTORY_PATH: &str = "glTF-Sample-Assets/Models"; fn check_import_result( result: gltf::Result<( - gltf::Document, - Vec, - Vec, + gltf::Root, + Vec, + Vec, )>, ) { - use gltf::json::validation::Error; match result { Err(gltf::Error::Validation(errors)) => { assert!(errors .iter() - .all(|(_path, error)| *error == Error::Unsupported)); + .all(|(_path, error)| *error == gltf::validation::Error::Unsupported)); println!("skipped"); } Err(otherwise) => { panic!("{otherwise:#?}"); } - Ok((document, buffer_data, image_data)) => { + Ok((root, buffer_data, image_data)) => { // Check buffers. - assert_eq!(document.buffers().len(), buffer_data.len()); + assert_eq!(root.buffers.len(), buffer_data.len()); - for (buf, data) in document.buffers().zip(buffer_data.iter()) { - assert!((buf.length() + 3) & !3 <= data.0.len()) + for (buf, data) in root.buffers.iter().zip(buffer_data.iter()) { + assert!((buf.length.value() + 3) & !3 <= data.0.len()) } // Check images. - assert_eq!(document.images().len(), image_data.len()); + assert_eq!(root.images.len(), image_data.len()); println!("ok"); } diff --git a/tests/main.rs b/tests/main.rs new file mode 100644 index 00000000..b0e43889 --- /dev/null +++ b/tests/main.rs @@ -0,0 +1,63 @@ +use std::path::Path; + +type Result = std::result::Result>; +type UnitResult = Result<()>; + +const SAMPLE_MODELS_DIRECTORY_PATH: &str = "glTF-Sample-Assets/Models"; + +fn visit(category: &str, extension: &str, visitor: &dyn Fn(&Path) -> UnitResult) -> UnitResult { + let sample_dir_path = Path::new(SAMPLE_MODELS_DIRECTORY_PATH); + for entry in std::fs::read_dir(sample_dir_path)? { + let entry = entry?; + let metadata = entry.metadata()?; + if metadata.is_dir() { + let entry_path = entry.path(); + if let Some(dir_name) = entry_path.file_name() { + let mut file_path = entry_path.join(category).join(dir_name); + file_path.set_extension(extension); + if file_path.exists() { + print!("{}: ", file_path.display()); + let _ = visitor(&file_path)?; + println!("ok"); + } + } + } + } + Ok(()) +} + +#[test] +fn deserialize_standard() -> UnitResult { + visit("glTF", "gltf", &|path| { + let file = std::fs::read_to_string(path)?; + if let Err(error) = serde_json::from_str::(&file) { + panic!("failed to parse {}: {}", path.display(), error); + } else { + Ok(()) + } + }) +} + +#[test] +fn deserialize_binary() -> UnitResult { + visit("glTF-Binary", "glb", &|path| { + let file = std::fs::read(path)?; + if let Err(error) = gltf::binary::Glb::from_slice(&file) { + panic!("failed to parse {}: {}", path.display(), error); + } else { + Ok(()) + } + }) +} + +#[test] +fn deserialize_embedded() -> UnitResult { + visit("glTF-Embedded", "gltf", &|path| { + let file = std::fs::read_to_string(path)?; + if let Err(error) = serde_json::from_str::(&file) { + panic!("failed to parse {}: {}", path.display(), error); + } else { + Ok(()) + } + }) +} diff --git a/tests/test_wrapper.rs b/tests/test_wrapper.rs index 21c6b14b..4d0a9850 100644 --- a/tests/test_wrapper.rs +++ b/tests/test_wrapper.rs @@ -11,9 +11,9 @@ fn test_accessor_bounds() { let mut buffer = vec![]; reader.read_to_end(&mut buffer).unwrap(); let gltf = gltf::Gltf::from_slice(&buffer).unwrap(); - let mesh = &gltf.meshes().next().unwrap(); - let prim = mesh.primitives().next().unwrap(); - let bounds = prim.bounding_box(); + let mesh = &gltf.meshes[0]; + let primitive = &mesh.primitives[0]; + let bounds = primitive.bounding_box(&gltf.root); assert_eq!( bounds, Bounds { @@ -33,10 +33,11 @@ const SIMPLE_SPARSE_ACCESSOR_GLTF: &str = fn test_sparse_accessor_with_base_buffer_view_yield_exact_size_hints() { let (document, buffers, _) = gltf::import(SIMPLE_SPARSE_ACCESSOR_GLTF).unwrap(); - let mesh = document.meshes().next().unwrap(); - let primitive = mesh.primitives().next().unwrap(); - let reader = primitive - .reader(|buffer: gltf::Buffer| buffers.get(buffer.index()).map(|data| &data.0[..])); + let mesh = &document.meshes[0]; + let primitive = &mesh.primitives[0]; + let reader = primitive.reader(&document, |buffer: gltf::Index| { + buffers.get(buffer.value()).map(|data| &data.0[..]) + }); let mut positions = reader.read_positions().unwrap(); const EXPECTED_POSITION_COUNT: usize = 14; @@ -50,10 +51,11 @@ fn test_sparse_accessor_with_base_buffer_view_yield_exact_size_hints() { fn test_sparse_accessor_with_base_buffer_view_yield_all_values() { let (document, buffers, _) = gltf::import(SIMPLE_SPARSE_ACCESSOR_GLTF).unwrap(); - let mesh = document.meshes().next().unwrap(); - let primitive = mesh.primitives().next().unwrap(); - let reader = primitive - .reader(|buffer: gltf::Buffer| buffers.get(buffer.index()).map(|data| &data.0[..])); + let mesh = &document.meshes[0]; + let primitive = &mesh.primitives[0]; + let reader = primitive.reader(&document, |buffer: gltf::Index| { + buffers.get(buffer.value()).map(|data| &data.0[..]) + }); let positions: Vec<[f32; 3]> = reader.read_positions().unwrap().collect::>(); const EXPECTED_POSITIONS: [[f32; 3]; 14] = [ @@ -91,14 +93,15 @@ const BOX_SPARSE_GLTF: &str = "tests/box_sparse.gltf"; fn test_sparse_accessor_without_base_buffer_view_yield_exact_size_hints() { let (document, buffers, _) = gltf::import(BOX_SPARSE_GLTF).unwrap(); - let animation = document.animations().next().unwrap(); - let sampler = animation.samplers().next().unwrap(); - let output_accessor = sampler.output(); - let mut outputs_iter = - gltf::accessor::Iter::::new(output_accessor, |buffer: gltf::Buffer| { - buffers.get(buffer.index()).map(|data| &data.0[..]) - }) - .unwrap(); + let animation = &document.animations[0]; + let sampler = &animation.samplers[0]; + let output_accessor = &sampler.output; + let mut outputs_iter = gltf::accessor::Iter::::new( + &document, + *output_accessor, + |buffer: gltf::Index| buffers.get(buffer.value()).map(|data| &data.0[..]), + ) + .unwrap(); const EXPECTED_OUTPUT_COUNT: usize = 2; for i in (0..=EXPECTED_OUTPUT_COUNT).rev() { @@ -111,12 +114,14 @@ fn test_sparse_accessor_without_base_buffer_view_yield_exact_size_hints() { fn test_sparse_accessor_without_base_buffer_view_yield_all_values() { let (document, buffers, _) = gltf::import(BOX_SPARSE_GLTF).unwrap(); - let animation = document.animations().next().unwrap(); - let sampler = animation.samplers().next().unwrap(); - let output_accessor = sampler.output(); - let output_iter = gltf::accessor::Iter::::new(output_accessor, |buffer: gltf::Buffer| { - buffers.get(buffer.index()).map(|data| &data.0[..]) - }) + let animation = &document.animations[0]; + let sampler = &animation.samplers[0]; + let output_accessor = sampler.output; + let output_iter = gltf::accessor::Iter::::new( + &document, + output_accessor, + |buffer: gltf::Index| buffers.get(buffer.value()).map(|data| &data.0[..]), + ) .unwrap(); let outputs = output_iter.collect::>();