From b523986636b5bdf657fc5f954ac415ac0ce9f32d Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Mon, 22 Jan 2024 17:03:44 +0000 Subject: [PATCH 1/7] Simplify json::accessor module Fix tests Simplify json::animation module Simplify json::buffer module Simpify json::camera module Simpify json::extensions::scene module Simpify json::material module Simplify json::mesh module Simplify json::mesh module Simplify json::texture module Remove json::validation::Checked enum Replace unnecessary macro Merge gltf-json crate into gltf crate Simplify json::camera module some more Add stub wrapper code Trial generating wrapper code Replace Void type with IgnoredAny Remove unused Primitive type Add wrap_indexed extension Remove unnecessary const Add custom (de)serialization macros Remove accessor extensions Remove animation extensions Remove asset extensions Remove buffer extensions Remove image extensions Remove skin extensions Insert serde attributes automatically for Option Insert serde attributes automatically for Vec Test and fix auto camel case fields Add tests for gltf(extension) attribute Fix doc test builds Temporarily remove extras feature Refactor asset Temporarily remove names feature Refactor buffer Insert serde attributes automatically for bool Refactor accessor Refactor animation Refactor camera Refactor image Add default field attribute and derive macro Refactor material Refactor scene and root Refactor texture Refactor material and mesh Refactor skin Remove re-exports of serde_json functions Refactor remaining modules The giant hoist Move Wrap trait into its own module Refactor lib.rs Implement Index and IndexMut for Root Test camera projection Rename accessor::Type as AttributeType Remove leftovers Add fallback for unrecognized extensions Refactor import --- .gitmodules | 3 + Cargo.toml | 49 +- README.md | 19 - examples/Box.glb | Bin 1664 -> 0 bytes examples/Box.gltf | 142 -- examples/Box0.bin | Bin 648 -> 0 bytes examples/Lantern.gltf | 468 ------- examples/display/main.rs | 20 - examples/export/main.rs | 102 +- examples/roundtrip/main.rs | 21 - examples/tree/main.rs | 42 - glTF-Sample-Assets | 1 + gltf-derive/Cargo.toml | 7 +- gltf-derive/src/lib.rs | 1229 ++++++++++++++++- gltf-derive/tests/main.rs | 330 +++++ gltf-json/LICENSE-APACHE | 202 --- gltf-json/LICENSE-MIT | 26 - gltf-json/src/animation.rs | 263 ---- gltf-json/src/asset.rs | 46 - gltf-json/src/buffer.rs | 165 --- gltf-json/src/extensions/accessor.rs | 30 - gltf-json/src/extensions/asset.rs | 6 - gltf-json/src/extensions/buffer.rs | 20 - gltf-json/src/extensions/camera.rs | 31 - gltf-json/src/extensions/image.rs | 12 - gltf-json/src/extensions/material.rs | 417 ------ gltf-json/src/extensions/mesh.rs | 43 - gltf-json/src/extensions/root.rs | 118 -- gltf-json/src/extensions/skin.rs | 12 - gltf-json/src/extensions/texture.rs | 114 -- gltf-json/src/extras.rs | 33 - gltf-json/src/image.rs | 49 - gltf-json/src/lib.rs | 107 -- gltf-json/src/material.rs | 311 ----- gltf-json/src/scene.rs | 114 -- gltf-json/tests/minimal_accessor_invalid.gltf | 53 - ...n_sparse_accessor_without_buffer_view.gltf | 53 - gltf-json/tests/test_validation.rs | 46 - src/accessor.rs | 282 ++++ src/accessor/sparse.rs | 143 -- src/animation.rs | 149 ++ src/animation/util/mod.rs | 188 --- src/animation/util/morph_target_weights.rs | 231 ---- src/animation/util/rotations.rs | 231 ---- src/asset.rs | 64 + src/buffer.rs | 316 ++--- src/camera.rs | 321 ++--- src/image.rs | 196 +-- src/import.rs | 233 +++- src/iter.rs | 626 --------- src/khr_lights_punctual.rs | 119 -- src/khr_materials_variants.rs | 64 - src/lib.rs | 675 ++------- src/material.rs | 1051 ++++++-------- src/mesh.rs | 315 +++++ src/mesh/iter.rs | 177 --- src/mesh/mod.rs | 476 ------- src/mesh/util/colors.rs | 337 ----- src/mesh/util/indices.rs | 95 -- src/mesh/util/joints.rs | 86 -- src/mesh/util/mod.rs | 242 ---- src/mesh/util/tex_coords.rs | 139 -- src/mesh/util/weights.rs | 139 -- {gltf-json/src => src}/path.rs | 8 +- {gltf-json/src => src}/root.rs | 369 ++--- src/scene.rs | 206 +++ src/scene/iter.rs | 64 - src/scene/mod.rs | 629 --------- {gltf-json/src => src}/skin.rs | 28 +- src/skin/iter.rs | 40 - src/skin/mod.rs | 118 -- src/skin/util.rs | 29 - src/texture.rs | 413 +++--- {gltf-json/src => src}/validation.rs | 105 +- src/wrapper.rs | 116 ++ tests/box_sparse.bin | Bin 352 -> 0 bytes tests/box_sparse.glb | Bin 2100 -> 0 bytes tests/box_sparse.gltf | 210 --- tests/main.rs | 63 + tests/minimal_accessor_min_max.gltf | 54 - 80 files changed, 4204 insertions(+), 9847 deletions(-) create mode 100644 .gitmodules delete mode 100644 examples/Box.glb delete mode 100644 examples/Box.gltf delete mode 100644 examples/Box0.bin delete mode 100644 examples/Lantern.gltf delete mode 100644 examples/display/main.rs delete mode 100644 examples/roundtrip/main.rs delete mode 100644 examples/tree/main.rs create mode 160000 glTF-Sample-Assets create mode 100644 gltf-derive/tests/main.rs delete mode 100644 gltf-json/LICENSE-APACHE delete mode 100644 gltf-json/LICENSE-MIT delete mode 100644 gltf-json/src/animation.rs delete mode 100644 gltf-json/src/asset.rs delete mode 100644 gltf-json/src/buffer.rs delete mode 100644 gltf-json/src/extensions/accessor.rs delete mode 100644 gltf-json/src/extensions/asset.rs delete mode 100644 gltf-json/src/extensions/buffer.rs delete mode 100644 gltf-json/src/extensions/camera.rs delete mode 100644 gltf-json/src/extensions/image.rs delete mode 100644 gltf-json/src/extensions/material.rs delete mode 100644 gltf-json/src/extensions/mesh.rs delete mode 100644 gltf-json/src/extensions/root.rs delete mode 100644 gltf-json/src/extensions/skin.rs delete mode 100644 gltf-json/src/extensions/texture.rs delete mode 100644 gltf-json/src/extras.rs delete mode 100644 gltf-json/src/image.rs delete mode 100644 gltf-json/src/lib.rs delete mode 100644 gltf-json/src/material.rs delete mode 100644 gltf-json/src/scene.rs delete mode 100644 gltf-json/tests/minimal_accessor_invalid.gltf delete mode 100644 gltf-json/tests/non_sparse_accessor_without_buffer_view.gltf delete mode 100644 gltf-json/tests/test_validation.rs create mode 100644 src/accessor.rs delete mode 100644 src/accessor/sparse.rs create mode 100644 src/animation.rs delete mode 100644 src/animation/util/mod.rs delete mode 100644 src/animation/util/morph_target_weights.rs delete mode 100644 src/animation/util/rotations.rs create mode 100644 src/asset.rs delete mode 100644 src/iter.rs delete mode 100644 src/khr_lights_punctual.rs delete mode 100644 src/khr_materials_variants.rs create mode 100644 src/mesh.rs delete mode 100644 src/mesh/iter.rs delete mode 100644 src/mesh/mod.rs delete mode 100644 src/mesh/util/colors.rs delete mode 100644 src/mesh/util/indices.rs delete mode 100644 src/mesh/util/joints.rs delete mode 100644 src/mesh/util/mod.rs delete mode 100644 src/mesh/util/tex_coords.rs delete mode 100644 src/mesh/util/weights.rs rename {gltf-json/src => src}/path.rs (94%) rename {gltf-json/src => src}/root.rs (65%) create mode 100644 src/scene.rs delete mode 100644 src/scene/iter.rs delete mode 100644 src/scene/mod.rs rename {gltf-json/src => src}/skin.rs (51%) delete mode 100644 src/skin/iter.rs delete mode 100644 src/skin/mod.rs delete mode 100644 src/skin/util.rs rename {gltf-json/src => src}/validation.rs (62%) create mode 100644 src/wrapper.rs delete mode 100644 tests/box_sparse.bin delete mode 100644 tests/box_sparse.glb delete mode 100644 tests/box_sparse.gltf create mode 100644 tests/main.rs delete mode 100644 tests/minimal_accessor_min_max.gltf 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..d84996ba 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"] +[features]= +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 95ec886b6b92b134291fd41d34ac9d5349306e0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1664 zcmb7EOK;jh5S}DW+O$p6v`u^8GeNd_1bm4oEftl43Q#VHgE0$OGB&bJ+Q_oRvHz-n zq(7!Jix*Y|3Dweg=e6_A%bt4u#xVe_&H(fXY|f(@CPIkBiUbn22;I3GyAPRY#|SxE#v~@J z-RZV!7Blr6`_bt&`^`?9nFdzn`eWB2AFOMR#W1rd(&eFRdl`st&r#1>1WTZ{gEyie zT(@AfoJ@Fl@A97_$mlWVoykNr7-KrYd=dEEkNb}c3{ujK0x6e1_PSp7z%Cj)?0>TUa1Jlw h71BAph6{KDmq-`z7OvnOyhpl%4{!}1;S^J 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..84662323 100644 --- a/examples/export/main.rs +++ b/examples/export/main.rs @@ -1,11 +1,7 @@ -use gltf_json as json; - -use std::{fs, mem}; - -use json::validation::Checked::Valid; -use json::validation::USize64; +use gltf::validation::USize64; use std::borrow::Cow; use std::io::Write; +use std::{fs, mem}; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] enum Output { @@ -72,96 +68,92 @@ 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(), + let buffer = root.push(gltf::Buffer { + length: USize64::from(buffer_length), name: None, uri: if output == Output::Standard { Some("buffer0.bin".into()) } else { None }, + extras: Default::default(), + unrecognized_extensions: Default::default(), }); - 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(), + length: USize64::from(buffer_length), + offset: USize64(0), + stride: Some(gltf::buffer::Stride(mem::size_of::())), name: None, - target: Some(Valid(json::buffer::Target::ArrayBuffer)), + target: Some(gltf::buffer::Target::ArrayBuffer), + extras: Default::default(), + unrecognized_extensions: Default::default(), }); - 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))), + 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))), name: None, normalized: false, sparse: None, + extras: Default::default(), + unrecognized_extensions: Default::default(), }); - 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), + component_type: gltf::accessor::ComponentType::F32, + attribute_type: gltf::accessor::AttributeType::Vec3, min: None, max: None, name: None, normalized: false, sparse: None, + extras: Default::default(), + unrecognized_extensions: Default::default(), }); - 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, + targets: Vec::new(), + variants: None, + extras: Default::default(), + unrecognized_extensions: Default::default(), }; - let mesh = root.push(json::Mesh { - extensions: Default::default(), - extras: Default::default(), + let mesh = root.push(gltf::Mesh { name: None, primitives: vec![primitive], - weights: None, + weights: Vec::new(), + extras: Default::default(), + unrecognized_extensions: Default::default(), }); - 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(), + root.push(gltf::Scene { name: None, nodes: vec![node], + extras: Default::default(), + unrecognized_extensions: Default::default(), }); match output { @@ -169,14 +161,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..6ddacf27 --- /dev/null +++ b/glTF-Sample-Assets @@ -0,0 +1 @@ +Subproject commit 6ddacf278993d1a0f0138a383a01fd1cf154b836 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..5a0c7546 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,7 +9,9 @@ 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 { @@ -30,10 +34,587 @@ impl syn::parse::Parse for ValidateHook { } } -fn expand(ast: &DeriveInput) -> proc_macro2::TokenStream { - use proc_macro2::TokenStream; - use quote::quote; +/// Provided `struct` attributes. +enum StructAttribute { + /// Identifies a 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, +} + +/// 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), +} + +impl syn::parse::Parse for StructAttribute { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result { + let tag = input.parse::()?; + match tag.to_string().as_str() { + "indexed" => Ok(Self::Indexed), + 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)) + } + unrecognized => { + panic!("gltf({unrecognized}) is not a recognized named `struct` field attribute") + } + } + } +} + +/// Implements the `Default` trait. +#[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 { + 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 `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 + } + } + } + } + } + } + + 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"), + } +} + +/// 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); +/// } +/// } +/// ``` +#[proc_macro_derive(Validate, attributes(gltf))] +pub fn derive_validate(input: TokenStream) -> TokenStream { + expand_validate(&syn::parse_macro_input!(input as DeriveInput)).into() +} + +struct ValidateHook(pub syn::Ident); + +impl syn::parse::Parse for ValidateHook { + 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)) + } else { + panic!("unrecognized gltf attribute"); + } + } +} +fn expand_validate(ast: &DeriveInput) -> proc_macro2::TokenStream2 { let mut validate_hook = quote! {}; for attr in &ast.attrs { if attr.path().is_ident("gltf") { @@ -51,7 +632,7 @@ 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| { @@ -65,7 +646,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 +670,639 @@ 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); + } + } + } + } + + 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/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/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/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/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/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/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/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/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.rs b/src/accessor.rs new file mode 100644 index 00000000..5900cbfb --- /dev/null +++ b/src/accessor.rs @@ -0,0 +1,282 @@ +use crate::validation::{Error, USize64, Validate}; +use crate::{buffer, Extras, Index, Path, Root, UnrecognizedExtensions}; +use serde_json::Value; + +/// 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 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, + } + } +} + +/// 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, +} +impl Validate for AttributeType {} + +/// Contains data structures for sparse storage. +pub mod sparse { + use super::*; + use crate::validation::Validate; + + /// 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, + } + impl Validate for IndexType {} + + 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() + } + } + + /// Indices of those attributes that deviate from their initialization value. + #[derive( + Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, 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, + } + + /// Sparse storage of attributes that deviate from their initialization value. + #[derive( + Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, 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, + } + + /// 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::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)] +pub struct Accessor { + /// Specifies if the attribute is a scalar, vector, or matrix. + #[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, + + /// 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, +} + +impl Validate for Accessor { + fn validate(&self, root: &Root, path: P, report: &mut R) + where + P: Fn() -> Path, + R: FnMut(&dyn Fn() -> Path, Error), + { + if self.sparse.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. + if self.buffer_view.is_none() { + report(&|| path().field("bufferView"), Error::Missing); + } + } + + self.attribute_type + .validate(root, || path().field("type"), report); + self.buffer_view + .validate(root, || path().field("bufferView"), report); + self.byte_offset + .validate(root, || path().field("byteOffset"), report); + self.count.validate(root, || path().field("count"), report); + self.component_type + .validate(root, || path().field("componentType"), report); + self.extras + .validate(root, || path().field("extras"), report); + self.min.validate(root, || path().field("min"), report); + self.max.validate(root, || path().field("max"), report); + self.normalized + .validate(root, || path().field("normalized"), report); + self.sparse + .validate(root, || path().field("sparse"), report); + } +} + +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, + } + } + + /// Returns the corresponding `GLenum`. + pub fn as_gl_enum(self) -> u32 { + self as u32 + } +} + +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/animation.rs b/src/animation.rs new file mode 100644 index 00000000..7e2ae2ce --- /dev/null +++ b/src/animation.rs @@ -0,0 +1,149 @@ +use crate::validation::{Error, Validate}; +use crate::{accessor, scene, Extras, Index, Path, Root, UnrecognizedExtensions}; + +/// 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, + /// XYZ scale vector. + #[serde(rename = "scale")] + Scale, + /// Weights of morph targets. + #[serde(rename = "weights")] + MorphTargetWeights, +} +impl Validate for Property {} + +/// A keyframe animation. +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize)] +pub struct Animation { + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + + /// 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, +} + +/// Targets an animation's sampler at a node's property. +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::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, + + /// 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)] +pub struct Target { + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + + /// 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: Property, +} + +/// Defines a keyframe graph but not its target. +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, 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/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..3d82ab23 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,209 +1,157 @@ -#[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, 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, - - /// 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<'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 +impl Validate for Target {} + +/// Distance between individual items in a buffer view, measured in bytes. +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + serde_derive::Deserialize, + serde_derive::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, gltf_derive::Deserialize, gltf_derive::Serialize, 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::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(default, rename = "byteOffset")] + 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")] + 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()) - } - - /// 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) - } + pub target: 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, +} + +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..3fed059c 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,208 +1,165 @@ -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, +use crate::validation::{Error, Validate}; +use crate::{Extras, Path, Root, 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, + }, } -/// 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, -} - -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, - } - } - - /// 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::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::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 - } - - /// 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 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::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..f67f375f 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,184 +1,36 @@ -#[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, +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, 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 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 - } + /// 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..7f6a2d53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,189 +8,121 @@ //! 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. -#[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; +/// Reference importer implementation. +pub mod import; -/// 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; - -/// 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 self::import::import_slice; +pub use scene::Node; #[doc(inline)] -pub use self::material::Material; +pub use scene::Scene; #[doc(inline)] -pub use self::mesh::{Attribute, Mesh, Primitive, Semantic}; +pub use skin::Skin; #[doc(inline)] -pub use self::scene::{Node, Scene}; +pub use texture::Texture; + +#[doc(inline)] +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; + +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 +152,7 @@ pub enum Error { }, /// JSON deserialization error. - Deserialize(json::Error), + Deserialize(serde_json::Error), /// Standard I/O error. Io(std::io::Error), @@ -256,28 +188,36 @@ pub enum Error { UnsupportedScheme, /// glTF validation error. - Validation(Vec<(json::Path, json::validation::Error)>), + Validation(Vec<(Path, validation::Error)>), +} + +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 wrapper plus binary payload. +/// 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 +233,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 +252,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 +360,14 @@ 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(), - ] - } -} diff --git a/src/material.rs b/src/material.rs index b0f28992..2ff8c2fd 100644 --- a/src/material.rs +++ b/src/material.rs @@ -1,700 +1,497 @@ -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() - } - - /// 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) - } - - /// 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) - } - - /// 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)) - } + 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 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)) - } + /// 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, - /// 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) - } + /// The emissive color of the material. + #[gltf(default)] + pub emissive_factor: [f32; 3], - /// 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_pbrSpecularGlossiness` extension. + #[gltf(extension = "KHR_materials_pbrSpecularGlossiness")] + pub pbr_specular_glossiness: + 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_unlit` extension. + #[gltf(extension = "KHR_materials_unlit")] + pub unlit: 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_transmission` extension. + #[gltf(extension = "KHR_materials_transmission")] + pub transmission: 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) - }) - } + /// Support for the `KHR_materials_volume` extension. + #[gltf(extension = "KHR_materials_volume")] + pub volume: 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 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) - }) - } + /// Support for the `KHR_materials_specular` extension. + #[gltf(extension = "KHR_materials_specular")] + pub specular: Option, - /// 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) - }) - } + /// Support for the `KHR_materials_ior` extension. + #[gltf(extension = "KHR_materials_ior")] + pub ior: Option, - /// 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 - } + /// Support for the `KHR_materials_emissive_strength` extension. + #[gltf(extension = "KHR_materials_emissive_strength")] + pub emissive_strength: Option, - /// 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 - } - - /// 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) - }) - } - - /// 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) - }) - } + pub metallic_roughness_texture: Option, - /// 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) - }) - } + /// 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 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, -} +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +pub struct NormalTexture { + /// The index of the texture. + pub index: Index, -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 - } + /// 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::Validate)] +pub struct OcclusionTexture { + /// The index of the texture. + pub index: Index, - /// Returns the set index of the texture's `TEXCOORD` attribute. - pub fn tex_coord(&self) -> u32 { - self.json.tex_coord - } + /// The scalar multiplier controlling the amount of occlusion applied. + #[gltf(default = 1.0)] + pub strength: f32, - /// 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.rs b/src/mesh.rs new file mode 100644 index 00000000..f1c152df --- /dev/null +++ b/src/mesh.rs @@ -0,0 +1,315 @@ +use crate::validation::{Error, Validate}; +use crate::{accessor, material, Extras, Index, Path, 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::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::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, + } +} + +/// 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 Validate for Mode {} + +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::Validate)] +pub struct Mesh { + /// Optional user-defined name for this object. + 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. + pub weights: Vec, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, +} + +/// Geometry to be rendered with the given material. +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize)] +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>, + + /// The type of primitives to render. + #[gltf(default)] + pub mode: Mode, + + /// 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, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + + /// Support for the `KHR_materials_variants` extension. + #[gltf(extension = "KHR_materials_variants")] + pub variants: Option, +} + +impl Validate for Primitive { + fn validate(&self, root: &Root, path: P, report: &mut R) + where + P: Fn() -> Path, + R: FnMut(&dyn Fn() -> Path, Error), + { + // Generated part + self.attributes + .validate(root, || path().field("attributes"), report); + self.extras + .validate(root, || path().field("extras"), report); + self.indices + .validate(root, || path().field("indices"), report); + self.material + .validate(root, || path().field("material"), report); + self.mode.validate(root, || path().field("mode"), report); + self.targets + .validate(root, || path().field("targets"), report); + + // Custom part + let position_path = &|| path().field("attributes").key("POSITION"); + if let Some(pos_accessor_index) = self.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 { + 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, gltf_derive::Deserialize, gltf_derive::Serialize, 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())), + } + } +} + +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 { + 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/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 deleted file mode 100644 index bbf6b2ff..00000000 --- a/src/mesh/mod.rs +++ /dev/null @@ -1,476 +0,0 @@ -//! # 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>); - -/// Vertex position bounding box. -pub type BoundingBox = Bounds<[f32; 3]>; - -/// The minimum and maximum values for a generic accessor. -#[derive(Clone, Debug, PartialEq)] -pub struct Bounds { - /// Minimum value. - pub min: T, - - /// Maximum value. - 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, -} - -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 - } - - /// 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")] - #[cfg_attr(docsrs, doc(cfg(feature = "names")))] - pub fn name(&self) -> Option<&'a str> { - self.json.name.as_deref() - } - - /// 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(), - } - } - - /// Defines the weights to be applied to the morph targets. - pub fn weights(&self) -> Option<&'a [f32]> { - self.json.weights.as_deref() - } -} - -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) - } - - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } - - /// 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()) - } - - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index - } - - /// 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()) - } - - /// 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(), - } - } - - /// 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)) - } - - /// The type of primitives to render. - pub fn mode(&self) -> Mode { - self.json.mode.unwrap() - } - - /// 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(), - } - } else { - iter::MorphTargets { - document: self.mesh.document, - iter: ([]).iter(), - } - } - } - - /// 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, - } - } - - /// 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> - where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, - { - Reader { - primitive: self, - get_buffer_data, - } - } -} - -#[cfg(feature = "utils")] -impl<'a, 's, F> Reader<'a, 's, F> -where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, -{ - /// Visits the vertex positions of a primitive. - pub fn read_positions(&self) -> Option> { - self.primitive - .get(&Semantic::Positions) - .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) - } - - /// Visits the vertex normals of a primitive. - pub fn read_normals(&self) -> Option> { - self.primitive - .get(&Semantic::Normals) - .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) - } - - /// Visits the vertex tangents of a primitive. - pub fn read_tangents(&self) -> Option> { - self.primitive - .get(&Semantic::Tangents) - .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) - } - - /// 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!(), - }, - ) - } - - /// 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!(), - }) - } - - /// 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!(), - }) - } - - /// 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!(), - }) - } - - /// Visits the joint weights of the primitive. - pub fn read_weights(&self, set: u32) -> Option> { - use self::accessor::DataType; - 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!(), - }) - } - - /// Visits the morph targets of the primitive. - pub fn read_morph_targets(&self) -> util::ReadMorphTargets<'a, 's, F> { - util::ReadMorphTargets { - index: 0, - reader: self.clone(), - } - } -} - -impl<'a> MorphTarget<'a> { - /// Returns the XYZ vertex position displacements. - pub fn positions(&self) -> Option> { - self.positions.clone() - } - - /// Returns the XYZ vertex normal displacements. - pub fn normals(&self) -> Option> { - self.normals.clone() - } - - /// Returns the XYZ vertex tangent displacements. - pub fn tangents(&self) -> Option> { - self.tangents.clone() - } -} diff --git a/src/mesh/util/colors.rs b/src/mesh/util/colors.rs deleted file mode 100644 index 91aebb3b..00000000 --- a/src/mesh/util/colors.rs +++ /dev/null @@ -1,337 +0,0 @@ -use std::marker::PhantomData; - -use crate::Normalize; - -use super::ReadColors; - -/// Casting iterator for `Colors`. -#[derive(Clone, Debug)] -pub struct CastingIter<'a, T>(ReadColors<'a>, PhantomData); - -/// Type which describes how to cast any color into RGB u8. -#[derive(Clone, Debug)] -pub struct RgbU8; - -/// Type which describes how to cast any color into RGB u16. -#[derive(Clone, Debug)] -pub struct RgbU16; - -/// Type which describes how to cast any color into RGB f32. -#[derive(Clone, Debug)] -pub struct RgbF32; - -/// Type which describes how to cast any color into RGBA u8. -#[derive(Clone, Debug)] -pub struct RgbaU8; - -/// Type which describes how to cast any color into RGBA u16. -#[derive(Clone, Debug)] -pub struct RgbaU16; - -/// Type which describes how to cast any color into RGBA f32. -#[derive(Clone, Debug)] -pub struct RgbaF32; - -trait ColorChannel { - fn max_color() -> Self; -} - -impl ColorChannel for u8 { - fn max_color() -> Self { - u8::max_value() - } -} - -impl ColorChannel for u16 { - fn max_color() -> Self { - u16::max_value() - } -} - -impl ColorChannel for f32 { - fn max_color() -> Self { - 1.0 - } -} - -trait ColorArray { - fn into_rgb(self) -> [T; 3]; - fn into_rgba(self) -> [T; 4]; -} - -impl ColorArray for [T; 3] { - fn into_rgb(self) -> [T; 3] { - self - } - fn into_rgba(self) -> [T; 4] { - [self[0], self[1], self[2], T::max_color()] - } -} - -impl ColorArray for [T; 4] { - fn into_rgb(self) -> [T; 3] { - [self[0], self[1], self[2]] - } - fn into_rgba(self) -> [T; 4] { - self - } -} - -/// Trait for types which describe casting behaviour. -pub trait Cast { - /// Output type. - type Output; - - /// Cast from RGB u8. - fn cast_rgb_u8(x: [u8; 3]) -> Self::Output; - - /// Cast from RGB u16. - fn cast_rgb_u16(x: [u16; 3]) -> Self::Output; - - /// Cast from RGB f32. - fn cast_rgb_f32(x: [f32; 3]) -> Self::Output; - - /// Cast from RGBA u8. - fn cast_rgba_u8(x: [u8; 4]) -> Self::Output; - - /// Cast from RGBA u16. - fn cast_rgba_u16(x: [u16; 4]) -> Self::Output; - - /// Cast from RGBA f32. - fn cast_rgba_f32(x: [f32; 4]) -> Self::Output; -} - -impl<'a, A> CastingIter<'a, A> { - pub(crate) fn new(iter: ReadColors<'a>) -> Self { - CastingIter(iter, PhantomData) - } - - /// Unwrap underlying `ReadColors` object. - pub fn unwrap(self) -> ReadColors<'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 { - ReadColors::RgbU8(ref mut i) => i.next().map(A::cast_rgb_u8), - ReadColors::RgbU16(ref mut i) => i.next().map(A::cast_rgb_u16), - ReadColors::RgbF32(ref mut i) => i.next().map(A::cast_rgb_f32), - ReadColors::RgbaU8(ref mut i) => i.next().map(A::cast_rgba_u8), - ReadColors::RgbaU16(ref mut i) => i.next().map(A::cast_rgba_u16), - ReadColors::RgbaF32(ref mut i) => i.next().map(A::cast_rgba_f32), - } - } - - #[inline] - fn nth(&mut self, x: usize) -> Option { - match self.0 { - ReadColors::RgbU8(ref mut i) => i.nth(x).map(A::cast_rgb_u8), - ReadColors::RgbU16(ref mut i) => i.nth(x).map(A::cast_rgb_u16), - ReadColors::RgbF32(ref mut i) => i.nth(x).map(A::cast_rgb_f32), - ReadColors::RgbaU8(ref mut i) => i.nth(x).map(A::cast_rgba_u8), - ReadColors::RgbaU16(ref mut i) => i.nth(x).map(A::cast_rgba_u16), - ReadColors::RgbaF32(ref mut i) => i.nth(x).map(A::cast_rgba_f32), - } - } - - fn last(self) -> Option { - match self.0 { - ReadColors::RgbU8(i) => i.last().map(A::cast_rgb_u8), - ReadColors::RgbU16(i) => i.last().map(A::cast_rgb_u16), - ReadColors::RgbF32(i) => i.last().map(A::cast_rgb_f32), - ReadColors::RgbaU8(i) => i.last().map(A::cast_rgba_u8), - ReadColors::RgbaU16(i) => i.last().map(A::cast_rgba_u16), - ReadColors::RgbaF32(i) => i.last().map(A::cast_rgba_f32), - } - } - - fn count(self) -> usize { - self.size_hint().0 - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - match self.0 { - ReadColors::RgbU8(ref i) => i.size_hint(), - ReadColors::RgbU16(ref i) => i.size_hint(), - ReadColors::RgbF32(ref i) => i.size_hint(), - ReadColors::RgbaU8(ref i) => i.size_hint(), - ReadColors::RgbaU16(ref i) => i.size_hint(), - ReadColors::RgbaF32(ref i) => i.size_hint(), - } - } -} - -impl Cast for RgbU8 { - type Output = [u8; 3]; - - fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { - x.into_rgb().normalize() - } -} - -impl Cast for RgbU16 { - type Output = [u16; 3]; - - fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { - x.into_rgb().normalize() - } -} - -impl Cast for RgbF32 { - type Output = [f32; 3]; - - fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { - x.into_rgb().normalize() - } - - fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { - x.into_rgb().normalize() - } -} - -impl Cast for RgbaU8 { - type Output = [u8; 4]; - - fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { - x.normalize().into_rgba() - } -} - -impl Cast for RgbaU16 { - type Output = [u16; 4]; - - fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { - x.normalize().into_rgba() - } -} - -impl Cast for RgbaF32 { - type Output = [f32; 4]; - - fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { - x.normalize().into_rgba() - } - - fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { - x.normalize().into_rgba() - } -} diff --git a/src/mesh/util/indices.rs b/src/mesh/util/indices.rs deleted file mode 100644 index acf99572..00000000 --- a/src/mesh/util/indices.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::marker::PhantomData; - -use super::ReadIndices; - -/// Casting iterator for `Indices`. -#[derive(Clone, Debug)] -pub struct CastingIter<'a, T>(ReadIndices<'a>, PhantomData); - -/// Type which describes how to cast any index into u32. -#[derive(Clone, Debug)] -pub struct U32; - -/// Trait for types which describe casting behaviour. -pub trait Cast { - /// Output type. - type Output; - - /// Cast from u8. - fn cast_u8(x: u8) -> Self::Output; - - /// Cast from u16. - fn cast_u16(x: u16) -> Self::Output; - - /// Cast from u32. - fn cast_u32(x: u32) -> Self::Output; -} - -impl<'a, A> CastingIter<'a, A> { - pub(crate) fn new(iter: ReadIndices<'a>) -> Self { - CastingIter(iter, PhantomData) - } - - /// Unwrap underlying `Indices` object. - pub fn unwrap(self) -> ReadIndices<'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 { - ReadIndices::U8(ref mut i) => i.next().map(A::cast_u8), - ReadIndices::U16(ref mut i) => i.next().map(A::cast_u16), - ReadIndices::U32(ref mut i) => i.next().map(A::cast_u32), - } - } - - #[inline] - fn nth(&mut self, x: usize) -> Option { - match self.0 { - ReadIndices::U8(ref mut i) => i.nth(x).map(A::cast_u8), - ReadIndices::U16(ref mut i) => i.nth(x).map(A::cast_u16), - ReadIndices::U32(ref mut i) => i.nth(x).map(A::cast_u32), - } - } - - fn last(self) -> Option { - match self.0 { - ReadIndices::U8(i) => i.last().map(A::cast_u8), - ReadIndices::U16(i) => i.last().map(A::cast_u16), - ReadIndices::U32(i) => i.last().map(A::cast_u32), - } - } - - fn count(self) -> usize { - self.size_hint().0 - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - match self.0 { - ReadIndices::U8(ref i) => i.size_hint(), - ReadIndices::U16(ref i) => i.size_hint(), - ReadIndices::U32(ref i) => i.size_hint(), - } - } -} - -impl Cast for U32 { - type Output = u32; - - fn cast_u8(x: u8) -> Self::Output { - x as Self::Output - } - fn cast_u16(x: u16) -> Self::Output { - x as Self::Output - } - fn cast_u32(x: u32) -> Self::Output { - x - } -} diff --git a/src/mesh/util/joints.rs b/src/mesh/util/joints.rs deleted file mode 100644 index b8202272..00000000 --- a/src/mesh/util/joints.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::marker::PhantomData; - -use super::ReadJoints; - -/// Casting iterator for `Joints`. -#[derive(Clone, Debug)] -pub struct CastingIter<'a, T>(ReadJoints<'a>, PhantomData); - -/// Type which describes how to cast any joint into u16. -#[derive(Clone, Debug)] -pub struct U16; - -/// Trait for types which describe casting behaviour. -pub trait Cast { - /// Output type. - type Output; - - /// Cast from u8. - fn cast_u8(x: [u8; 4]) -> Self::Output; - - /// Cast from u16. - fn cast_u16(x: [u16; 4]) -> Self::Output; -} - -impl<'a, A> CastingIter<'a, A> { - pub(crate) fn new(iter: ReadJoints<'a>) -> Self { - CastingIter(iter, PhantomData) - } - - /// Unwrap underlying `Joints` object. - pub fn unwrap(self) -> ReadJoints<'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 { - ReadJoints::U8(ref mut i) => i.next().map(A::cast_u8), - ReadJoints::U16(ref mut i) => i.next().map(A::cast_u16), - } - } - - #[inline] - fn nth(&mut self, x: usize) -> Option { - match self.0 { - ReadJoints::U8(ref mut i) => i.nth(x).map(A::cast_u8), - ReadJoints::U16(ref mut i) => i.nth(x).map(A::cast_u16), - } - } - - fn last(self) -> Option { - match self.0 { - ReadJoints::U8(i) => i.last().map(A::cast_u8), - ReadJoints::U16(i) => i.last().map(A::cast_u16), - } - } - - fn count(self) -> usize { - self.size_hint().0 - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - match self.0 { - ReadJoints::U8(ref i) => i.size_hint(), - ReadJoints::U16(ref i) => i.size_hint(), - } - } -} - -impl Cast for U16 { - type Output = [u16; 4]; - - fn cast_u8(x: [u8; 4]) -> Self::Output { - [x[0] as u16, x[1] as u16, x[2] as u16, x[3] as u16] - } - - fn cast_u16(x: [u16; 4]) -> Self::Output { - x - } -} diff --git a/src/mesh/util/mod.rs b/src/mesh/util/mod.rs deleted file mode 100644 index 206373f5..00000000 --- a/src/mesh/util/mod.rs +++ /dev/null @@ -1,242 +0,0 @@ -/// Casting iterator adapters for colors. -pub mod colors; - -/// Casting iterator adapters for vertex indices. -pub mod indices; - -/// Casting iterator adapters for joint indices. -pub mod joints; - -/// Casting iterator adapters for texture co-ordinates. -pub mod tex_coords; - -/// Casting iterator adapters for node weights. -pub mod weights; - -use crate::mesh; - -use crate::accessor::Iter; -use crate::Buffer; - -/// XYZ vertex positions of type `[f32; 3]`. -pub type ReadPositions<'a> = Iter<'a, [f32; 3]>; - -/// XYZ vertex normals of type `[f32; 3]`. -pub type ReadNormals<'a> = Iter<'a, [f32; 3]>; - -/// XYZW vertex tangents of type `[f32; 4]` where the `w` component is a -/// sign value (-1 or +1) indicating the handedness of the tangent basis. -pub type ReadTangents<'a> = Iter<'a, [f32; 4]>; - -/// XYZ vertex position displacements of type `[f32; 3]`. -pub type ReadPositionDisplacements<'a> = Iter<'a, [f32; 3]>; - -/// XYZ vertex normal displacements of type `[f32; 3]`. -pub type ReadNormalDisplacements<'a> = Iter<'a, [f32; 3]>; - -/// XYZ vertex tangent displacements. -pub type ReadTangentDisplacements<'a> = Iter<'a, [f32; 3]>; - -/// Vertex colors. -#[derive(Clone, Debug)] -pub enum ReadColors<'a> { - /// RGB vertex color of type `[u8; 3]>`. - RgbU8(Iter<'a, [u8; 3]>), - /// RGB vertex color of type `[u16; 3]>`. - RgbU16(Iter<'a, [u16; 3]>), - /// RGB vertex color of type `[f32; 3]`. - RgbF32(Iter<'a, [f32; 3]>), - /// RGBA vertex color of type `[u8; 4]>`. - RgbaU8(Iter<'a, [u8; 4]>), - /// RGBA vertex color of type `[u16; 4]>`. - RgbaU16(Iter<'a, [u16; 4]>), - /// RGBA vertex color of type `[f32; 4]`. - RgbaF32(Iter<'a, [f32; 4]>), -} - -/// Index data. -#[derive(Clone, Debug)] -pub enum ReadIndices<'a> { - /// Index data of type U8 - U8(Iter<'a, u8>), - /// Index data of type U16 - U16(Iter<'a, u16>), - /// Index data of type U32 - U32(Iter<'a, u32>), -} - -/// Vertex joints. -#[derive(Clone, Debug)] -pub enum ReadJoints<'a> { - /// Joints of type `[u8; 4]`. - /// Refer to the documentation on morph targets and skins for more - /// information. - U8(Iter<'a, [u8; 4]>), - /// Joints of type `[u16; 4]`. - /// Refer to the documentation on morph targets and skins for more - /// information. - U16(Iter<'a, [u16; 4]>), -} - -/// UV texture co-ordinates. -#[derive(Clone, Debug)] -pub enum ReadTexCoords<'a> { - /// UV texture co-ordinates of type `[u8; 2]>`. - U8(Iter<'a, [u8; 2]>), - /// UV texture co-ordinates of type `[u16; 2]>`. - U16(Iter<'a, [u16; 2]>), - /// UV texture co-ordinates of type `[f32; 2]`. - F32(Iter<'a, [f32; 2]>), -} - -/// Weights. -#[derive(Clone, Debug)] -pub enum ReadWeights<'a> { - /// Weights of type `[u8; 4]`. - U8(Iter<'a, [u8; 4]>), - /// Weights of type `[u16; 4]`. - U16(Iter<'a, [u16; 4]>), - /// Weights of type `[f32; 4]`. - F32(Iter<'a, [f32; 4]>), -} - -/// Morph targets. -#[derive(Clone, Debug)] -pub struct ReadMorphTargets<'a, 's, F> -where - F: Clone + Fn(Buffer<'a>) -> 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]> -{ -} - -impl<'a, 's, F> Iterator for ReadMorphTargets<'a, 's, F> -where - F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, -{ - type Item = ( - Option>, - Option>, - Option>, - ); - fn next(&mut self) -> Option { - self.index += 1; - self.reader - .primitive - .morph_targets() - .nth(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())); - (positions, normals, tangents) - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.reader.primitive.morph_targets().size_hint() - } -} - -impl<'a> ReadColors<'a> { - /// Reinterpret colors as RGB u8, discarding alpha, if present. Lossy if - /// the underlying iterator yields u16, f32 or any RGBA. - pub fn into_rgb_u8(self) -> self::colors::CastingIter<'a, self::colors::RgbU8> { - self::colors::CastingIter::new(self) - } - - /// Reinterpret colors as RGB u16, discarding alpha, if present. Lossy if - /// the underlying iterator yields f32 or any RGBA. - pub fn into_rgb_u16(self) -> self::colors::CastingIter<'a, self::colors::RgbU16> { - self::colors::CastingIter::new(self) - } - - /// Reinterpret colors as RGB f32, discarding alpha, if present. Lossy if - /// the underlying iterator yields u16 or any RGBA. - pub fn into_rgb_f32(self) -> self::colors::CastingIter<'a, self::colors::RgbF32> { - self::colors::CastingIter::new(self) - } - - /// Reinterpret colors as RGBA u8, with default alpha 255. Lossy if the - /// underlying iterator yields u16 or f32. - pub fn into_rgba_u8(self) -> self::colors::CastingIter<'a, self::colors::RgbaU8> { - self::colors::CastingIter::new(self) - } - - /// Reinterpret colors as RGBA u16, with default alpha 65535. Lossy if the - /// underlying iterator yields f32. - pub fn into_rgba_u16(self) -> self::colors::CastingIter<'a, self::colors::RgbaU16> { - self::colors::CastingIter::new(self) - } - - /// Reinterpret colors as RGBA f32, with default alpha 1.0. Lossy if the - /// underlying iterator yields u16. - pub fn into_rgba_f32(self) -> self::colors::CastingIter<'a, self::colors::RgbaF32> { - self::colors::CastingIter::new(self) - } -} - -impl<'a> ReadIndices<'a> { - /// Reinterpret indices as u32, which can fit any possible index. - pub fn into_u32(self) -> self::indices::CastingIter<'a, self::indices::U32> { - self::indices::CastingIter::new(self) - } -} - -impl<'a> ReadJoints<'a> { - /// Reinterpret joints as u16, which can fit any possible joint. - pub fn into_u16(self) -> self::joints::CastingIter<'a, self::joints::U16> { - self::joints::CastingIter::new(self) - } -} - -impl<'a> ReadTexCoords<'a> { - /// Reinterpret texture coordinates as u8. Lossy if the underlying iterator - /// yields u16 or f32. - pub fn into_u8(self) -> self::tex_coords::CastingIter<'a, self::tex_coords::U8> { - self::tex_coords::CastingIter::new(self) - } - - /// Reinterpret texture coordinates as u16. Lossy if the underlying - /// iterator yields f32. - pub fn into_u16(self) -> self::tex_coords::CastingIter<'a, self::tex_coords::U16> { - self::tex_coords::CastingIter::new(self) - } - - /// Reinterpret texture coordinates as f32. Lossy if the underlying - /// iterator yields u16. - pub fn into_f32(self) -> self::tex_coords::CastingIter<'a, self::tex_coords::F32> { - self::tex_coords::CastingIter::new(self) - } -} - -impl<'a> ReadWeights<'a> { - /// Reinterpret weights as u8. Lossy if the underlying iterator yields u16 - /// or f32. - pub fn into_u8(self) -> self::weights::CastingIter<'a, self::weights::U8> { - self::weights::CastingIter::new(self) - } - - /// Reinterpret weights as u16. Lossy if the underlying iterator yields - /// f32. - pub fn into_u16(self) -> self::weights::CastingIter<'a, self::weights::U16> { - self::weights::CastingIter::new(self) - } - - /// Reinterpret weights as f32. Lossy if the underlying iterator yields - /// u16. - pub fn into_f32(self) -> self::weights::CastingIter<'a, self::weights::F32> { - self::weights::CastingIter::new(self) - } -} diff --git a/src/mesh/util/tex_coords.rs b/src/mesh/util/tex_coords.rs deleted file mode 100644 index 92f68a50..00000000 --- a/src/mesh/util/tex_coords.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::marker::PhantomData; - -use crate::Normalize; - -use super::ReadTexCoords; - -/// Casting iterator for `TexCoords`. -#[derive(Clone, Debug)] -pub struct CastingIter<'a, T>(ReadTexCoords<'a>, PhantomData); - -/// Type which describes how to cast any texture coordinate into pair of u8. -#[derive(Clone, Debug)] -pub struct U8; - -/// Type which describes how to cast any texture coordinate into pair of u16. -#[derive(Clone, Debug)] -pub struct U16; - -/// Type which describes how to cast any texture coordinate into pair of f32. -#[derive(Clone, Debug)] -pub struct F32; - -/// Trait for types which describe casting behaviour. -pub trait Cast { - /// Output type. - type Output; - - /// Cast from u8 pair. - fn cast_u8(x: [u8; 2]) -> Self::Output; - - /// Cast from u16 pair. - fn cast_u16(x: [u16; 2]) -> Self::Output; - - /// Cast from f32 pair. - fn cast_f32(x: [f32; 2]) -> Self::Output; -} - -impl<'a, A> CastingIter<'a, A> { - pub(crate) fn new(iter: ReadTexCoords<'a>) -> Self { - CastingIter(iter, PhantomData) - } - - /// Unwrap underlying `TexCoords` object. - pub fn unwrap(self) -> ReadTexCoords<'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 { - ReadTexCoords::U8(ref mut i) => i.next().map(A::cast_u8), - ReadTexCoords::U16(ref mut i) => i.next().map(A::cast_u16), - ReadTexCoords::F32(ref mut i) => i.next().map(A::cast_f32), - } - } - - #[inline] - fn nth(&mut self, x: usize) -> Option { - match self.0 { - ReadTexCoords::U8(ref mut i) => i.nth(x).map(A::cast_u8), - ReadTexCoords::U16(ref mut i) => i.nth(x).map(A::cast_u16), - ReadTexCoords::F32(ref mut i) => i.nth(x).map(A::cast_f32), - } - } - - fn last(self) -> Option { - match self.0 { - ReadTexCoords::U8(i) => i.last().map(A::cast_u8), - ReadTexCoords::U16(i) => i.last().map(A::cast_u16), - ReadTexCoords::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 { - ReadTexCoords::U8(ref i) => i.size_hint(), - ReadTexCoords::U16(ref i) => i.size_hint(), - ReadTexCoords::F32(ref i) => i.size_hint(), - } - } -} - -impl Cast for U8 { - type Output = [u8; 2]; - - fn cast_u8(x: [u8; 2]) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: [u16; 2]) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: [f32; 2]) -> Self::Output { - x.normalize() - } -} - -impl Cast for U16 { - type Output = [u16; 2]; - - fn cast_u8(x: [u8; 2]) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: [u16; 2]) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: [f32; 2]) -> Self::Output { - x.normalize() - } -} - -impl Cast for F32 { - type Output = [f32; 2]; - - fn cast_u8(x: [u8; 2]) -> Self::Output { - x.normalize() - } - - fn cast_u16(x: [u16; 2]) -> Self::Output { - x.normalize() - } - - fn cast_f32(x: [f32; 2]) -> Self::Output { - x.normalize() - } -} diff --git a/src/mesh/util/weights.rs b/src/mesh/util/weights.rs deleted file mode 100644 index 1edf4b21..00000000 --- a/src/mesh/util/weights.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::marker::PhantomData; - -use crate::Normalize; - -use super::ReadWeights; - -/// Casting iterator for `Weights`. -#[derive(Clone, Debug)] -pub struct CastingIter<'a, T>(ReadWeights<'a>, PhantomData); - -/// 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 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 u8. - fn cast_u8(x: [u8; 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: ReadWeights<'a>) -> Self { - CastingIter(iter, PhantomData) - } - - /// Unwrap underlying `Weights` object. - pub fn unwrap(self) -> ReadWeights<'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 { - ReadWeights::U8(ref mut i) => i.next().map(A::cast_u8), - ReadWeights::U16(ref mut i) => i.next().map(A::cast_u16), - ReadWeights::F32(ref mut i) => i.next().map(A::cast_f32), - } - } - - #[inline] - fn nth(&mut self, x: usize) -> Option { - match self.0 { - ReadWeights::U8(ref mut i) => i.nth(x).map(A::cast_u8), - ReadWeights::U16(ref mut i) => i.nth(x).map(A::cast_u16), - ReadWeights::F32(ref mut i) => i.nth(x).map(A::cast_f32), - } - } - - fn last(self) -> Option { - match self.0 { - ReadWeights::U8(i) => i.last().map(A::cast_u8), - ReadWeights::U16(i) => i.last().map(A::cast_u16), - ReadWeights::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 { - ReadWeights::U8(ref i) => i.size_hint(), - ReadWeights::U16(ref i) => i.size_hint(), - ReadWeights::F32(ref i) => i.size_hint(), - } - } -} - -impl Cast for U8 { - type Output = [u8; 4]; - - fn cast_u8(x: [u8; 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_u8(x: [u8; 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_u8(x: [u8; 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/gltf-json/src/path.rs b/src/path.rs similarity index 94% rename from gltf-json/src/path.rs rename to src/path.rs index 9323d197..0567f2d6 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()); /// ``` diff --git a/gltf-json/src/root.rs b/src/root.rs similarity index 65% rename from gltf-json/src/root.rs rename to src/root.rs index 493271de..9e5918df 100644 --- a/gltf-json/src/root.rs +++ b/src/root.rs @@ -1,24 +1,74 @@ 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::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,127 +79,76 @@ 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)] +#[derive( + Clone, Debug, Default, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate, +)] #[gltf(validate_hook = "root_validate_hook")] 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) @@ -172,13 +171,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 +221,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 +229,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 +337,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 +350,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 +482,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(); diff --git a/src/scene.rs b/src/scene.rs new file mode 100644 index 00000000..5274977e --- /dev/null +++ b/src/scene.rs @@ -0,0 +1,206 @@ +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::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 {} + + /// A directional, point, or spot light placeable within a scene. + #[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize)] + 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::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..ce717959 100644 --- a/gltf-json/src/skin.rs +++ b/src/skin.rs @@ -1,43 +1,31 @@ -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::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..3ebaa9fb 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -1,304 +1,203 @@ -use crate::{image, Document}; - -pub use json::texture::{MagFilter, MinFilter, WrappingMode}; -#[cfg(feature = "extensions")] -use serde_json::{Map, Value}; +use crate::validation::Validate; +use crate::{image, Extras, Index, UnrecognizedExtensions}; + +/// 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::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: Index, - /// 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() - } - - /// 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::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..89e3805b 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); } } @@ -230,9 +150,12 @@ impl Validate for bool {} impl Validate for u32 {} 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/box_sparse.bin b/tests/box_sparse.bin deleted file mode 100644 index bf5c336611f775426ff57d91e5ae57cd67c182b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 352 zcmb8pQ4WJJ3pl`o@QJ0-juSgdRn;RrgOW>lg*CjV%WcBV=%pRA zcD%)<;>sMC_T8KJ2AA^>pXeSs+x5-)j5qGvW%u&%@vmPT^$mMaouj+H(`UEy52DZ! A;Q#;t diff --git a/tests/box_sparse.glb b/tests/box_sparse.glb deleted file mode 100644 index e132c815e68f93d1bfc62a0edc41881c51ce6567..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2100 zcmb_d+iu!G5Z&JU8Pz_!5g3y;ytHYQN>m7{g7Q)!bs>wfWUnoIAw&qNpVhbgTF>nI z1|}7)iZsmZWiET>Vn3XGe##j0UNiRVC1YR4qv4i|`CMjMoMycDeamO7#X_bxfxP3r zmcwWFT8A<+$$QCV~r*M7|3`(y49!x48k*H@j=+1u7x*D3!>&$OG9jp67c-P4^O zzVWPjW!uPEBGL>fZ2_A>5Kj%%Z2;tHvkp4kHbDO)(tvs`!d25!r(vnW3re-cVIsVS zW*+vxV`6cznRkK7p-U7`Mp7cEz=|pO=Pe& z=nt<=6U8fS?zlS}eP%jyKM-FknF<{vSnh{kO!)G1fB8@LC+v8nM~hv%ydX z22%MHf2WINS8xzkRhj13&8#TrM4{RCn)Opa*6}XR@xGefat1W)T%g12HBs6f>og#& z+la7kct7CB4IhN(dPv$y5j7OCagrj6xc(B?!^TO9hZwls9F)^AK+-BV7`!rD(wxAY zuWu)55Cu9|=SM;V-`PsriSl(c?oaxoA>c-})YZxX2Ka0mtm#U_qbjA!q26fvrEpBv zm57i|K8rV;ZfA2;aPxX$1jA#x`|*UaPyHdjW{g2(&x~X_edC^kUVvWZGI4zJ43SKC zXic1S=`QJzEs}}Tk7WAMJ(7u&KAqEjvOzNOJcbVtA3y~!WLg(g-y^voIxl46#hSR0 ZZCpPg6S0q=2N3-VZddzuioL()zX2r7LjV8( diff --git a/tests/box_sparse.gltf b/tests/box_sparse.gltf deleted file mode 100644 index 2782f271..00000000 --- a/tests/box_sparse.gltf +++ /dev/null @@ -1,210 +0,0 @@ -{ - "accessors": [ - { - "bufferView": 0, - "byteOffset": 0, - "count": 36, - "componentType": 5125, - "extras": {}, - "type": "SCALAR", - "min": [ - 0 - ], - "max": [ - 7 - ] - }, - { - "bufferView": 1, - "byteOffset": 0, - "count": 8, - "componentType": 5126, - "extras": {}, - "type": "VEC3", - "min": [ - -0.5, - -0.5, - -0.5 - ], - "max": [ - 0.5, - 0.5, - 0.5 - ] - }, - { - "byteOffset": 0, - "count": 2, - "componentType": 5126, - "extras": {}, - "type": "SCALAR", - "min": [ - 0.0 - ], - "max": [ - 1.0 - ], - "sparse": { - "count": 1, - "indices": { - "bufferView": 2, - "byteOffset": 0, - "componentType": 5125, - "extras": {} - }, - "values": { - "bufferView": 3, - "byteOffset": 0, - "extras": {} - }, - "extras": {} - } - }, - { - "bufferView": 4, - "byteOffset": 0, - "count": 2, - "componentType": 5126, - "extras": {}, - "type": "SCALAR", - "min": [ - 1.0 - ], - "max": [ - 2.0 - ] - }, - { - "bufferView": 5, - "byteOffset": 0, - "count": 8, - "componentType": 5126, - "extras": {}, - "type": "VEC3", - "min": [ - -0.5, - -0.5, - -0.5 - ], - "max": [ - 0.0, - 0.0, - 0.0 - ] - } - ], - "animations": [ - { - "extras": {}, - "channels": [ - { - "sampler": 0, - "target": { - "extras": {}, - "node": 0, - "path": "weights" - }, - "extras": {} - } - ], - "samplers": [ - { - "extras": {}, - "input": 3, - "interpolation": "LINEAR", - "output": 2 - } - ] - } - ], - "asset": { - "extras": {}, - "generator": "gltfgen v0.2.0", - "version": "2.0" - }, - "buffers": [ - { - "byteLength": 352, - "uri": "box_sparse.bin", - "extras": {} - } - ], - "bufferViews": [ - { - "buffer": 0, - "byteLength": 144, - "byteOffset": 0, - "target": 34963, - "extras": {} - }, - { - "buffer": 0, - "byteLength": 96, - "byteOffset": 144, - "byteStride": 12, - "target": 34962, - "extras": {} - }, - { - "buffer": 0, - "byteLength": 4, - "byteOffset": 240, - "extras": {} - }, - { - "buffer": 0, - "byteLength": 4, - "byteOffset": 244, - "extras": {} - }, - { - "buffer": 0, - "byteLength": 8, - "byteOffset": 248, - "extras": {} - }, - { - "buffer": 0, - "byteLength": 96, - "byteOffset": 256, - "byteStride": 12, - "target": 34962, - "extras": {} - } - ], - "extras": {}, - "meshes": [ - { - "extras": {}, - "primitives": [ - { - "attributes": { - "POSITION": 1 - }, - "extras": {}, - "indices": 0, - "targets": [ - { - "POSITION": 4 - } - ] - } - ] - } - ], - "nodes": [ - { - "extras": {}, - "mesh": 0, - "name": "box" - } - ], - "scenes": [ - { - "extras": {}, - "nodes": [ - 0 - ] - } - ] -} 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/minimal_accessor_min_max.gltf b/tests/minimal_accessor_min_max.gltf deleted file mode 100644 index 52f42fba..00000000 --- a/tests/minimal_accessor_min_max.gltf +++ /dev/null @@ -1,54 +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.01, 0.02 ], - "min" : [ -0.03, -0.04, -0.05 ] - } - ], - - "asset" : { - "version" : "2.0" - } -} From fcc59f5bad648d1d50dc75438f4d9c065ebabd4a Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Mon, 3 Jun 2024 20:05:41 +0100 Subject: [PATCH 2/7] Add reader utility back --- Cargo.toml | 2 +- examples/Box.gltf | 142 ++++++++ examples/Box0.bin | Bin 0 -> 648 bytes examples/export/main.rs | 2 +- gltf-derive/src/lib.rs | 61 +--- src/accessor.rs | 282 --------------- src/accessor/mod.rs | 362 +++++++++++++------ src/accessor/util.rs | 196 ++++++----- src/animation/iter.rs | 75 ---- src/animation/mod.rs | 268 -------------- src/buffer.rs | 13 +- src/lib.rs | 11 + src/mesh.rs | 315 ----------------- src/mesh/mod.rs | 521 ++++++++++++++++++++++++++++ src/mesh/util/colors.rs | 337 ++++++++++++++++++ src/mesh/util/indices.rs | 95 +++++ src/mesh/util/joints.rs | 86 +++++ src/mesh/util/mod.rs | 257 ++++++++++++++ src/mesh/util/normalize.rs | 189 ++++++++++ src/mesh/util/tex_coords.rs | 139 ++++++++ src/mesh/util/weights.rs | 139 ++++++++ src/path.rs | 2 +- src/root.rs | 19 +- tests/box_sparse.bin | Bin 0 -> 352 bytes tests/box_sparse.glb | Bin 0 -> 2100 bytes tests/box_sparse.gltf | 210 +++++++++++ tests/import_sample_models.rs | 19 +- tests/minimal_accessor_min_max.gltf | 54 +++ tests/test_wrapper.rs | 55 +-- 29 files changed, 2602 insertions(+), 1249 deletions(-) create mode 100644 examples/Box.gltf create mode 100644 examples/Box0.bin delete mode 100644 src/accessor.rs delete mode 100644 src/animation/iter.rs delete mode 100644 src/animation/mod.rs delete mode 100644 src/mesh.rs create mode 100644 src/mesh/mod.rs create mode 100644 src/mesh/util/colors.rs create mode 100644 src/mesh/util/indices.rs create mode 100644 src/mesh/util/joints.rs create mode 100644 src/mesh/util/mod.rs create mode 100644 src/mesh/util/normalize.rs create mode 100644 src/mesh/util/tex_coords.rs create mode 100644 src/mesh/util/weights.rs create mode 100644 tests/box_sparse.bin create mode 100644 tests/box_sparse.glb create mode 100644 tests/box_sparse.gltf create mode 100644 tests/minimal_accessor_min_max.gltf diff --git a/Cargo.toml b/Cargo.toml index d84996ba..5f767329 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ features = ["jpeg", "png"] optional = true version = "0.25" -[features]= +[features] default = ["import", "utils"] allow_empty_texture = [] extensions = [] diff --git a/examples/Box.gltf b/examples/Box.gltf new file mode 100644 index 00000000..7f603f07 --- /dev/null +++ b/examples/Box.gltf @@ -0,0 +1,142 @@ +{ + "asset": { + "generator": "COLLADA2GLTF", + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 2 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "Mesh" + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 36, + "max": [ + 23 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 288, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.800000011920929, + 0.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "name": "Red" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 576, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 576, + "byteStride": 12, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 648, + "uri": "Box0.bin" + } + ] +} diff --git a/examples/Box0.bin b/examples/Box0.bin new file mode 100644 index 0000000000000000000000000000000000000000..d7798abb5161eca17ac8a6883a1f53c6786b484d GIT binary patch literal 648 zcmb7;3kt$O5JO+ps`X#BdYoRZH}h&v#Ne^J())), + stride: Some(gltf::buffer::Stride(mem::size_of::() as u8)), name: None, target: Some(gltf::buffer::Target::ArrayBuffer), extras: Default::default(), diff --git a/gltf-derive/src/lib.rs b/gltf-derive/src/lib.rs index 5a0c7546..8991c2f7 100644 --- a/gltf-derive/src/lib.rs +++ b/gltf-derive/src/lib.rs @@ -13,27 +13,6 @@ 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() -} - -struct ValidateHook(pub syn::Ident); - -impl syn::parse::Parse for ValidateHook { - 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)) - } else { - panic!("unrecognized gltf attribute"); - } - } -} - /// Provided `struct` attributes. enum StructAttribute { /// Identifies a indexable data structure. @@ -42,6 +21,9 @@ enum StructAttribute { /// extra `fn index(&self) -> usize` function defined in their /// generated reader type. Indexed, + + /// A hook for extra validation steps. + ValidateHook(syn::Ident), } /// Provided attributes for named `struct` fields. @@ -61,6 +43,12 @@ impl syn::parse::Parse for StructAttribute { let tag = input.parse::()?; match tag.to_string().as_str() { "indexed" => Ok(Self::Indexed), + "validate_hook" => { + let _eq = input.parse::()?; + let literal = input.parse::()?; + let ident = syn::Ident::new(&literal.value(), tag.span()); + Ok(Self::ValidateHook(ident)) + } unrecognized => { panic!("gltf({unrecognized}) is not a recognized `struct` attribute") } @@ -400,6 +388,7 @@ fn wrap_indexed(attributes: &[syn::Attribute]) -> TokenStream2 { } } } + StructAttribute::ValidateHook(_) => {} } } } @@ -598,32 +587,18 @@ pub fn derive_validate(input: TokenStream) -> TokenStream { expand_validate(&syn::parse_macro_input!(input as DeriveInput)).into() } -struct ValidateHook(pub syn::Ident); - -impl syn::parse::Parse for ValidateHook { - 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)) - } else { - panic!("unrecognized gltf attribute"); - } - } -} - -fn expand_validate(ast: &DeriveInput) -> proc_macro2::TokenStream2 { +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::ValidateHook(ident) = parsed_attr { + validate_hook = quote! { + #ident(self, _root, _path, _report); + }; + } } } diff --git a/src/accessor.rs b/src/accessor.rs deleted file mode 100644 index 5900cbfb..00000000 --- a/src/accessor.rs +++ /dev/null @@ -1,282 +0,0 @@ -use crate::validation::{Error, USize64, Validate}; -use crate::{buffer, Extras, Index, Path, Root, UnrecognizedExtensions}; -use serde_json::Value; - -/// 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 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, - } - } -} - -/// 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, -} -impl Validate for AttributeType {} - -/// Contains data structures for sparse storage. -pub mod sparse { - use super::*; - use crate::validation::Validate; - - /// 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, - } - impl Validate for IndexType {} - - 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() - } - } - - /// Indices of those attributes that deviate from their initialization value. - #[derive( - Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, 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, - } - - /// Sparse storage of attributes that deviate from their initialization value. - #[derive( - Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, 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, - } - - /// 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::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)] -pub struct Accessor { - /// Specifies if the attribute is a scalar, vector, or matrix. - #[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, - - /// 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, -} - -impl Validate for Accessor { - fn validate(&self, root: &Root, path: P, report: &mut R) - where - P: Fn() -> Path, - R: FnMut(&dyn Fn() -> Path, Error), - { - if self.sparse.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. - if self.buffer_view.is_none() { - report(&|| path().field("bufferView"), Error::Missing); - } - } - - self.attribute_type - .validate(root, || path().field("type"), report); - self.buffer_view - .validate(root, || path().field("bufferView"), report); - self.byte_offset - .validate(root, || path().field("byteOffset"), report); - self.count.validate(root, || path().field("count"), report); - self.component_type - .validate(root, || path().field("componentType"), report); - self.extras - .validate(root, || path().field("extras"), report); - self.min.validate(root, || path().field("min"), report); - self.max.validate(root, || path().field("max"), report); - self.normalized - .validate(root, || path().field("normalized"), report); - self.sparse - .validate(root, || path().field("sparse"), report); - } -} - -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, - } - } - - /// Returns the corresponding `GLenum`. - pub fn as_gl_enum(self) -> u32 { - self as u32 - } -} - -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/mod.rs b/src/accessor/mod.rs index f4d3fc67..cdfdfa7e 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,279 @@ //! # } //! ``` -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, 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 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 Validate for ComponentType {} - /// The corresponding JSON index. - index: usize, +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, + } + } +} - /// The corresponding JSON struct. - json: &'a json::accessor::Accessor, +/// 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, } +impl Validate for AttributeType {} + +/// Contains data structures for sparse storage. +pub mod sparse { + use crate::validation::{USize64, Validate}; + use crate::{buffer, Extras, Index, 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, + } + impl Validate for IndexType {} + + impl IndexType { + /// Returns the number of bytes this value represents. + pub fn size(self) -> usize { + super::ComponentType::from(self).size() + } -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, + /// Returns the corresponding `GLenum`. + pub fn as_gl_enum(self) -> u32 { + super::ComponentType::from(self).as_gl_enum() } } - /// Returns the internal JSON index. - pub fn index(&self) -> usize { - self.index + /// Indices of those attributes that deviate from their initialization value. + #[derive( + Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, 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, } - /// Returns the size of each component that this accessor describes. - pub fn size(&self) -> usize { - self.data_type().size() * self.dimensions().multiplicity() + /// Sparse storage of attributes that deviate from their initialization value. + #[derive( + Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, 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, } - /// 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()) + /// 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::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::Validate)] +#[gltf(validate_hook = "accessor_validate_hook")] +pub struct Accessor { + /// Specifies if the attribute is a scalar, vector, or matrix. + #[serde(rename = "type")] + pub attribute_type: AttributeType, - /// Returns the offset relative to the start of the parent buffer view in bytes. + /// The parent buffer view this accessor reads from. /// - /// 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 - } + /// 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, - /// Returns the number of components within the buffer view - not to be confused + /// 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 - } + pub count: USize64, - /// Returns the data type of components in the attribute. - pub fn data_type(&self) -> DataType { - self.json.component_type.unwrap().0 - } + /// The data type of components in the attribute. + pub component_type: ComponentType, - /// 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) - } + /// Minimum value of each component in this attribute. + pub min: 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) - } + /// Maximum value of each component in this attribute. + pub max: Option, - /// Optional application specific data. - pub fn extras(&self) -> &'a json::Extras { - &self.json.extras - } + /// Optional user-defined name for this object. + pub name: Option, - /// Specifies if the attribute is a scalar, vector, or matrix. - pub fn dimensions(&self) -> Dimensions { - self.json.type_.unwrap() - } + /// Specifies whether integer data values should be normalized. + pub normalized: bool, - /// Returns the minimum value of each component in this attribute. - pub fn min(&self) -> Option { - self.json.min.clone() + /// 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 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); } +} - /// 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/util.rs b/src/accessor/util.rs index a9f027bd..e5ef634c 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,25 @@ 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 + .map(|s| s.value()) + .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 +320,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.map(|s| s.value()).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 +349,13 @@ 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 + .map(|s| s.value()) + .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 +363,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 +371,34 @@ 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 + .map(|s| s.value()) + .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 +426,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 +446,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/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/buffer.rs b/src/buffer.rs index 3d82ab23..a0b59629 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -2,10 +2,10 @@ use crate::validation::{Error, USize64, Validate}; use crate::{Extras, Index, Path, Root, UnrecognizedExtensions}; /// The minimum byte stride. -pub const MIN_BYTE_STRIDE: usize = 4; +pub const MIN_BYTE_STRIDE: u8 = 4; /// The maximum byte stride. -pub const MAX_BYTE_STRIDE: usize = 252; +pub const MAX_BYTE_STRIDE: u8 = 252; /// Specifies the target a GPU buffer should be bound to. #[derive( @@ -33,7 +33,14 @@ impl Validate for Target {} serde_derive::Deserialize, serde_derive::Serialize, )] -pub struct Stride(pub usize); +pub struct Stride(pub u8); + +impl Stride { + /// Widens the value to `usize`. + pub fn value(&self) -> usize { + self.0 as usize + } +} impl Validate for Stride { fn validate(&self, _root: &Root, path: P, report: &mut R) diff --git a/src/lib.rs b/src/lib.rs index 7f6a2d53..8658da65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -371,3 +371,14 @@ impl From> for Error { Error::Validation(errs) } } + +/// 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/src/mesh.rs b/src/mesh.rs deleted file mode 100644 index f1c152df..00000000 --- a/src/mesh.rs +++ /dev/null @@ -1,315 +0,0 @@ -use crate::validation::{Error, Validate}; -use crate::{accessor, material, Extras, Index, Path, 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::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::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, - } -} - -/// 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 Validate for Mode {} - -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::Validate)] -pub struct Mesh { - /// Optional user-defined name for this object. - 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. - pub weights: Vec, - - /// Unrecognized extension data. - pub unrecognized_extensions: UnrecognizedExtensions, - - /// Optional application specific data. - pub extras: Option, -} - -/// Geometry to be rendered with the given material. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize)] -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>, - - /// The type of primitives to render. - #[gltf(default)] - pub mode: Mode, - - /// 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, - - /// Unrecognized extension data. - pub unrecognized_extensions: UnrecognizedExtensions, - - /// Optional application specific data. - pub extras: Option, - - /// Support for the `KHR_materials_variants` extension. - #[gltf(extension = "KHR_materials_variants")] - pub variants: Option, -} - -impl Validate for Primitive { - fn validate(&self, root: &Root, path: P, report: &mut R) - where - P: Fn() -> Path, - R: FnMut(&dyn Fn() -> Path, Error), - { - // Generated part - self.attributes - .validate(root, || path().field("attributes"), report); - self.extras - .validate(root, || path().field("extras"), report); - self.indices - .validate(root, || path().field("indices"), report); - self.material - .validate(root, || path().field("material"), report); - self.mode.validate(root, || path().field("mode"), report); - self.targets - .validate(root, || path().field("targets"), report); - - // Custom part - let position_path = &|| path().field("attributes").key("POSITION"); - if let Some(pos_accessor_index) = self.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 { - 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, gltf_derive::Deserialize, gltf_derive::Serialize, 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())), - } - } -} - -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 { - 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/mod.rs b/src/mesh/mod.rs new file mode 100644 index 00000000..388bb359 --- /dev/null +++ b/src/mesh/mod.rs @@ -0,0 +1,521 @@ +/// Utility functions. +#[cfg(feature = "utils")] +#[cfg_attr(docsrs, doc(cfg(feature = "utils")))] +pub mod util; + +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::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::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]>; + +/// The minimum and maximum values for a generic accessor. +#[derive(Clone, Debug, PartialEq)] +pub struct Bounds { + /// Minimum value. + pub min: T, + + /// Maximum value. + pub max: T, +} + +/// 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 Validate for Mode {} + +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::Validate)] +pub struct Mesh { + /// Optional user-defined name for this object. + 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. + pub weights: Vec, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, +} + +/// Geometry to be rendered with the given material. +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::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>, + + /// 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>, + + /// The type of primitives to render. + #[gltf(default)] + pub mode: Mode, + + /// 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, + + /// Unrecognized extension data. + pub unrecognized_extensions: UnrecognizedExtensions, + + /// Optional application specific data. + pub extras: Option, + + /// Support for the `KHR_materials_variants` extension. + #[gltf(extension = "KHR_materials_variants")] + pub variants: Option, +} + +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(&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); + } +} + +#[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<'a, 's, F>(&'a self, root: &'a Root, get_buffer_data: F) -> Reader<'a, 's, F> + where + 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(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(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(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(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::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::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::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::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::ComponentType; + use self::util::ReadWeights; + 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. + pub fn read_morph_targets(&self) -> util::ReadMorphTargets<'a, 's, F> { + util::ReadMorphTargets { + index: 0, + reader: self.clone(), + } + } +} + +/// A dictionary mapping attributes to their deviations in the Morph Target. +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, 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())), + } + } +} + +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 { + 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 new file mode 100644 index 00000000..855aa139 --- /dev/null +++ b/src/mesh/util/colors.rs @@ -0,0 +1,337 @@ +use std::marker::PhantomData; + +use super::normalize::Normalize; + +use super::ReadColors; + +/// Casting iterator for `Colors`. +#[derive(Clone, Debug)] +pub struct CastingIter<'a, T>(ReadColors<'a>, PhantomData); + +/// Type which describes how to cast any color into RGB u8. +#[derive(Clone, Debug)] +pub struct RgbU8; + +/// Type which describes how to cast any color into RGB u16. +#[derive(Clone, Debug)] +pub struct RgbU16; + +/// Type which describes how to cast any color into RGB f32. +#[derive(Clone, Debug)] +pub struct RgbF32; + +/// Type which describes how to cast any color into RGBA u8. +#[derive(Clone, Debug)] +pub struct RgbaU8; + +/// Type which describes how to cast any color into RGBA u16. +#[derive(Clone, Debug)] +pub struct RgbaU16; + +/// Type which describes how to cast any color into RGBA f32. +#[derive(Clone, Debug)] +pub struct RgbaF32; + +trait ColorChannel { + fn max_color() -> Self; +} + +impl ColorChannel for u8 { + fn max_color() -> Self { + u8::max_value() + } +} + +impl ColorChannel for u16 { + fn max_color() -> Self { + u16::max_value() + } +} + +impl ColorChannel for f32 { + fn max_color() -> Self { + 1.0 + } +} + +trait ColorArray { + fn into_rgb(self) -> [T; 3]; + fn into_rgba(self) -> [T; 4]; +} + +impl ColorArray for [T; 3] { + fn into_rgb(self) -> [T; 3] { + self + } + fn into_rgba(self) -> [T; 4] { + [self[0], self[1], self[2], T::max_color()] + } +} + +impl ColorArray for [T; 4] { + fn into_rgb(self) -> [T; 3] { + [self[0], self[1], self[2]] + } + fn into_rgba(self) -> [T; 4] { + self + } +} + +/// Trait for types which describe casting behaviour. +pub trait Cast { + /// Output type. + type Output; + + /// Cast from RGB u8. + fn cast_rgb_u8(x: [u8; 3]) -> Self::Output; + + /// Cast from RGB u16. + fn cast_rgb_u16(x: [u16; 3]) -> Self::Output; + + /// Cast from RGB f32. + fn cast_rgb_f32(x: [f32; 3]) -> Self::Output; + + /// Cast from RGBA u8. + fn cast_rgba_u8(x: [u8; 4]) -> Self::Output; + + /// Cast from RGBA u16. + fn cast_rgba_u16(x: [u16; 4]) -> Self::Output; + + /// Cast from RGBA f32. + fn cast_rgba_f32(x: [f32; 4]) -> Self::Output; +} + +impl<'a, A> CastingIter<'a, A> { + pub(crate) fn new(iter: ReadColors<'a>) -> Self { + CastingIter(iter, PhantomData) + } + + /// Unwrap underlying `ReadColors` object. + pub fn unwrap(self) -> ReadColors<'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 { + ReadColors::RgbU8(ref mut i) => i.next().map(A::cast_rgb_u8), + ReadColors::RgbU16(ref mut i) => i.next().map(A::cast_rgb_u16), + ReadColors::RgbF32(ref mut i) => i.next().map(A::cast_rgb_f32), + ReadColors::RgbaU8(ref mut i) => i.next().map(A::cast_rgba_u8), + ReadColors::RgbaU16(ref mut i) => i.next().map(A::cast_rgba_u16), + ReadColors::RgbaF32(ref mut i) => i.next().map(A::cast_rgba_f32), + } + } + + #[inline] + fn nth(&mut self, x: usize) -> Option { + match self.0 { + ReadColors::RgbU8(ref mut i) => i.nth(x).map(A::cast_rgb_u8), + ReadColors::RgbU16(ref mut i) => i.nth(x).map(A::cast_rgb_u16), + ReadColors::RgbF32(ref mut i) => i.nth(x).map(A::cast_rgb_f32), + ReadColors::RgbaU8(ref mut i) => i.nth(x).map(A::cast_rgba_u8), + ReadColors::RgbaU16(ref mut i) => i.nth(x).map(A::cast_rgba_u16), + ReadColors::RgbaF32(ref mut i) => i.nth(x).map(A::cast_rgba_f32), + } + } + + fn last(self) -> Option { + match self.0 { + ReadColors::RgbU8(i) => i.last().map(A::cast_rgb_u8), + ReadColors::RgbU16(i) => i.last().map(A::cast_rgb_u16), + ReadColors::RgbF32(i) => i.last().map(A::cast_rgb_f32), + ReadColors::RgbaU8(i) => i.last().map(A::cast_rgba_u8), + ReadColors::RgbaU16(i) => i.last().map(A::cast_rgba_u16), + ReadColors::RgbaF32(i) => i.last().map(A::cast_rgba_f32), + } + } + + fn count(self) -> usize { + self.size_hint().0 + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self.0 { + ReadColors::RgbU8(ref i) => i.size_hint(), + ReadColors::RgbU16(ref i) => i.size_hint(), + ReadColors::RgbF32(ref i) => i.size_hint(), + ReadColors::RgbaU8(ref i) => i.size_hint(), + ReadColors::RgbaU16(ref i) => i.size_hint(), + ReadColors::RgbaF32(ref i) => i.size_hint(), + } + } +} + +impl Cast for RgbU8 { + type Output = [u8; 3]; + + fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { + x.into_rgb().normalize() + } +} + +impl Cast for RgbU16 { + type Output = [u16; 3]; + + fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { + x.into_rgb().normalize() + } +} + +impl Cast for RgbF32 { + type Output = [f32; 3]; + + fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { + x.into_rgb().normalize() + } + + fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { + x.into_rgb().normalize() + } +} + +impl Cast for RgbaU8 { + type Output = [u8; 4]; + + fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { + x.normalize().into_rgba() + } +} + +impl Cast for RgbaU16 { + type Output = [u16; 4]; + + fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { + x.normalize().into_rgba() + } +} + +impl Cast for RgbaF32 { + type Output = [f32; 4]; + + fn cast_rgb_u8(x: [u8; 3]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgb_u16(x: [u16; 3]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgb_f32(x: [f32; 3]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgba_u8(x: [u8; 4]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgba_u16(x: [u16; 4]) -> Self::Output { + x.normalize().into_rgba() + } + + fn cast_rgba_f32(x: [f32; 4]) -> Self::Output { + x.normalize().into_rgba() + } +} diff --git a/src/mesh/util/indices.rs b/src/mesh/util/indices.rs new file mode 100644 index 00000000..acf99572 --- /dev/null +++ b/src/mesh/util/indices.rs @@ -0,0 +1,95 @@ +use std::marker::PhantomData; + +use super::ReadIndices; + +/// Casting iterator for `Indices`. +#[derive(Clone, Debug)] +pub struct CastingIter<'a, T>(ReadIndices<'a>, PhantomData); + +/// Type which describes how to cast any index into u32. +#[derive(Clone, Debug)] +pub struct U32; + +/// Trait for types which describe casting behaviour. +pub trait Cast { + /// Output type. + type Output; + + /// Cast from u8. + fn cast_u8(x: u8) -> Self::Output; + + /// Cast from u16. + fn cast_u16(x: u16) -> Self::Output; + + /// Cast from u32. + fn cast_u32(x: u32) -> Self::Output; +} + +impl<'a, A> CastingIter<'a, A> { + pub(crate) fn new(iter: ReadIndices<'a>) -> Self { + CastingIter(iter, PhantomData) + } + + /// Unwrap underlying `Indices` object. + pub fn unwrap(self) -> ReadIndices<'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 { + ReadIndices::U8(ref mut i) => i.next().map(A::cast_u8), + ReadIndices::U16(ref mut i) => i.next().map(A::cast_u16), + ReadIndices::U32(ref mut i) => i.next().map(A::cast_u32), + } + } + + #[inline] + fn nth(&mut self, x: usize) -> Option { + match self.0 { + ReadIndices::U8(ref mut i) => i.nth(x).map(A::cast_u8), + ReadIndices::U16(ref mut i) => i.nth(x).map(A::cast_u16), + ReadIndices::U32(ref mut i) => i.nth(x).map(A::cast_u32), + } + } + + fn last(self) -> Option { + match self.0 { + ReadIndices::U8(i) => i.last().map(A::cast_u8), + ReadIndices::U16(i) => i.last().map(A::cast_u16), + ReadIndices::U32(i) => i.last().map(A::cast_u32), + } + } + + fn count(self) -> usize { + self.size_hint().0 + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self.0 { + ReadIndices::U8(ref i) => i.size_hint(), + ReadIndices::U16(ref i) => i.size_hint(), + ReadIndices::U32(ref i) => i.size_hint(), + } + } +} + +impl Cast for U32 { + type Output = u32; + + fn cast_u8(x: u8) -> Self::Output { + x as Self::Output + } + fn cast_u16(x: u16) -> Self::Output { + x as Self::Output + } + fn cast_u32(x: u32) -> Self::Output { + x + } +} diff --git a/src/mesh/util/joints.rs b/src/mesh/util/joints.rs new file mode 100644 index 00000000..b8202272 --- /dev/null +++ b/src/mesh/util/joints.rs @@ -0,0 +1,86 @@ +use std::marker::PhantomData; + +use super::ReadJoints; + +/// Casting iterator for `Joints`. +#[derive(Clone, Debug)] +pub struct CastingIter<'a, T>(ReadJoints<'a>, PhantomData); + +/// Type which describes how to cast any joint into u16. +#[derive(Clone, Debug)] +pub struct U16; + +/// Trait for types which describe casting behaviour. +pub trait Cast { + /// Output type. + type Output; + + /// Cast from u8. + fn cast_u8(x: [u8; 4]) -> Self::Output; + + /// Cast from u16. + fn cast_u16(x: [u16; 4]) -> Self::Output; +} + +impl<'a, A> CastingIter<'a, A> { + pub(crate) fn new(iter: ReadJoints<'a>) -> Self { + CastingIter(iter, PhantomData) + } + + /// Unwrap underlying `Joints` object. + pub fn unwrap(self) -> ReadJoints<'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 { + ReadJoints::U8(ref mut i) => i.next().map(A::cast_u8), + ReadJoints::U16(ref mut i) => i.next().map(A::cast_u16), + } + } + + #[inline] + fn nth(&mut self, x: usize) -> Option { + match self.0 { + ReadJoints::U8(ref mut i) => i.nth(x).map(A::cast_u8), + ReadJoints::U16(ref mut i) => i.nth(x).map(A::cast_u16), + } + } + + fn last(self) -> Option { + match self.0 { + ReadJoints::U8(i) => i.last().map(A::cast_u8), + ReadJoints::U16(i) => i.last().map(A::cast_u16), + } + } + + fn count(self) -> usize { + self.size_hint().0 + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self.0 { + ReadJoints::U8(ref i) => i.size_hint(), + ReadJoints::U16(ref i) => i.size_hint(), + } + } +} + +impl Cast for U16 { + type Output = [u16; 4]; + + fn cast_u8(x: [u8; 4]) -> Self::Output { + [x[0] as u16, x[1] as u16, x[2] as u16, x[3] as u16] + } + + fn cast_u16(x: [u16; 4]) -> Self::Output { + x + } +} diff --git a/src/mesh/util/mod.rs b/src/mesh/util/mod.rs new file mode 100644 index 00000000..f28716b7 --- /dev/null +++ b/src/mesh/util/mod.rs @@ -0,0 +1,257 @@ +/// Casting iterator adapters for colors. +pub mod colors; + +/// Casting iterator adapters for vertex indices. +pub mod indices; + +/// Casting iterator adapters for joint indices. +pub mod joints; + +/// Casting iterator adapters for texture co-ordinates. +pub mod tex_coords; + +/// Casting iterator adapters for node weights. +pub mod weights; + +/// Conversions for normalized integers. +pub(crate) mod normalize; + +use crate::{mesh, Index}; + +use crate::accessor::Iter; +use crate::Buffer; + +/// XYZ vertex positions of type `[f32; 3]`. +pub type ReadPositions<'a> = Iter<'a, [f32; 3]>; + +/// XYZ vertex normals of type `[f32; 3]`. +pub type ReadNormals<'a> = Iter<'a, [f32; 3]>; + +/// XYZW vertex tangents of type `[f32; 4]` where the `w` component is a +/// sign value (-1 or +1) indicating the handedness of the tangent basis. +pub type ReadTangents<'a> = Iter<'a, [f32; 4]>; + +/// XYZ vertex position displacements of type `[f32; 3]`. +pub type ReadPositionDisplacements<'a> = Iter<'a, [f32; 3]>; + +/// XYZ vertex normal displacements of type `[f32; 3]`. +pub type ReadNormalDisplacements<'a> = Iter<'a, [f32; 3]>; + +/// XYZ vertex tangent displacements. +pub type ReadTangentDisplacements<'a> = Iter<'a, [f32; 3]>; + +/// Vertex colors. +#[derive(Clone, Debug)] +pub enum ReadColors<'a> { + /// RGB vertex color of type `[u8; 3]>`. + RgbU8(Iter<'a, [u8; 3]>), + /// RGB vertex color of type `[u16; 3]>`. + RgbU16(Iter<'a, [u16; 3]>), + /// RGB vertex color of type `[f32; 3]`. + RgbF32(Iter<'a, [f32; 3]>), + /// RGBA vertex color of type `[u8; 4]>`. + RgbaU8(Iter<'a, [u8; 4]>), + /// RGBA vertex color of type `[u16; 4]>`. + RgbaU16(Iter<'a, [u16; 4]>), + /// RGBA vertex color of type `[f32; 4]`. + RgbaF32(Iter<'a, [f32; 4]>), +} + +/// Index data. +#[derive(Clone, Debug)] +pub enum ReadIndices<'a> { + /// Index data of type U8 + U8(Iter<'a, u8>), + /// Index data of type U16 + U16(Iter<'a, u16>), + /// Index data of type U32 + U32(Iter<'a, u32>), +} + +/// Vertex joints. +#[derive(Clone, Debug)] +pub enum ReadJoints<'a> { + /// Joints of type `[u8; 4]`. + /// Refer to the documentation on morph targets and skins for more + /// information. + U8(Iter<'a, [u8; 4]>), + /// Joints of type `[u16; 4]`. + /// Refer to the documentation on morph targets and skins for more + /// information. + U16(Iter<'a, [u16; 4]>), +} + +/// UV texture co-ordinates. +#[derive(Clone, Debug)] +pub enum ReadTexCoords<'a> { + /// UV texture co-ordinates of type `[u8; 2]>`. + U8(Iter<'a, [u8; 2]>), + /// UV texture co-ordinates of type `[u16; 2]>`. + U16(Iter<'a, [u16; 2]>), + /// UV texture co-ordinates of type `[f32; 2]`. + F32(Iter<'a, [f32; 2]>), +} + +/// Weights. +#[derive(Clone, Debug)] +pub enum ReadWeights<'a> { + /// Weights of type `[u8; 4]`. + U8(Iter<'a, [u8; 4]>), + /// Weights of type `[u16; 4]`. + U16(Iter<'a, [u16; 4]>), + /// Weights of type `[f32; 4]`. + F32(Iter<'a, [f32; 4]>), +} + +/// Morph targets. +#[derive(Clone, Debug)] +pub struct ReadMorphTargets<'a, 's, F> +where + 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(Index) -> Option<&'s [u8]> +{ +} + +impl<'a, 's, F> Iterator for ReadMorphTargets<'a, 's, F> +where + F: Clone + Fn(Index) -> Option<&'s [u8]>, +{ + type Item = ( + Option>, + Option>, + Option>, + ); + fn next(&mut self) -> Option { + self.index += 1; + self.reader + .primitive + .targets + .get(self.index - 1) + .map(|morph_target| { + 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.targets.iter().size_hint() + } +} + +impl<'a> ReadColors<'a> { + /// Reinterpret colors as RGB u8, discarding alpha, if present. Lossy if + /// the underlying iterator yields u16, f32 or any RGBA. + pub fn into_rgb_u8(self) -> self::colors::CastingIter<'a, self::colors::RgbU8> { + self::colors::CastingIter::new(self) + } + + /// Reinterpret colors as RGB u16, discarding alpha, if present. Lossy if + /// the underlying iterator yields f32 or any RGBA. + pub fn into_rgb_u16(self) -> self::colors::CastingIter<'a, self::colors::RgbU16> { + self::colors::CastingIter::new(self) + } + + /// Reinterpret colors as RGB f32, discarding alpha, if present. Lossy if + /// the underlying iterator yields u16 or any RGBA. + pub fn into_rgb_f32(self) -> self::colors::CastingIter<'a, self::colors::RgbF32> { + self::colors::CastingIter::new(self) + } + + /// Reinterpret colors as RGBA u8, with default alpha 255. Lossy if the + /// underlying iterator yields u16 or f32. + pub fn into_rgba_u8(self) -> self::colors::CastingIter<'a, self::colors::RgbaU8> { + self::colors::CastingIter::new(self) + } + + /// Reinterpret colors as RGBA u16, with default alpha 65535. Lossy if the + /// underlying iterator yields f32. + pub fn into_rgba_u16(self) -> self::colors::CastingIter<'a, self::colors::RgbaU16> { + self::colors::CastingIter::new(self) + } + + /// Reinterpret colors as RGBA f32, with default alpha 1.0. Lossy if the + /// underlying iterator yields u16. + pub fn into_rgba_f32(self) -> self::colors::CastingIter<'a, self::colors::RgbaF32> { + self::colors::CastingIter::new(self) + } +} + +impl<'a> ReadIndices<'a> { + /// Reinterpret indices as u32, which can fit any possible index. + pub fn into_u32(self) -> self::indices::CastingIter<'a, self::indices::U32> { + self::indices::CastingIter::new(self) + } +} + +impl<'a> ReadJoints<'a> { + /// Reinterpret joints as u16, which can fit any possible joint. + pub fn into_u16(self) -> self::joints::CastingIter<'a, self::joints::U16> { + self::joints::CastingIter::new(self) + } +} + +impl<'a> ReadTexCoords<'a> { + /// Reinterpret texture coordinates as u8. Lossy if the underlying iterator + /// yields u16 or f32. + pub fn into_u8(self) -> self::tex_coords::CastingIter<'a, self::tex_coords::U8> { + self::tex_coords::CastingIter::new(self) + } + + /// Reinterpret texture coordinates as u16. Lossy if the underlying + /// iterator yields f32. + pub fn into_u16(self) -> self::tex_coords::CastingIter<'a, self::tex_coords::U16> { + self::tex_coords::CastingIter::new(self) + } + + /// Reinterpret texture coordinates as f32. Lossy if the underlying + /// iterator yields u16. + pub fn into_f32(self) -> self::tex_coords::CastingIter<'a, self::tex_coords::F32> { + self::tex_coords::CastingIter::new(self) + } +} + +impl<'a> ReadWeights<'a> { + /// Reinterpret weights as u8. Lossy if the underlying iterator yields u16 + /// or f32. + pub fn into_u8(self) -> self::weights::CastingIter<'a, self::weights::U8> { + self::weights::CastingIter::new(self) + } + + /// Reinterpret weights as u16. Lossy if the underlying iterator yields + /// f32. + pub fn into_u16(self) -> self::weights::CastingIter<'a, self::weights::U16> { + self::weights::CastingIter::new(self) + } + + /// Reinterpret weights as f32. Lossy if the underlying iterator yields + /// u16. + pub fn into_f32(self) -> self::weights::CastingIter<'a, self::weights::F32> { + self::weights::CastingIter::new(self) + } +} 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 new file mode 100644 index 00000000..a348b678 --- /dev/null +++ b/src/mesh/util/tex_coords.rs @@ -0,0 +1,139 @@ +use std::marker::PhantomData; + +use super::normalize::Normalize; + +use super::ReadTexCoords; + +/// Casting iterator for `TexCoords`. +#[derive(Clone, Debug)] +pub struct CastingIter<'a, T>(ReadTexCoords<'a>, PhantomData); + +/// Type which describes how to cast any texture coordinate into pair of u8. +#[derive(Clone, Debug)] +pub struct U8; + +/// Type which describes how to cast any texture coordinate into pair of u16. +#[derive(Clone, Debug)] +pub struct U16; + +/// Type which describes how to cast any texture coordinate into pair of f32. +#[derive(Clone, Debug)] +pub struct F32; + +/// Trait for types which describe casting behaviour. +pub trait Cast { + /// Output type. + type Output; + + /// Cast from u8 pair. + fn cast_u8(x: [u8; 2]) -> Self::Output; + + /// Cast from u16 pair. + fn cast_u16(x: [u16; 2]) -> Self::Output; + + /// Cast from f32 pair. + fn cast_f32(x: [f32; 2]) -> Self::Output; +} + +impl<'a, A> CastingIter<'a, A> { + pub(crate) fn new(iter: ReadTexCoords<'a>) -> Self { + CastingIter(iter, PhantomData) + } + + /// Unwrap underlying `TexCoords` object. + pub fn unwrap(self) -> ReadTexCoords<'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 { + ReadTexCoords::U8(ref mut i) => i.next().map(A::cast_u8), + ReadTexCoords::U16(ref mut i) => i.next().map(A::cast_u16), + ReadTexCoords::F32(ref mut i) => i.next().map(A::cast_f32), + } + } + + #[inline] + fn nth(&mut self, x: usize) -> Option { + match self.0 { + ReadTexCoords::U8(ref mut i) => i.nth(x).map(A::cast_u8), + ReadTexCoords::U16(ref mut i) => i.nth(x).map(A::cast_u16), + ReadTexCoords::F32(ref mut i) => i.nth(x).map(A::cast_f32), + } + } + + fn last(self) -> Option { + match self.0 { + ReadTexCoords::U8(i) => i.last().map(A::cast_u8), + ReadTexCoords::U16(i) => i.last().map(A::cast_u16), + ReadTexCoords::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 { + ReadTexCoords::U8(ref i) => i.size_hint(), + ReadTexCoords::U16(ref i) => i.size_hint(), + ReadTexCoords::F32(ref i) => i.size_hint(), + } + } +} + +impl Cast for U8 { + type Output = [u8; 2]; + + fn cast_u8(x: [u8; 2]) -> Self::Output { + x.normalize() + } + + fn cast_u16(x: [u16; 2]) -> Self::Output { + x.normalize() + } + + fn cast_f32(x: [f32; 2]) -> Self::Output { + x.normalize() + } +} + +impl Cast for U16 { + type Output = [u16; 2]; + + fn cast_u8(x: [u8; 2]) -> Self::Output { + x.normalize() + } + + fn cast_u16(x: [u16; 2]) -> Self::Output { + x.normalize() + } + + fn cast_f32(x: [f32; 2]) -> Self::Output { + x.normalize() + } +} + +impl Cast for F32 { + type Output = [f32; 2]; + + fn cast_u8(x: [u8; 2]) -> Self::Output { + x.normalize() + } + + fn cast_u16(x: [u16; 2]) -> Self::Output { + x.normalize() + } + + fn cast_f32(x: [f32; 2]) -> Self::Output { + x.normalize() + } +} diff --git a/src/mesh/util/weights.rs b/src/mesh/util/weights.rs new file mode 100644 index 00000000..51a4a096 --- /dev/null +++ b/src/mesh/util/weights.rs @@ -0,0 +1,139 @@ +use std::marker::PhantomData; + +use super::normalize::Normalize; + +use super::ReadWeights; + +/// Casting iterator for `Weights`. +#[derive(Clone, Debug)] +pub struct CastingIter<'a, T>(ReadWeights<'a>, PhantomData); + +/// 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 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 u8. + fn cast_u8(x: [u8; 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: ReadWeights<'a>) -> Self { + CastingIter(iter, PhantomData) + } + + /// Unwrap underlying `Weights` object. + pub fn unwrap(self) -> ReadWeights<'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 { + ReadWeights::U8(ref mut i) => i.next().map(A::cast_u8), + ReadWeights::U16(ref mut i) => i.next().map(A::cast_u16), + ReadWeights::F32(ref mut i) => i.next().map(A::cast_f32), + } + } + + #[inline] + fn nth(&mut self, x: usize) -> Option { + match self.0 { + ReadWeights::U8(ref mut i) => i.nth(x).map(A::cast_u8), + ReadWeights::U16(ref mut i) => i.nth(x).map(A::cast_u16), + ReadWeights::F32(ref mut i) => i.nth(x).map(A::cast_f32), + } + } + + fn last(self) -> Option { + match self.0 { + ReadWeights::U8(i) => i.last().map(A::cast_u8), + ReadWeights::U16(i) => i.last().map(A::cast_u16), + ReadWeights::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 { + ReadWeights::U8(ref i) => i.size_hint(), + ReadWeights::U16(ref i) => i.size_hint(), + ReadWeights::F32(ref i) => i.size_hint(), + } + } +} + +impl Cast for U8 { + type Output = [u8; 4]; + + fn cast_u8(x: [u8; 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_u8(x: [u8; 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_u8(x: [u8; 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/path.rs b/src/path.rs index 0567f2d6..b7c010c3 100644 --- a/src/path.rs +++ b/src/path.rs @@ -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/src/root.rs b/src/root.rs index 9e5918df..d042c7b0 100644 --- a/src/root.rs +++ b/src/root.rs @@ -157,7 +157,7 @@ where 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() @@ -508,22 +508,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/tests/box_sparse.bin b/tests/box_sparse.bin new file mode 100644 index 0000000000000000000000000000000000000000..bf5c336611f775426ff57d91e5ae57cd67c182b5 GIT binary patch literal 352 zcmb8pQ4WJJ3pl`o@QJ0-juSgdRn;RrgOW>lg*CjV%WcBV=%pRA zcD%)<;>sMC_T8KJ2AA^>pXeSs+x5-)j5qGvW%u&%@vmPT^$mMaouj+H(`UEy52DZ! A;Q#;t literal 0 HcmV?d00001 diff --git a/tests/box_sparse.glb b/tests/box_sparse.glb new file mode 100644 index 0000000000000000000000000000000000000000..e132c815e68f93d1bfc62a0edc41881c51ce6567 GIT binary patch literal 2100 zcmb_d+iu!G5Z&JU8Pz_!5g3y;ytHYQN>m7{g7Q)!bs>wfWUnoIAw&qNpVhbgTF>nI z1|}7)iZsmZWiET>Vn3XGe##j0UNiRVC1YR4qv4i|`CMjMoMycDeamO7#X_bxfxP3r zmcwWFT8A<+$$QCV~r*M7|3`(y49!x48k*H@j=+1u7x*D3!>&$OG9jp67c-P4^O zzVWPjW!uPEBGL>fZ2_A>5Kj%%Z2;tHvkp4kHbDO)(tvs`!d25!r(vnW3re-cVIsVS zW*+vxV`6cznRkK7p-U7`Mp7cEz=|pO=Pe& z=nt<=6U8fS?zlS}eP%jyKM-FknF<{vSnh{kO!)G1fB8@LC+v8nM~hv%ydX z22%MHf2WINS8xzkRhj13&8#TrM4{RCn)Opa*6}XR@xGefat1W)T%g12HBs6f>og#& z+la7kct7CB4IhN(dPv$y5j7OCagrj6xc(B?!^TO9hZwls9F)^AK+-BV7`!rD(wxAY zuWu)55Cu9|=SM;V-`PsriSl(c?oaxoA>c-})YZxX2Ka0mtm#U_qbjA!q26fvrEpBv zm57i|K8rV;ZfA2;aPxX$1jA#x`|*UaPyHdjW{g2(&x~X_edC^kUVvWZGI4zJ43SKC zXic1S=`QJzEs}}Tk7WAMJ(7u&KAqEjvOzNOJcbVtA3y~!WLg(g-y^voIxl46#hSR0 ZZCpPg6S0q=2N3-VZddzuioL()zX2r7LjV8( literal 0 HcmV?d00001 diff --git a/tests/box_sparse.gltf b/tests/box_sparse.gltf new file mode 100644 index 00000000..2782f271 --- /dev/null +++ b/tests/box_sparse.gltf @@ -0,0 +1,210 @@ +{ + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "count": 36, + "componentType": 5125, + "extras": {}, + "type": "SCALAR", + "min": [ + 0 + ], + "max": [ + 7 + ] + }, + { + "bufferView": 1, + "byteOffset": 0, + "count": 8, + "componentType": 5126, + "extras": {}, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.5, + 0.5, + 0.5 + ] + }, + { + "byteOffset": 0, + "count": 2, + "componentType": 5126, + "extras": {}, + "type": "SCALAR", + "min": [ + 0.0 + ], + "max": [ + 1.0 + ], + "sparse": { + "count": 1, + "indices": { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5125, + "extras": {} + }, + "values": { + "bufferView": 3, + "byteOffset": 0, + "extras": {} + }, + "extras": {} + } + }, + { + "bufferView": 4, + "byteOffset": 0, + "count": 2, + "componentType": 5126, + "extras": {}, + "type": "SCALAR", + "min": [ + 1.0 + ], + "max": [ + 2.0 + ] + }, + { + "bufferView": 5, + "byteOffset": 0, + "count": 8, + "componentType": 5126, + "extras": {}, + "type": "VEC3", + "min": [ + -0.5, + -0.5, + -0.5 + ], + "max": [ + 0.0, + 0.0, + 0.0 + ] + } + ], + "animations": [ + { + "extras": {}, + "channels": [ + { + "sampler": 0, + "target": { + "extras": {}, + "node": 0, + "path": "weights" + }, + "extras": {} + } + ], + "samplers": [ + { + "extras": {}, + "input": 3, + "interpolation": "LINEAR", + "output": 2 + } + ] + } + ], + "asset": { + "extras": {}, + "generator": "gltfgen v0.2.0", + "version": "2.0" + }, + "buffers": [ + { + "byteLength": 352, + "uri": "box_sparse.bin", + "extras": {} + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 144, + "byteOffset": 0, + "target": 34963, + "extras": {} + }, + { + "buffer": 0, + "byteLength": 96, + "byteOffset": 144, + "byteStride": 12, + "target": 34962, + "extras": {} + }, + { + "buffer": 0, + "byteLength": 4, + "byteOffset": 240, + "extras": {} + }, + { + "buffer": 0, + "byteLength": 4, + "byteOffset": 244, + "extras": {} + }, + { + "buffer": 0, + "byteLength": 8, + "byteOffset": 248, + "extras": {} + }, + { + "buffer": 0, + "byteLength": 96, + "byteOffset": 256, + "byteStride": 12, + "target": 34962, + "extras": {} + } + ], + "extras": {}, + "meshes": [ + { + "extras": {}, + "primitives": [ + { + "attributes": { + "POSITION": 1 + }, + "extras": {}, + "indices": 0, + "targets": [ + { + "POSITION": 4 + } + ] + } + ] + } + ], + "nodes": [ + { + "extras": {}, + "mesh": 0, + "name": "box" + } + ], + "scenes": [ + { + "extras": {}, + "nodes": [ + 0 + ] + } + ] +} 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/minimal_accessor_min_max.gltf b/tests/minimal_accessor_min_max.gltf new file mode 100644 index 00000000..52f42fba --- /dev/null +++ b/tests/minimal_accessor_min_max.gltf @@ -0,0 +1,54 @@ +{ + "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.01, 0.02 ], + "min" : [ -0.03, -0.04, -0.05 ] + } + ], + + "asset" : { + "version" : "2.0" + } +} 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::>(); From 9b8bc87d4fc096594baa17661b17f35f85e475eb Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Tue, 4 Jun 2024 12:42:21 +0100 Subject: [PATCH 3/7] Address first round of feedback * Replace `buffer::Stride` with `usize` * Avoid serializing `buffer::View::offset` Additional changes: * Rename validate_hook as validate * Allow `#[gltf(validate = "...")]` to be applied to fields * Remove remaining gltf-json code * Document `gltf_derive::Default` macro * Document `gltf_derive::Validate` hook --- examples/export/main.rs | 2 +- gltf-derive/src/lib.rs | 234 ++++++++++++- gltf-json/Cargo.toml | 32 -- gltf-json/src/accessor.rs | 423 ----------------------- gltf-json/src/camera.rs | 163 --------- gltf-json/src/extensions/animation.rs | 24 -- gltf-json/src/extensions/mod.rs | 73 ---- gltf-json/src/extensions/scene.rs | 230 ------------- gltf-json/src/mesh.rs | 377 -------------------- gltf-json/src/texture.rs | 479 -------------------------- src/accessor/mod.rs | 4 +- src/accessor/util.rs | 17 +- src/buffer.rs | 55 +-- src/mesh/mod.rs | 4 +- src/root.rs | 4 +- src/validation.rs | 1 + 16 files changed, 254 insertions(+), 1868 deletions(-) delete mode 100644 gltf-json/Cargo.toml delete mode 100644 gltf-json/src/accessor.rs delete mode 100644 gltf-json/src/camera.rs delete mode 100644 gltf-json/src/extensions/animation.rs delete mode 100644 gltf-json/src/extensions/mod.rs delete mode 100644 gltf-json/src/extensions/scene.rs delete mode 100644 gltf-json/src/mesh.rs delete mode 100644 gltf-json/src/texture.rs diff --git a/examples/export/main.rs b/examples/export/main.rs index d7c0a72d..19e587b3 100644 --- a/examples/export/main.rs +++ b/examples/export/main.rs @@ -86,7 +86,7 @@ fn export(output: Output) { buffer, length: USize64::from(buffer_length), offset: USize64(0), - stride: Some(gltf::buffer::Stride(mem::size_of::() as u8)), + stride: Some(mem::size_of::()), name: None, target: Some(gltf::buffer::Target::ArrayBuffer), extras: Default::default(), diff --git a/gltf-derive/src/lib.rs b/gltf-derive/src/lib.rs index 8991c2f7..a6309a5a 100644 --- a/gltf-derive/src/lib.rs +++ b/gltf-derive/src/lib.rs @@ -15,15 +15,15 @@ use syn::{parse_quote, DeriveInput}; /// Provided `struct` attributes. enum StructAttribute { - /// Identifies a indexable data structure. + /// 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. - ValidateHook(syn::Ident), + /// A hook for extra validation steps applied to the whole struct. + Validate(syn::Ident), } /// Provided attributes for named `struct` fields. @@ -36,6 +36,9 @@ enum FieldAttribute { /// 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 StructAttribute { @@ -43,11 +46,11 @@ impl syn::parse::Parse for StructAttribute { let tag = input.parse::()?; match tag.to_string().as_str() { "indexed" => Ok(Self::Indexed), - "validate_hook" => { + "validate" => { let _eq = input.parse::()?; let literal = input.parse::()?; let ident = syn::Ident::new(&literal.value(), tag.span()); - Ok(Self::ValidateHook(ident)) + Ok(Self::Validate(ident)) } unrecognized => { panic!("gltf({unrecognized}) is not a recognized `struct` attribute") @@ -75,6 +78,12 @@ impl syn::parse::Parse for FieldAttribute { 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") } @@ -83,6 +92,26 @@ impl syn::parse::Parse for FieldAttribute { } /// 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() @@ -388,7 +417,7 @@ fn wrap_indexed(attributes: &[syn::Attribute]) -> TokenStream2 { } } } - StructAttribute::ValidateHook(_) => {} + StructAttribute::Validate(_) => {} } } } @@ -582,6 +611,173 @@ fn expand_wrap(ast: &DeriveInput) -> TokenStream2 { /// } /// } /// ``` +/// +/// # 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() @@ -594,9 +790,9 @@ fn expand_validate(ast: &DeriveInput) -> TokenStream2 { let parsed_attr = attr .parse_args::() .expect("failed to parse attribute"); - if let StructAttribute::ValidateHook(ident) = parsed_attr { + if let StructAttribute::Validate(hook_ident) = parsed_attr { validate_hook = quote! { - #ident(self, _root, _path, _report); + #hook_ident(self, _root, _path, _report); }; } } @@ -609,11 +805,28 @@ fn expand_validate(ast: &DeriveInput) -> TokenStream2 { let ident = &ast.ident; 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), @@ -1001,6 +1214,7 @@ fn expand_for_struct( is_extension = true; ext_renames.push(ident); } + FieldAttribute::Validate(_) => {} } } } 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/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/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/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/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/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/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/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/src/accessor/mod.rs b/src/accessor/mod.rs index cdfdfa7e..04ab6f4c 100644 --- a/src/accessor/mod.rs +++ b/src/accessor/mod.rs @@ -238,7 +238,7 @@ pub mod sparse { /// A typed view into a buffer view. #[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] -#[gltf(validate_hook = "accessor_validate_hook")] +#[gltf(validate = "validate_accessor")] pub struct Accessor { /// Specifies if the attribute is a scalar, vector, or matrix. #[serde(rename = "type")] @@ -284,7 +284,7 @@ pub struct Accessor { pub extras: Option, } -fn accessor_validate_hook(accessor: &Accessor, _root: &Root, path: P, report: &mut R) +fn validate_accessor(accessor: &Accessor, _root: &Root, path: P, report: &mut R) where P: Fn() -> Path, R: FnMut(&dyn Fn() -> Path, Error), diff --git a/src/accessor/util.rs b/src/accessor/util.rs index e5ef634c..f8f45a09 100644 --- a/src/accessor/util.rs +++ b/src/accessor/util.rs @@ -307,10 +307,7 @@ impl<'a, 's, T: Item> Iter<'s, T> { .as_ref() .and_then(|index| root.get(*index)) { - let stride = view - .stride - .map(|s| s.value()) - .unwrap_or(mem::size_of::()); + 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) @@ -328,7 +325,7 @@ impl<'a, 's, T: Item> Iter<'s, T> { let index_iter = { let view = root.get(indices.buffer_view)?; let index_size = indices.index_type.size(); - let stride = view.stride.map(|s| s.value()).unwrap_or(index_size); + let stride = view.stride.unwrap_or(index_size); let start = indices.byte_offset.value(); let end = start + stride * (sparse_count - 1) + index_size; @@ -350,10 +347,7 @@ impl<'a, 's, T: Item> Iter<'s, T> { let value_iter = { let view = root.get(values.buffer_view)?; - let stride = view - .stride - .map(|s| s.value()) - .unwrap_or(mem::size_of::()); + let stride = view.stride.unwrap_or(mem::size_of::()); let start = values.byte_offset.value(); let end = start + stride * (sparse_count - 1) + mem::size_of::(); @@ -376,10 +370,7 @@ impl<'a, 's, T: Item> Iter<'s, T> { .as_ref() .and_then(|index| root.get(*index)) .and_then(|view| { - let stride = view - .stride - .map(|s| s.value()) - .unwrap_or(mem::size_of::()); + let stride = view.stride.unwrap_or(mem::size_of::()); debug_assert!( stride >= mem::size_of::(), "Mismatch in stride, expected at least {} stride but found {}", diff --git a/src/buffer.rs b/src/buffer.rs index a0b59629..a50a2e17 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -2,10 +2,10 @@ use crate::validation::{Error, USize64, Validate}; use crate::{Extras, Index, Path, Root, UnrecognizedExtensions}; /// The minimum byte stride. -pub const MIN_BYTE_STRIDE: u8 = 4; +pub const MIN_BYTE_STRIDE: usize = 4; /// The maximum byte stride. -pub const MAX_BYTE_STRIDE: u8 = 252; +pub const MAX_BYTE_STRIDE: usize = 252; /// Specifies the target a GPU buffer should be bound to. #[derive( @@ -21,39 +21,6 @@ pub enum Target { } impl Validate for Target {} -/// Distance between individual items in a buffer view, measured in bytes. -#[derive( - Clone, - Copy, - Debug, - Default, - Eq, - Hash, - PartialEq, - serde_derive::Deserialize, - serde_derive::Serialize, -)] -pub struct Stride(pub u8); - -impl Stride { - /// Widens the value to `usize`. - pub fn value(&self) -> usize { - self.0 as 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, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] pub struct Buffer { @@ -89,14 +56,16 @@ pub struct View { pub length: USize64, /// Offset into the parent buffer in bytes. - #[serde(default, rename = "byteOffset")] + #[serde(rename = "byteOffset")] + #[gltf(default)] pub offset: USize64, /// The stride in bytes between vertex attributes or other interleavable data. /// /// When zero, data is assumed to be tightly packed. #[serde(rename = "byteStride")] - pub stride: Option, + #[gltf(validate = "validate_stride")] + pub stride: Option, /// Optional user-defined name for this object. pub name: Option, @@ -111,6 +80,18 @@ pub struct View { 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); + } + } +} + mod tests { #[test] fn serialize_target() { diff --git a/src/mesh/mod.rs b/src/mesh/mod.rs index 388bb359..49723827 100644 --- a/src/mesh/mod.rs +++ b/src/mesh/mod.rs @@ -129,7 +129,7 @@ pub struct Mesh { /// Geometry to be rendered with the given material. #[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] -#[gltf(validate_hook = "primitive_validate_hook")] +#[gltf(validate = "validate_primitive")] pub struct Primitive { /// Maps attribute semantic names to the `Accessor`s containing the /// corresponding attribute data. @@ -161,7 +161,7 @@ pub struct Primitive { pub variants: Option, } -fn primitive_validate_hook(primitive: &Primitive, root: &crate::Root, path: P, report: &mut R) +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), diff --git a/src/root.rs b/src/root.rs index d042c7b0..fa3ebf90 100644 --- a/src/root.rs +++ b/src/root.rs @@ -83,7 +83,7 @@ pub struct Index(u32, marker::PhantomData T>); #[derive( Clone, Debug, Default, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate, )] -#[gltf(validate_hook = "root_validate_hook")] +#[gltf(validate = "validate_root")] pub struct Root { /// An array of accessors. pub accessors: Vec, @@ -151,7 +151,7 @@ pub struct Root { 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), diff --git a/src/validation.rs b/src/validation.rs index 89e3805b..fbb79805 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -148,6 +148,7 @@ 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] {} From 344e17fb8b7a934c9c1a91cea8a4918fba3e7f21 Mon Sep 17 00:00:00 2001 From: Katie Macaulay Date: Mon, 20 Jan 2025 11:03:37 +0000 Subject: [PATCH 4/7] Add Stub trait --- examples/export/main.rs | 35 +++++---------------- glTF-Sample-Assets | 2 +- gltf-derive/src/lib.rs | 70 +++++++++++++++++++++++++++++++++++++++++ src/accessor/mod.rs | 55 ++++++++++++++++++++++++++++---- src/animation.rs | 31 +++++++++++++++--- src/buffer.rs | 27 ++++++++++++++-- src/camera.rs | 13 +++++++- src/image.rs | 9 +++++- src/lib.rs | 20 ++++++++++++ src/material.rs | 18 +++++++++-- src/mesh/mod.rs | 41 +++++++++++++++++++++--- src/root.rs | 7 ++++- src/scene.rs | 24 ++++++++++++-- src/skin.rs | 9 +++++- src/texture.rs | 18 +++++++++-- 15 files changed, 321 insertions(+), 58 deletions(-) diff --git a/examples/export/main.rs b/examples/export/main.rs index 19e587b3..5e54769f 100644 --- a/examples/export/main.rs +++ b/examples/export/main.rs @@ -1,4 +1,5 @@ use gltf::validation::USize64; +use gltf::Stub; use std::borrow::Cow; use std::io::Write; use std::{fs, mem}; @@ -73,24 +74,20 @@ fn export(output: Output) { let buffer_length = triangle_vertices.len() * mem::size_of::(); let buffer = root.push(gltf::Buffer { length: USize64::from(buffer_length), - name: None, uri: if output == Output::Standard { Some("buffer0.bin".into()) } else { None }, - extras: Default::default(), - unrecognized_extensions: Default::default(), + ..Stub::stub() }); let buffer_view = root.push(gltf::buffer::View { buffer, length: USize64::from(buffer_length), offset: USize64(0), stride: Some(mem::size_of::()), - name: None, target: Some(gltf::buffer::Target::ArrayBuffer), - extras: Default::default(), - unrecognized_extensions: Default::default(), + ..Stub::stub() }); let positions = root.push(gltf::Accessor { buffer_view: Some(buffer_view), @@ -100,11 +97,8 @@ fn export(output: Output) { attribute_type: gltf::accessor::AttributeType::Vec3, min: Some(gltf::Value::from(Vec::from(min))), max: Some(gltf::Value::from(Vec::from(max))), - name: None, normalized: false, - sparse: None, - extras: Default::default(), - unrecognized_extensions: Default::default(), + ..Stub::stub() }); let colors = root.push(gltf::Accessor { buffer_view: Some(buffer_view), @@ -112,13 +106,8 @@ fn export(output: Output) { count: USize64::from(triangle_vertices.len()), component_type: gltf::accessor::ComponentType::F32, attribute_type: gltf::accessor::AttributeType::Vec3, - min: None, - max: None, - name: None, normalized: false, - sparse: None, - extras: Default::default(), - unrecognized_extensions: Default::default(), + ..Stub::stub() }); let primitive = gltf::mesh::Primitive { @@ -130,18 +119,12 @@ fn export(output: Output) { indices: None, material: None, mode: gltf::mesh::Mode::Triangles, - targets: Vec::new(), - variants: None, - extras: Default::default(), - unrecognized_extensions: Default::default(), + ..Stub::stub() }; let mesh = root.push(gltf::Mesh { - name: None, primitives: vec![primitive], - weights: Vec::new(), - extras: Default::default(), - unrecognized_extensions: Default::default(), + ..Stub::stub() }); let node = root.push(gltf::Node { @@ -150,10 +133,8 @@ fn export(output: Output) { }); root.push(gltf::Scene { - name: None, nodes: vec![node], - extras: Default::default(), - unrecognized_extensions: Default::default(), + ..Stub::stub() }); match output { diff --git a/glTF-Sample-Assets b/glTF-Sample-Assets index 6ddacf27..b40880a4 160000 --- a/glTF-Sample-Assets +++ b/glTF-Sample-Assets @@ -1 +1 @@ -Subproject commit 6ddacf278993d1a0f0138a383a01fd1cf154b836 +Subproject commit b40880a4823deb041cce26fac4b55af662ceded3 diff --git a/gltf-derive/src/lib.rs b/gltf-derive/src/lib.rs index a6309a5a..aef11508 100644 --- a/gltf-derive/src/lib.rs +++ b/gltf-derive/src/lib.rs @@ -163,6 +163,76 @@ fn impl_default_for_struct(ident: &syn::Ident, data: &syn::DataStruct) -> TokenS } } +/// 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 diff --git a/src/accessor/mod.rs b/src/accessor/mod.rs index 04ab6f4c..2ff18290 100644 --- a/src/accessor/mod.rs +++ b/src/accessor/mod.rs @@ -65,7 +65,7 @@ pub mod util; #[doc(inline)] pub use self::util::{Item, Iter}; use crate::validation::{Error, USize64, Validate}; -use crate::{buffer, Extras, Index, Path, Root, UnrecognizedExtensions}; +use crate::{buffer, Extras, Index, Path, Root, Stub, UnrecognizedExtensions}; use serde_json::Value; /// The component data type. @@ -87,6 +87,7 @@ pub enum ComponentType { /// Corresponds to `GL_FLOAT`. F32 = 5126, } + impl Validate for ComponentType {} impl From for ComponentType { @@ -99,6 +100,12 @@ impl From for ComponentType { } } +impl Stub for ComponentType { + fn stub() -> Self { + Self::I8 + } +} + /// Specifies whether an attribute, vector, or matrix. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde_derive::Deserialize, serde_derive::Serialize)] pub enum AttributeType { @@ -124,12 +131,19 @@ pub enum AttributeType { #[serde(rename = "MAT4")] Mat4, } + impl Validate for AttributeType {} +impl Stub for AttributeType { + fn stub() -> Self { + Self::Scalar + } +} + /// Contains data structures for sparse storage. pub mod sparse { use crate::validation::{USize64, Validate}; - use crate::{buffer, Extras, Index, UnrecognizedExtensions}; + use crate::{buffer, Extras, Index, Stub, UnrecognizedExtensions}; /// Data type specific to sparse indices. #[derive( @@ -144,8 +158,15 @@ pub mod sparse { /// Corresponds to `GL_UNSIGNED_INT`. U32 = super::ComponentType::U32 as u32, } + impl Validate for IndexType {} + impl Stub for IndexType { + fn stub() -> Self { + Self::U8 + } + } + impl IndexType { /// Returns the number of bytes this value represents. pub fn size(self) -> usize { @@ -160,7 +181,12 @@ pub mod sparse { /// Indices of those attributes that deviate from their initialization value. #[derive( - Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate, + 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. @@ -186,7 +212,12 @@ pub mod sparse { /// Sparse storage of attributes that deviate from their initialization value. #[derive( - Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate, + 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. @@ -215,7 +246,12 @@ pub mod sparse { /// 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::Validate, + 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. @@ -237,7 +273,14 @@ pub mod sparse { } /// A typed view into a buffer view. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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. diff --git a/src/animation.rs b/src/animation.rs index 7e2ae2ce..a308d0ef 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -1,5 +1,5 @@ use crate::validation::{Error, Validate}; -use crate::{accessor, scene, Extras, Index, Path, Root, UnrecognizedExtensions}; +use crate::{accessor, scene, Extras, Index, Path, Root, Stub, UnrecognizedExtensions}; /// Specifies an interpolation algorithm. #[derive( @@ -52,10 +52,17 @@ pub enum Property { #[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)] +#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Stub)] pub struct Animation { /// Unrecognized extension data. pub unrecognized_extensions: UnrecognizedExtensions, @@ -78,7 +85,7 @@ pub struct Animation { } /// Targets an animation's sampler at a node's property. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize)] +#[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. @@ -95,7 +102,14 @@ pub struct Channel { } /// 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)] +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Validate, + gltf_derive::Stub, +)] pub struct Target { /// Unrecognized extension data. pub unrecognized_extensions: UnrecognizedExtensions, @@ -112,7 +126,14 @@ pub struct Target { } /// Defines a keyframe graph but not its target. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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, diff --git a/src/buffer.rs b/src/buffer.rs index a50a2e17..503e0c9d 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,5 +1,5 @@ use crate::validation::{Error, USize64, Validate}; -use crate::{Extras, Index, Path, Root, UnrecognizedExtensions}; +use crate::{Extras, Index, Path, Root, Stub, UnrecognizedExtensions}; /// The minimum byte stride. pub const MIN_BYTE_STRIDE: usize = 4; @@ -19,10 +19,24 @@ pub enum Target { /// Corresponds to `GL_ELEMENT_ARRAY_BUFFER`. ElementArrayBuffer = 34_963, } + impl Validate for Target {} +impl Stub for Target { + fn stub() -> Self { + Self::ArrayBuffer + } +} + /// A buffer points to binary data representing geometry, animations, or skins. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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. #[serde(rename = "byteLength")] @@ -46,7 +60,14 @@ pub struct Buffer { /// /// /// -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] pub struct View { /// The parent `Buffer`. pub buffer: Index, diff --git a/src/camera.rs b/src/camera.rs index 3fed059c..f718fefb 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,5 +1,5 @@ use crate::validation::{Error, Validate}; -use crate::{Extras, Path, Root, UnrecognizedExtensions}; +use crate::{Extras, Path, Root, Stub, UnrecognizedExtensions}; /// Projection matrix parameters. #[derive(Clone, Debug, serde_derive::Deserialize, serde_derive::Serialize, gltf_derive::Wrap)] @@ -17,6 +17,14 @@ pub enum Projection { }, } +impl Stub for Projection { + fn stub() -> Self { + Self::Perspective { + perspective: Stub::stub(), + } + } +} + /// A viewpoint in the scene. /// /// A node can reference a camera to apply a transform to place the camera in the @@ -26,6 +34,7 @@ pub enum Projection { Debug, gltf_derive::Deserialize, gltf_derive::Serialize, + gltf_derive::Stub, gltf_derive::Validate, gltf_derive::Wrap, )] @@ -51,6 +60,7 @@ pub struct Camera { Debug, gltf_derive::Deserialize, gltf_derive::Serialize, + gltf_derive::Stub, gltf_derive::Validate, gltf_derive::Wrap, )] @@ -80,6 +90,7 @@ pub struct Orthographic { Debug, gltf_derive::Deserialize, gltf_derive::Serialize, + gltf_derive::Stub, gltf_derive::Validate, gltf_derive::Wrap, )] diff --git a/src/image.rs b/src/image.rs index f67f375f..8cb6b9a6 100644 --- a/src/image.rs +++ b/src/image.rs @@ -5,7 +5,14 @@ use crate::{buffer, Extras, Index, UnrecognizedExtensions}; pub const VALID_MIME_TYPES: &[&str] = &["image/jpeg", "image/png"]; /// Image data used to create a texture. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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. diff --git a/src/lib.rs b/src/lib.rs index 8658da65..bb1866fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,6 +108,26 @@ pub type UnrecognizedExtensions = serde_json::Map; /// 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),*) => { $( diff --git a/src/material.rs b/src/material.rs index 2ff8c2fd..8bbb175a 100644 --- a/src/material.rs +++ b/src/material.rs @@ -435,7 +435,14 @@ pub struct PbrMetallicRoughness { } /// Defines the normal texture of a material. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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, @@ -458,7 +465,14 @@ pub struct NormalTexture { } /// Defines the occlusion texture of a material. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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, diff --git a/src/mesh/mod.rs b/src/mesh/mod.rs index 49723827..d3bc6234 100644 --- a/src/mesh/mod.rs +++ b/src/mesh/mod.rs @@ -15,7 +15,12 @@ pub mod khr_materials_variants { /// Identifies all material variants applicable to a particular primitive. #[derive( - Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate, + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, )] pub struct Variants { /// Applicable material variant mappings. @@ -30,7 +35,12 @@ pub mod khr_materials_variants { /// Identifies a single material variant applicable to a particular primitive. #[derive( - Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate, + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, )] pub struct Mapping { /// Base material index. @@ -109,7 +119,14 @@ impl Mode { /// /// 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::Validate)] +#[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. pub name: Option, @@ -128,7 +145,14 @@ pub struct Mesh { } /// Geometry to be rendered with the given material. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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 @@ -397,7 +421,14 @@ where } /// A dictionary mapping attributes to their deviations in the Morph Target. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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")] diff --git a/src/root.rs b/src/root.rs index fa3ebf90..62207ff8 100644 --- a/src/root.rs +++ b/src/root.rs @@ -37,7 +37,12 @@ pub mod khr_lights_punctual { 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::Validate, + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, )] pub struct Variant { /// The name of the material variant. diff --git a/src/scene.rs b/src/scene.rs index 5274977e..a48e21ad 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -7,7 +7,12 @@ pub mod khr_lights_punctual { /// Introduces a light source to a scene node. #[derive( - Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate, + 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. @@ -58,8 +63,14 @@ pub mod khr_lights_punctual { 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)] + #[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])] @@ -190,7 +201,14 @@ pub struct Node { } /// The root `Node`s of a scene. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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, diff --git a/src/skin.rs b/src/skin.rs index ce717959..56d1dc40 100644 --- a/src/skin.rs +++ b/src/skin.rs @@ -1,7 +1,14 @@ use crate::{accessor, scene, Extras, Index, UnrecognizedExtensions}; /// Joints and matrices defining a skin. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[derive( + Clone, + Debug, + gltf_derive::Deserialize, + gltf_derive::Serialize, + gltf_derive::Stub, + gltf_derive::Validate, +)] pub struct Skin { /// The index of the accessor containing the 4x4 inverse-bind matrices. /// diff --git a/src/texture.rs b/src/texture.rs index 3ebaa9fb..27147a17 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -163,7 +163,14 @@ pub struct Sampler { } /// A texture and its sampler. -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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. pub name: Option, @@ -181,7 +188,14 @@ pub struct Texture { pub extras: Option, } -#[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Validate)] +#[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. From e2f27a2ec2033b69481239572a5d8e42ee90532d Mon Sep 17 00:00:00 2001 From: Katie Macaulay Date: Mon, 20 Jan 2025 12:34:16 +0000 Subject: [PATCH 5/7] Make import optional --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index bb1866fc..c058bcfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ pub mod camera; pub mod image; /// Reference importer implementation. +#[cfg(feature = "import")] pub mod import; /// Material properties for rendering primitives. @@ -76,6 +77,7 @@ pub use camera::Camera; #[doc(inline)] pub use image::Image; #[doc(inline)] +#[cfg(feature = "import")] pub use import::{import, import_slice}; #[doc(inline)] pub use material::Material; From 7cc2a3c9f3b8986cc835ff210a557d6dd4fe36ec Mon Sep 17 00:00:00 2001 From: Katie Macaulay Date: Mon, 20 Jan 2025 14:49:44 +0000 Subject: [PATCH 6/7] Support KHR_animation_pointer --- src/animation.rs | 56 +++++++++++++++++++++++++++++++++++++----------- src/lib.rs | 1 + 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/animation.rs b/src/animation.rs index a308d0ef..a92c2759 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -1,6 +1,29 @@ 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, @@ -45,6 +68,9 @@ pub enum Property { /// 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, @@ -64,12 +90,6 @@ impl Stub for Property { /// A keyframe animation. #[derive(Clone, Debug, gltf_derive::Deserialize, gltf_derive::Serialize, gltf_derive::Stub)] pub struct Animation { - /// Unrecognized extension data. - pub unrecognized_extensions: UnrecognizedExtensions, - - /// Optional application specific data. - pub extras: Option, - /// An array of channels, each of which targets an animation's sampler at a /// node's property. /// @@ -82,6 +102,12 @@ pub struct Animation { /// 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. @@ -111,18 +137,22 @@ pub struct Channel { gltf_derive::Stub, )] pub struct Target { - /// Unrecognized extension data. - pub unrecognized_extensions: UnrecognizedExtensions, - - /// Optional application specific data. - pub extras: Option, - /// The index of the node to target. - pub node: Index, + 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. diff --git a/src/lib.rs b/src/lib.rs index c058bcfe..55cb1eba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -396,6 +396,7 @@ impl From> for Error { /// Names of glTF 2.0 extensions supported by the library. pub const SUPPORTED_EXTENSIONS: &[&str] = &[ + "KHR_animation_pointer", "KHR_lights_punctual", "KHR_materials_pbrSpecularGlossiness", "KHR_materials_unlit", From e3c16e2aeda34e3a133f3d3082dc468e5f020053 Mon Sep 17 00:00:00 2001 From: Katie Macaulay Date: Mon, 20 Jan 2025 15:12:46 +0000 Subject: [PATCH 7/7] Support KHR_texture_basisu --- src/lib.rs | 7 ++++--- src/texture.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 55cb1eba..695a13af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -398,10 +398,11 @@ impl From> for Error { 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_texture_transform", "KHR_materials_transmission", - "KHR_materials_ior", - "KHR_materials_emissive_strength", + "KHR_texture_basisu", + "KHR_texture_transform", ]; diff --git a/src/texture.rs b/src/texture.rs index 27147a17..8fd2cbe7 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -1,6 +1,29 @@ 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, + } +} + /// 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. @@ -179,7 +202,10 @@ pub struct Texture { pub sampler: Option>, /// The index of the image used by this texture. - pub source: Index, + pub source: Option>, + + /// The KTX v2 image with basis universal compression used by this texture. + pub basisu: Option, /// Unrecognized extension data. pub unrecognized_extensions: UnrecognizedExtensions,