├── rustfmt.toml ├── clippy.toml ├── examples ├── Box.glb ├── Box0.bin ├── display │ └── main.rs ├── roundtrip │ └── main.rs ├── tree │ └── main.rs ├── Box.gltf └── export │ └── main.rs ├── tests ├── box_sparse.bin ├── box_sparse.glb ├── minimal_accessor_min_max.gltf ├── roundtrip_binary_gltf.rs ├── import_sample_models.rs ├── box_sparse.gltf └── test_wrapper.rs ├── gltf-json ├── src │ ├── extensions │ │ ├── asset.rs │ │ ├── image.rs │ │ ├── skin.rs │ │ ├── buffer.rs │ │ ├── animation.rs │ │ ├── camera.rs │ │ ├── accessor.rs │ │ ├── mesh.rs │ │ ├── mod.rs │ │ ├── root.rs │ │ ├── texture.rs │ │ └── scene.rs │ ├── extras.rs │ ├── asset.rs │ ├── skin.rs │ ├── image.rs │ ├── path.rs │ ├── lib.rs │ ├── scene.rs │ ├── camera.rs │ ├── buffer.rs │ └── validation.rs ├── Cargo.toml ├── LICENSE-MIT └── tests │ ├── minimal_accessor_invalid.gltf │ ├── non_sparse_accessor_without_buffer_view.gltf │ └── test_validation.rs ├── CODE_OF_CONDUCT.md ├── .gitignore ├── gltf-derive ├── Cargo.toml ├── LICENSE-MIT └── src │ └── lib.rs ├── src ├── skin │ ├── util.rs │ ├── iter.rs │ └── mod.rs ├── khr_materials_variants.rs ├── scene │ └── iter.rs ├── mesh │ ├── util │ │ ├── joints.rs │ │ ├── indices.rs │ │ ├── weights.rs │ │ ├── tex_coords.rs │ │ └── mod.rs │ └── iter.rs ├── animation │ ├── iter.rs │ ├── util │ │ ├── rotations.rs │ │ ├── morph_target_weights.rs │ │ └── mod.rs │ └── mod.rs ├── accessor │ ├── sparse.rs │ └── mod.rs ├── khr_lights_punctual.rs ├── image.rs ├── buffer.rs └── camera.rs ├── .github └── workflows │ └── test.yml ├── LICENSE-MIT ├── CONTRIBUTING.md ├── Cargo.toml └── README.md /rustfmt.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | doc-valid-idents = ["glTF"] 2 | -------------------------------------------------------------------------------- /examples/Box.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gltf-rs/gltf/HEAD/examples/Box.glb -------------------------------------------------------------------------------- /examples/Box0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gltf-rs/gltf/HEAD/examples/Box0.bin -------------------------------------------------------------------------------- /tests/box_sparse.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gltf-rs/gltf/HEAD/tests/box_sparse.bin -------------------------------------------------------------------------------- /tests/box_sparse.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gltf-rs/gltf/HEAD/tests/box_sparse.glb -------------------------------------------------------------------------------- /gltf-json/src/extensions/asset.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | 4 | /// Metadata about the glTF asset. 5 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 6 | pub struct Asset {} 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Contributors are expected to abide by the [Rust code of conduct](https://www.rust-lang.org/en-US/conduct.html) and are encouraged to contact the author at david@harvey-macaulay.com should confidential moderation be required. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | Cargo.lock 3 | gltf-derive/target/ 4 | gltf-derive/Cargo.lock 5 | gltf-importer/target/ 6 | gltf-importer/Cargo.lock 7 | gltf-json/target/ 8 | gltf-json/Cargo.lock 9 | gltf-utils/target/ 10 | gltf-utils/Cargo.lock 11 | *.bk 12 | glTF-Sample-Models -------------------------------------------------------------------------------- /gltf-json/src/extensions/image.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | #[cfg(feature = "extensions")] 4 | use serde_json::{Map, Value}; 5 | 6 | /// Image data used to create a texture. 7 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 8 | pub struct Image { 9 | #[cfg(feature = "extensions")] 10 | #[serde(default, flatten)] 11 | pub others: Map, 12 | } 13 | -------------------------------------------------------------------------------- /gltf-json/src/extensions/skin.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | #[cfg(feature = "extensions")] 4 | use serde_json::{Map, Value}; 5 | 6 | /// Joints and matrices defining a skin. 7 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 8 | pub struct Skin { 9 | #[cfg(feature = "extensions")] 10 | #[serde(default, flatten)] 11 | pub others: Map, 12 | } 13 | -------------------------------------------------------------------------------- /gltf-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gltf-derive" 3 | version = "1.4.1" 4 | authors = ["David Harvey-Macaulay "] 5 | description = "Internal macros for the gltf crate" 6 | repository = "https://github.com/gltf-rs/gltf" 7 | license = "MIT OR Apache-2.0" 8 | edition = "2021" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | inflections = "1.1" 15 | proc-macro2 = "1" 16 | quote = "1" 17 | syn = "2" 18 | -------------------------------------------------------------------------------- /examples/display/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, io}; 2 | 3 | use std::boxed::Box; 4 | use std::error::Error as StdError; 5 | 6 | fn run(path: &str) -> Result<(), Box> { 7 | let file = fs::File::open(path)?; 8 | let reader = io::BufReader::new(file); 9 | let gltf = gltf::Gltf::from_reader(reader)?; 10 | println!("{:#?}", gltf); 11 | Ok(()) 12 | } 13 | 14 | fn main() { 15 | if let Some(path) = std::env::args().nth(1) { 16 | run(&path).expect("runtime error"); 17 | } else { 18 | println!("usage: gltf-display "); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/roundtrip/main.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, io}; 2 | 3 | use std::boxed::Box; 4 | use std::error::Error as StdError; 5 | 6 | fn run(path: &str) -> Result<(), Box> { 7 | let file = fs::File::open(path)?; 8 | let reader = io::BufReader::new(file); 9 | let gltf = gltf::Gltf::from_reader(reader)?; 10 | let json = gltf.document.into_json().to_string_pretty()?; 11 | println!("{}", json); 12 | Ok(()) 13 | } 14 | 15 | fn main() { 16 | if let Some(path) = std::env::args().nth(1) { 17 | run(&path).expect("runtime error"); 18 | } else { 19 | println!("usage: gltf-roundtrip "); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gltf-json/src/extensions/buffer.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | #[cfg(feature = "extensions")] 4 | use serde_json::{Map, Value}; 5 | 6 | /// A buffer points to binary data representing geometry, animations, or skins. 7 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 8 | pub struct Buffer { 9 | #[cfg(feature = "extensions")] 10 | #[serde(default, flatten)] 11 | pub others: Map, 12 | } 13 | 14 | /// A view into a buffer generally representing a subset of the buffer. 15 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 16 | pub struct View { 17 | #[cfg(feature = "extensions")] 18 | #[serde(default, flatten)] 19 | pub others: Map, 20 | } 21 | -------------------------------------------------------------------------------- /src/skin/util.rs: -------------------------------------------------------------------------------- 1 | use crate::accessor; 2 | 3 | use crate::{Buffer, Skin}; 4 | 5 | /// Inverse Bind Matrices of type `[[f32; 4]; 4]`. 6 | pub type ReadInverseBindMatrices<'a> = accessor::Iter<'a, [[f32; 4]; 4]>; 7 | 8 | /// Skin reader. 9 | #[derive(Clone, Debug)] 10 | pub struct Reader<'a, 's, F> 11 | where 12 | F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, 13 | { 14 | pub(crate) skin: Skin<'a>, 15 | pub(crate) get_buffer_data: F, 16 | } 17 | 18 | impl<'a, 's, F> Reader<'a, 's, F> 19 | where 20 | F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, 21 | { 22 | /// Returns an `Iterator` that reads the inverse bind matrices of 23 | /// the skin. 24 | pub fn read_inverse_bind_matrices(&self) -> Option> { 25 | self.skin 26 | .inverse_bind_matrices() 27 | .and_then(|accessor| accessor::Iter::new(accessor, self.get_buffer_data.clone())) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /gltf-json/src/extensions/animation.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | #[cfg(feature = "extensions")] 4 | use serde_json::{Map, Value}; 5 | 6 | /// A keyframe animation. 7 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 8 | pub struct Animation { 9 | #[cfg(feature = "extensions")] 10 | #[serde(default, flatten)] 11 | pub others: Map, 12 | } 13 | 14 | /// Targets an animation's sampler at a node's property. 15 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 16 | pub struct Channel {} 17 | 18 | /// The index of the node and TRS property that an animation channel targets. 19 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 20 | pub struct Target {} 21 | 22 | /// Defines a keyframe graph but not its target. 23 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 24 | pub struct Sampler {} 25 | -------------------------------------------------------------------------------- /gltf-json/src/extras.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | use std::fmt; 4 | 5 | #[cfg(feature = "extras")] 6 | pub use serde_json::value::RawValue; 7 | 8 | /// Data type of the `extras` attribute on all glTF objects. 9 | #[cfg(feature = "extras")] 10 | pub type Extras = Option<::std::boxed::Box>; 11 | 12 | /// Data type of the `extras` attribute on all glTF objects. 13 | #[cfg(not(feature = "extras"))] 14 | pub type Extras = Void; 15 | 16 | /// Type representing no user-defined data. 17 | #[derive(Clone, Default, Serialize, Deserialize, Validate)] 18 | pub struct Void { 19 | #[serde(default, skip_serializing)] 20 | pub(crate) _allow_unknown_fields: (), 21 | } 22 | 23 | impl fmt::Debug for Void { 24 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 25 | write!(f, "{{}}") 26 | } 27 | } 28 | 29 | impl fmt::Display for Void { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | write!(f, "{{}}") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: pull_request 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | test: 10 | name: Run test 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | - name: Checkout glTF Sample Assets 18 | uses: actions/checkout@v2 19 | with: 20 | repository: KhronosGroup/glTF-Sample-Assets 21 | path: glTF-Sample-Assets 22 | 23 | - name: Tests (minimal features) 24 | run: cargo test --all --release --features allow_empty_texture,allow_empty_animation_target_node -- --nocapture 25 | 26 | - name: Tests (all features) 27 | run: cargo test --all --all-features --release -- --nocapture 28 | 29 | - name: Formatting 30 | run: cargo fmt --all -- --check 31 | 32 | - name: Clippy 33 | run: cargo clippy --all-features 34 | 35 | - name: Semantic versioning checks 36 | uses: obi1kenobi/cargo-semver-checks-action@v2 37 | -------------------------------------------------------------------------------- /gltf-json/src/extensions/camera.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | #[cfg(feature = "extensions")] 4 | use serde_json::{Map, Value}; 5 | 6 | /// A camera's projection. 7 | /// 8 | /// A node can reference a camera to apply a transform to place the camera in the 9 | /// scene. 10 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 11 | pub struct Camera { 12 | #[cfg(feature = "extensions")] 13 | #[serde(default, flatten)] 14 | pub others: Map, 15 | } 16 | 17 | /// Values for an orthographic camera. 18 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 19 | pub struct Orthographic { 20 | #[cfg(feature = "extensions")] 21 | #[serde(default, flatten)] 22 | pub others: Map, 23 | } 24 | 25 | /// Values for a perspective camera. 26 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 27 | pub struct Perspective { 28 | #[cfg(feature = "extensions")] 29 | #[serde(default, flatten)] 30 | pub others: Map, 31 | } 32 | -------------------------------------------------------------------------------- /gltf-json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gltf-json" 3 | version = "1.4.1" 4 | authors = ["David Harvey-Macaulay "] 5 | description = "JSON parsing for the gltf crate" 6 | repository = "https://github.com/gltf-rs/gltf" 7 | license = "MIT OR Apache-2.0" 8 | edition = "2021" 9 | rust-version = "1.61" 10 | 11 | [dependencies] 12 | gltf-derive = { path = "../gltf-derive", version = "=1.4.1" } 13 | serde = "1.0" 14 | serde_derive = "1.0" 15 | serde_json = { features = ["raw_value"], version = "1.0" } 16 | 17 | [features] 18 | default = [] 19 | allow_empty_animation_target_node = [] 20 | allow_empty_texture = [] 21 | names = [] 22 | extensions = [] 23 | extras = [] 24 | KHR_lights_punctual = [] 25 | KHR_materials_ior = [] 26 | KHR_materials_pbrSpecularGlossiness = [] 27 | KHR_materials_specular = [] 28 | KHR_materials_sheen = [] 29 | KHR_materials_clearcoat = [] 30 | KHR_materials_transmission = [] 31 | KHR_materials_unlit = [] 32 | KHR_materials_variants = [] 33 | KHR_materials_volume = [] 34 | KHR_texture_transform = [] 35 | KHR_materials_emissive_strength = [] 36 | EXT_texture_webp = [] 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 The gltf Library Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | 27 | -------------------------------------------------------------------------------- /gltf-json/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 The gltf Library Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | 27 | -------------------------------------------------------------------------------- /gltf-json/src/extensions/accessor.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | #[cfg(feature = "extensions")] 4 | use serde_json::{Map, Value}; 5 | 6 | /// Contains data structures for sparse storage. 7 | pub mod sparse { 8 | use super::*; 9 | 10 | /// Indices of those attributes that deviate from their initialization value. 11 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 12 | pub struct Indices {} 13 | 14 | /// Sparse storage of attributes that deviate from their initialization value. 15 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 16 | pub struct Sparse {} 17 | 18 | /// Array of size `count * number_of_components` storing the displaced 19 | /// accessor attributes pointed by `accessor::sparse::Indices`. 20 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 21 | pub struct Values {} 22 | } 23 | 24 | /// A typed view into a buffer view. 25 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 26 | pub struct Accessor { 27 | #[cfg(feature = "extensions")] 28 | #[serde(default, flatten)] 29 | pub others: Map, 30 | } 31 | -------------------------------------------------------------------------------- /gltf-json/tests/minimal_accessor_invalid.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "scenes" : [ { "nodes" : [ 0 ] } ], 3 | "nodes" : [ { "mesh" : 0 } ], 4 | "meshes" : [ 5 | { 6 | "primitives" : [ { 7 | "attributes" : { "POSITION" : 1 }, 8 | "indices" : 0 9 | } ] 10 | } 11 | ], 12 | "buffers" : [ 13 | { 14 | "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=", 15 | "byteLength" : 44 16 | } 17 | ], 18 | "bufferViews" : [ 19 | { 20 | "buffer" : 0, 21 | "byteOffset" : 0, 22 | "byteLength" : 6, 23 | "target" : 34963 24 | }, 25 | { 26 | "buffer" : 0, 27 | "byteOffset" : 8, 28 | "byteLength" : 36, 29 | "target" : 34962 30 | } 31 | ], 32 | "accessors" : [ 33 | { 34 | "bufferView" : 0, 35 | "byteOffset" : 0, 36 | "componentType" : 5123, 37 | "count" : 3, 38 | "type" : "SCALAR" 39 | }, 40 | { 41 | "bufferView" : 1, 42 | "byteOffset" : 0, 43 | "componentType" : 5126, 44 | "count" : 3, 45 | "type" : "VEC3", 46 | "max" : [ 1.0, 1.0 ] 47 | } 48 | ], 49 | 50 | "asset" : { 51 | "version" : "2.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/tree/main.rs: -------------------------------------------------------------------------------- 1 | use std::boxed::Box; 2 | use std::error::Error as StdError; 3 | use std::{fs, io}; 4 | 5 | fn print_tree(node: &gltf::Node, depth: i32) { 6 | for _ in 0..(depth - 1) { 7 | print!(" "); 8 | } 9 | print!(" -"); 10 | print!(" Node {}", node.index()); 11 | #[cfg(feature = "names")] 12 | print!(" ({})", node.name().unwrap_or("")); 13 | println!(); 14 | 15 | for child in node.children() { 16 | print_tree(&child, depth + 1); 17 | } 18 | } 19 | 20 | fn run(path: &str) -> Result<(), Box> { 21 | let file = fs::File::open(path)?; 22 | let reader = io::BufReader::new(file); 23 | let gltf = gltf::Gltf::from_reader(reader)?; 24 | for scene in gltf.scenes() { 25 | print!("Scene {}", scene.index()); 26 | #[cfg(feature = "names")] 27 | print!(" ({})", scene.name().unwrap_or("")); 28 | println!(); 29 | for node in scene.nodes() { 30 | print_tree(&node, 1); 31 | } 32 | } 33 | Ok(()) 34 | } 35 | 36 | fn main() { 37 | if let Some(path) = std::env::args().nth(1) { 38 | run(&path).expect("runtime error"); 39 | } else { 40 | println!("usage: gltf-tree "); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /gltf-json/tests/non_sparse_accessor_without_buffer_view.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "scenes" : [ { "nodes" : [ 0 ] } ], 3 | "nodes" : [ { "mesh" : 0 } ], 4 | "meshes" : [ 5 | { 6 | "primitives" : [ { 7 | "attributes" : { "POSITION" : 1 }, 8 | "indices" : 0 9 | } ] 10 | } 11 | ], 12 | "buffers" : [ 13 | { 14 | "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=", 15 | "byteLength" : 44 16 | } 17 | ], 18 | "bufferViews" : [ 19 | { 20 | "buffer" : 0, 21 | "byteOffset" : 0, 22 | "byteLength" : 6, 23 | "target" : 34963 24 | }, 25 | { 26 | "buffer" : 0, 27 | "byteOffset" : 8, 28 | "byteLength" : 36, 29 | "target" : 34962 30 | } 31 | ], 32 | "accessors" : [ 33 | { 34 | "byteOffset" : 0, 35 | "componentType" : 5123, 36 | "count" : 3, 37 | "type" : "SCALAR" 38 | }, 39 | { 40 | "bufferView" : 1, 41 | "byteOffset" : 0, 42 | "componentType" : 5126, 43 | "count" : 3, 44 | "type" : "VEC3", 45 | "min" : [ 0.0, 0.0, 0.0 ], 46 | "max" : [ 1.0, 1.0, 1.0 ] 47 | } 48 | ], 49 | 50 | "asset" : { 51 | "version" : "2.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/minimal_accessor_min_max.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "scenes" : [ { "nodes" : [ 0 ] } ], 3 | "nodes" : [ { "mesh" : 0 } ], 4 | "meshes" : [ 5 | { 6 | "primitives" : [ { 7 | "attributes" : { "POSITION" : 1 }, 8 | "indices" : 0 9 | } ] 10 | } 11 | ], 12 | "buffers" : [ 13 | { 14 | "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=", 15 | "byteLength" : 44 16 | } 17 | ], 18 | "bufferViews" : [ 19 | { 20 | "buffer" : 0, 21 | "byteOffset" : 0, 22 | "byteLength" : 6, 23 | "target" : 34963 24 | }, 25 | { 26 | "buffer" : 0, 27 | "byteOffset" : 8, 28 | "byteLength" : 36, 29 | "target" : 34962 30 | } 31 | ], 32 | "accessors" : [ 33 | { 34 | "bufferView" : 0, 35 | "byteOffset" : 0, 36 | "componentType" : 5123, 37 | "count" : 3, 38 | "type" : "SCALAR" 39 | }, 40 | { 41 | "bufferView" : 1, 42 | "byteOffset" : 0, 43 | "componentType" : 5126, 44 | "count" : 3, 45 | "type" : "VEC3", 46 | "max" : [ 1.0, 1.01, 0.02 ], 47 | "min" : [ -0.03, -0.04, -0.05 ] 48 | } 49 | ], 50 | 51 | "asset" : { 52 | "version" : "2.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/skin/iter.rs: -------------------------------------------------------------------------------- 1 | use std::slice; 2 | 3 | use crate::{Document, Node}; 4 | 5 | /// An `Iterator` that visits the joints of a `Skin`. 6 | #[derive(Clone, Debug)] 7 | pub struct Joints<'a> { 8 | /// The parent `Document` struct. 9 | pub(crate) document: &'a Document, 10 | 11 | /// The internal node index iterator. 12 | pub(crate) iter: slice::Iter<'a, json::Index>, 13 | } 14 | 15 | impl<'a> ExactSizeIterator for Joints<'a> {} 16 | impl<'a> Iterator for Joints<'a> { 17 | type Item = Node<'a>; 18 | fn next(&mut self) -> Option { 19 | self.iter 20 | .next() 21 | .map(|index| self.document.nodes().nth(index.value()).unwrap()) 22 | } 23 | fn size_hint(&self) -> (usize, Option) { 24 | self.iter.size_hint() 25 | } 26 | fn count(self) -> usize { 27 | self.iter.count() 28 | } 29 | fn last(self) -> Option { 30 | let document = self.document; 31 | self.iter 32 | .last() 33 | .map(|index| document.nodes().nth(index.value()).unwrap()) 34 | } 35 | fn nth(&mut self, n: usize) -> Option { 36 | self.iter 37 | .nth(n) 38 | .map(|index| self.document.nodes().nth(index.value()).unwrap()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /gltf-derive/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | 2 | `gltf_derive` was adapted from `validator_derive` (https://github.com/Keats/validator) 3 | which has the following license: 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2016 Vincent Prouillet 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | -------------------------------------------------------------------------------- /gltf-json/src/extensions/mesh.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | #[cfg(feature = "extensions")] 4 | use serde_json::{Map, Value}; 5 | 6 | /// A set of primitives to be rendered. 7 | /// 8 | /// A node can contain one or more meshes and its transform places the meshes in 9 | /// the scene. 10 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 11 | pub struct Mesh { 12 | #[cfg(feature = "extensions")] 13 | #[serde(default, flatten)] 14 | pub others: Map, 15 | } 16 | 17 | /// Geometry to be rendered with the given material. 18 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 19 | pub struct Primitive { 20 | #[cfg(feature = "KHR_materials_variants")] 21 | #[serde( 22 | default, 23 | rename = "KHR_materials_variants", 24 | skip_serializing_if = "Option::is_none" 25 | )] 26 | pub khr_materials_variants: Option, 27 | #[cfg(feature = "extensions")] 28 | #[serde(default, flatten)] 29 | pub others: Map, 30 | } 31 | 32 | #[cfg(feature = "KHR_materials_variants")] 33 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 34 | pub struct KhrMaterialsVariants { 35 | pub mappings: Vec, 36 | } 37 | 38 | #[cfg(feature = "KHR_materials_variants")] 39 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 40 | pub struct Mapping { 41 | pub material: u32, 42 | pub variants: Vec, 43 | } 44 | -------------------------------------------------------------------------------- /gltf-json/tests/test_validation.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, io}; 2 | 3 | use gltf_json::validation::{Error, Validate}; 4 | use gltf_json::Path; 5 | 6 | fn import_json(filename: &str) -> gltf_json::Root { 7 | let file = fs::File::open(filename).unwrap(); 8 | let reader = io::BufReader::new(file); 9 | gltf_json::Root::from_reader(reader).unwrap() 10 | } 11 | 12 | #[test] 13 | fn test_accessor_bounds_validate() { 14 | // file with missing min/max values 15 | let json = import_json("tests/minimal_accessor_invalid.gltf"); 16 | let mut errs = vec![]; 17 | json.validate(&json, gltf_json::Path::new, &mut |path, err| { 18 | errs.push((path(), err)) 19 | }); 20 | assert_eq!( 21 | errs, 22 | [ 23 | ( 24 | Path("meshes[0].primitives[0].attributes[\"POSITION\"].min".into()), 25 | Error::Missing 26 | ), 27 | ( 28 | Path("meshes[0].primitives[0].attributes[\"POSITION\"].max".into()), 29 | Error::Invalid 30 | ) 31 | ] 32 | ); 33 | } 34 | 35 | #[test] 36 | fn test_non_sparse_accessor_without_buffer_view_validate() { 37 | let json = import_json("tests/non_sparse_accessor_without_buffer_view.gltf"); 38 | let mut errs = vec![]; 39 | json.validate(&json, gltf_json::Path::new, &mut |path, err| { 40 | errs.push((path(), err)) 41 | }); 42 | assert_eq!( 43 | errs, 44 | [(Path("accessors[0].bufferView".into()), Error::Missing)] 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /gltf-json/src/asset.rs: -------------------------------------------------------------------------------- 1 | use crate::{extensions, Extras}; 2 | use gltf_derive::Validate; 3 | use serde_derive::{Deserialize, Serialize}; 4 | 5 | /// Metadata about the glTF asset. 6 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 7 | pub struct Asset { 8 | /// A copyright message suitable for display to credit the content creator. 9 | #[serde(skip_serializing_if = "Option::is_none")] 10 | pub copyright: Option, 11 | 12 | /// Extension specific data. 13 | #[serde(default, skip_serializing_if = "Option::is_none")] 14 | pub extensions: Option, 15 | 16 | /// Optional application specific data. 17 | #[serde(default)] 18 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 19 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 20 | pub extras: Extras, 21 | 22 | /// Tool that generated this glTF model. 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | pub generator: Option, 25 | 26 | /// The minimum glTF version that this asset targets. 27 | #[serde(rename = "minVersion")] 28 | #[serde(skip_serializing_if = "Option::is_none")] 29 | pub min_version: Option, 30 | 31 | /// The glTF version of this asset. 32 | pub version: String, 33 | } 34 | 35 | impl Default for Asset { 36 | fn default() -> Self { 37 | Self { 38 | copyright: None, 39 | extensions: Default::default(), 40 | extras: Default::default(), 41 | generator: None, 42 | min_version: None, 43 | version: "2.0".to_string(), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/khr_materials_variants.rs: -------------------------------------------------------------------------------- 1 | use crate::{Document, Material}; 2 | 3 | /// A variant. 4 | pub struct Variant<'a> { 5 | /// The parent `Document` struct. 6 | #[allow(dead_code)] 7 | document: &'a Document, 8 | 9 | /// The corresponding JSON index. 10 | #[allow(dead_code)] 11 | index: usize, 12 | 13 | /// The corresponding JSON struct. 14 | json: &'a json::extensions::scene::khr_materials_variants::Variant, 15 | } 16 | 17 | impl<'a> Variant<'a> { 18 | /// Constructs a `Variant`. 19 | pub(crate) fn new( 20 | document: &'a Document, 21 | index: usize, 22 | json: &'a json::extensions::scene::khr_materials_variants::Variant, 23 | ) -> Self { 24 | Self { 25 | document, 26 | index, 27 | json, 28 | } 29 | } 30 | 31 | /// Name of the variant. 32 | pub fn name(&self) -> &'a str { 33 | &self.json.name 34 | } 35 | } 36 | 37 | /// A mapping. 38 | pub struct Mapping<'a> { 39 | /// The parent `Document` struct. 40 | document: &'a Document, 41 | 42 | /// The corresponding JSON struct. 43 | json: &'a json::extensions::mesh::Mapping, 44 | } 45 | 46 | impl<'a> Mapping<'a> { 47 | /// Constructs a `Mapping`. 48 | pub(crate) fn new(document: &'a Document, json: &'a json::extensions::mesh::Mapping) -> Self { 49 | Self { document, json } 50 | } 51 | 52 | /// Get the variant indices that use this material. 53 | pub fn variants(&self) -> &'a [u32] { 54 | &self.json.variants 55 | } 56 | 57 | /// Get the corresponding material. 58 | pub fn material(&self) -> Material<'a> { 59 | self.document 60 | .materials() 61 | .nth(self.json.material as usize) 62 | .unwrap_or_else(|| Material::default(self.document)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guide 2 | 3 | Thank for you taking the time to contribute to `gltf`. Your contributions are valued, no matter how small. 4 | 5 | ## Pull requests 6 | 7 | * Please submit all pull requests to the `master` branch. 8 | * Please format your patches with `rustfmt` to match the project's code style. 9 | * Please note your changes in the project's CHANGELOG.md file under the 'Unreleased' section. 10 | 11 | ## Contribution opportunities 12 | 13 | * Create or discuss issues for bugs and new features. 14 | * Improve the crate documentation. 15 | * Submit pull requests that implement new features, fix bugs, or improve ergonomics. 16 | * Participate in code reviews. 17 | * Improve formatting of code. 18 | 19 | ## Guidelines for API design and code style 20 | 21 | * We aim to follow the [Rust API guidelines](https://github.com/rust-lang-nursery/api-guidelines). 22 | * We aim to leave no blank spaces in generated documentation. 23 | * We use 4 space indentation with the [new Rust style guide](https://github.com/rust-lang-nursery/fmt-rfcs/blob/master/guide/guide.md). 24 | 25 | ## Code of conduct 26 | 27 | Contributors are expected to abide by the [Rust code of conduct](https://www.rust-lang.org/en-US/conduct.html) and are encouraged to contact the author at alteous@outlook.com should confidential moderation be required. 28 | 29 | ## License agreement 30 | 31 | Licensed under either of 32 | 33 | * Apache License, Version 2.0 34 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 35 | * MIT license 36 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 37 | 38 | at your option. 39 | 40 | Unless you explicitly state otherwise, any contribution intentionally submitted 41 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 42 | dual licensed as above, without any additional terms or conditions. 43 | -------------------------------------------------------------------------------- /gltf-json/src/skin.rs: -------------------------------------------------------------------------------- 1 | use crate::{accessor, extensions, scene, Extras, Index}; 2 | use gltf_derive::Validate; 3 | use serde_derive::{Deserialize, Serialize}; 4 | 5 | /// Joints and matrices defining a skin. 6 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 7 | pub struct Skin { 8 | /// Extension specific data. 9 | #[serde(default, skip_serializing_if = "Option::is_none")] 10 | pub extensions: Option, 11 | 12 | /// Optional application specific data. 13 | #[serde(default)] 14 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 15 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 16 | pub extras: Extras, 17 | 18 | /// The index of the accessor containing the 4x4 inverse-bind matrices. 19 | /// 20 | /// When `None`,each matrix is assumed to be the 4x4 identity matrix 21 | /// which implies that the inverse-bind matrices were pre-applied. 22 | #[serde(rename = "inverseBindMatrices")] 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | pub inverse_bind_matrices: Option>, 25 | 26 | /// Indices of skeleton nodes used as joints in this skin. 27 | /// 28 | /// The array length must be the same as the `count` property of the 29 | /// `inverse_bind_matrices` `Accessor` (when defined). 30 | #[serde(skip_serializing_if = "Vec::is_empty")] 31 | pub joints: Vec>, 32 | 33 | /// Optional user-defined name for this object. 34 | #[cfg(feature = "names")] 35 | #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] 36 | pub name: Option, 37 | 38 | /// The index of the node used as a skeleton root. 39 | /// 40 | /// When `None`, joints transforms resolve to scene root. 41 | #[serde(skip_serializing_if = "Option::is_none")] 42 | pub skeleton: Option>, 43 | } 44 | -------------------------------------------------------------------------------- /src/scene/iter.rs: -------------------------------------------------------------------------------- 1 | use std::slice; 2 | 3 | use crate::{Document, Node}; 4 | 5 | /// An `Iterator` that visits the nodes in a scene. 6 | #[derive(Clone, Debug)] 7 | pub struct Nodes<'a> { 8 | /// The parent `Document` struct. 9 | pub(crate) document: &'a Document, 10 | 11 | /// The internal node index iterator. 12 | pub(crate) iter: slice::Iter<'a, json::Index>, 13 | } 14 | 15 | /// An `Iterator` that visits the children of a node. 16 | #[derive(Clone, Debug)] 17 | pub struct Children<'a> { 18 | /// The parent `Document` struct. 19 | pub(crate) document: &'a Document, 20 | 21 | /// The internal node index iterator. 22 | pub(crate) iter: slice::Iter<'a, json::Index>, 23 | } 24 | 25 | impl<'a> ExactSizeIterator for Nodes<'a> {} 26 | impl<'a> Iterator for Nodes<'a> { 27 | type Item = Node<'a>; 28 | fn next(&mut self) -> Option { 29 | self.iter 30 | .next() 31 | .map(|index| self.document.nodes().nth(index.value()).unwrap()) 32 | } 33 | 34 | fn size_hint(&self) -> (usize, Option) { 35 | self.iter.size_hint() 36 | } 37 | } 38 | 39 | impl<'a> ExactSizeIterator for Children<'a> {} 40 | impl<'a> Iterator for Children<'a> { 41 | type Item = Node<'a>; 42 | fn next(&mut self) -> Option { 43 | self.iter 44 | .next() 45 | .map(|index| self.document.nodes().nth(index.value()).unwrap()) 46 | } 47 | fn size_hint(&self) -> (usize, Option) { 48 | self.iter.size_hint() 49 | } 50 | fn count(self) -> usize { 51 | self.iter.count() 52 | } 53 | fn last(self) -> Option { 54 | let document = self.document; 55 | self.iter 56 | .last() 57 | .map(|index| document.nodes().nth(index.value()).unwrap()) 58 | } 59 | fn nth(&mut self, n: usize) -> Option { 60 | self.iter 61 | .nth(n) 62 | .map(|index| self.document.nodes().nth(index.value()).unwrap()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /gltf-json/src/image.rs: -------------------------------------------------------------------------------- 1 | use crate::validation::Validate; 2 | use crate::{buffer, extensions, Extras, Index}; 3 | use gltf_derive::Validate; 4 | use serde_derive::{Deserialize, Serialize}; 5 | 6 | /// All valid MIME types. 7 | pub const VALID_MIME_TYPES: &[&str] = &[ 8 | "image/jpeg", 9 | "image/png", 10 | #[cfg(feature = "EXT_texture_webp")] 11 | "image/webp", 12 | ]; 13 | 14 | /// Image data used to create a texture. 15 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 16 | pub struct Image { 17 | /// The index of the buffer view that contains the image. Use this instead of 18 | /// the image's uri property. 19 | #[serde(rename = "bufferView")] 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub buffer_view: Option>, 22 | 23 | /// The image's MIME type. 24 | #[serde(rename = "mimeType")] 25 | #[serde(skip_serializing_if = "Option::is_none")] 26 | pub mime_type: Option, 27 | 28 | /// Optional user-defined name for this object. 29 | #[cfg(feature = "names")] 30 | #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] 31 | pub name: Option, 32 | 33 | /// The uri of the image. Relative paths are relative to the .gltf file. 34 | /// Instead of referencing an external file, the uri can also be a data-uri. 35 | /// The image format must be jpg or png. 36 | #[serde(skip_serializing_if = "Option::is_none")] 37 | pub uri: Option, 38 | 39 | /// Extension specific data. 40 | #[serde(default, skip_serializing_if = "Option::is_none")] 41 | pub extensions: Option, 42 | 43 | /// Optional application specific data. 44 | #[serde(default)] 45 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 46 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 47 | pub extras: Extras, 48 | } 49 | 50 | /// An image MIME type. 51 | #[derive(Clone, Debug, Deserialize, Serialize)] 52 | pub struct MimeType(pub String); 53 | 54 | impl Validate for MimeType {} 55 | -------------------------------------------------------------------------------- /src/mesh/util/joints.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use super::ReadJoints; 4 | 5 | /// Casting iterator for `Joints`. 6 | #[derive(Clone, Debug)] 7 | pub struct CastingIter<'a, T>(ReadJoints<'a>, PhantomData); 8 | 9 | /// Type which describes how to cast any joint into u16. 10 | #[derive(Clone, Debug)] 11 | pub struct U16; 12 | 13 | /// Trait for types which describe casting behaviour. 14 | pub trait Cast { 15 | /// Output type. 16 | type Output; 17 | 18 | /// Cast from u8. 19 | fn cast_u8(x: [u8; 4]) -> Self::Output; 20 | 21 | /// Cast from u16. 22 | fn cast_u16(x: [u16; 4]) -> Self::Output; 23 | } 24 | 25 | impl<'a, A> CastingIter<'a, A> { 26 | pub(crate) fn new(iter: ReadJoints<'a>) -> Self { 27 | CastingIter(iter, PhantomData) 28 | } 29 | 30 | /// Unwrap underlying `Joints` object. 31 | pub fn unwrap(self) -> ReadJoints<'a> { 32 | self.0 33 | } 34 | } 35 | 36 | impl<'a, A: Cast> ExactSizeIterator for CastingIter<'a, A> {} 37 | impl<'a, A: Cast> Iterator for CastingIter<'a, A> { 38 | type Item = A::Output; 39 | 40 | #[inline] 41 | fn next(&mut self) -> Option { 42 | match self.0 { 43 | ReadJoints::U8(ref mut i) => i.next().map(A::cast_u8), 44 | ReadJoints::U16(ref mut i) => i.next().map(A::cast_u16), 45 | } 46 | } 47 | 48 | #[inline] 49 | fn nth(&mut self, x: usize) -> Option { 50 | match self.0 { 51 | ReadJoints::U8(ref mut i) => i.nth(x).map(A::cast_u8), 52 | ReadJoints::U16(ref mut i) => i.nth(x).map(A::cast_u16), 53 | } 54 | } 55 | 56 | fn last(self) -> Option { 57 | match self.0 { 58 | ReadJoints::U8(i) => i.last().map(A::cast_u8), 59 | ReadJoints::U16(i) => i.last().map(A::cast_u16), 60 | } 61 | } 62 | 63 | fn count(self) -> usize { 64 | self.size_hint().0 65 | } 66 | 67 | #[inline] 68 | fn size_hint(&self) -> (usize, Option) { 69 | match self.0 { 70 | ReadJoints::U8(ref i) => i.size_hint(), 71 | ReadJoints::U16(ref i) => i.size_hint(), 72 | } 73 | } 74 | } 75 | 76 | impl Cast for U16 { 77 | type Output = [u16; 4]; 78 | 79 | fn cast_u8(x: [u8; 4]) -> Self::Output { 80 | [x[0] as u16, x[1] as u16, x[2] as u16, x[3] as u16] 81 | } 82 | 83 | fn cast_u16(x: [u16; 4]) -> Self::Output { 84 | x 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/animation/iter.rs: -------------------------------------------------------------------------------- 1 | use std::{iter, slice}; 2 | 3 | use crate::animation::{Animation, Channel, Sampler}; 4 | 5 | /// An `Iterator` that visits the channels of an animation. 6 | #[derive(Clone, Debug)] 7 | pub struct Channels<'a> { 8 | /// The parent `Animation` struct. 9 | pub(crate) anim: Animation<'a>, 10 | 11 | /// The internal channel iterator. 12 | pub(crate) iter: iter::Enumerate>, 13 | } 14 | 15 | /// An `Iterator` that visits the samplers of an animation. 16 | #[derive(Clone, Debug)] 17 | pub struct Samplers<'a> { 18 | /// The parent `Channel` struct. 19 | pub(crate) anim: Animation<'a>, 20 | 21 | /// The internal channel iterator. 22 | pub(crate) iter: iter::Enumerate>, 23 | } 24 | 25 | impl<'a> Iterator for Channels<'a> { 26 | type Item = Channel<'a>; 27 | fn next(&mut self) -> Option { 28 | self.iter 29 | .next() 30 | .map(|(index, json)| Channel::new(self.anim.clone(), json, index)) 31 | } 32 | fn size_hint(&self) -> (usize, Option) { 33 | self.iter.size_hint() 34 | } 35 | fn count(self) -> usize { 36 | self.iter.count() 37 | } 38 | fn last(self) -> Option { 39 | let anim = self.anim; 40 | self.iter 41 | .last() 42 | .map(|(index, json)| Channel::new(anim, json, index)) 43 | } 44 | fn nth(&mut self, n: usize) -> Option { 45 | self.iter 46 | .nth(n) 47 | .map(|(index, json)| Channel::new(self.anim.clone(), json, index)) 48 | } 49 | } 50 | 51 | impl<'a> Iterator for Samplers<'a> { 52 | type Item = Sampler<'a>; 53 | fn next(&mut self) -> Option { 54 | self.iter 55 | .next() 56 | .map(|(index, json)| Sampler::new(self.anim.clone(), json, index)) 57 | } 58 | fn size_hint(&self) -> (usize, Option) { 59 | self.iter.size_hint() 60 | } 61 | fn count(self) -> usize { 62 | self.iter.count() 63 | } 64 | fn last(self) -> Option { 65 | let anim = self.anim; 66 | self.iter 67 | .last() 68 | .map(|(index, json)| Sampler::new(anim, json, index)) 69 | } 70 | fn nth(&mut self, n: usize) -> Option { 71 | self.iter 72 | .nth(n) 73 | .map(|(index, json)| Sampler::new(self.anim.clone(), json, index)) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gltf-json/src/extensions/mod.rs: -------------------------------------------------------------------------------- 1 | /// Contains `Accessor` and other related data structures. 2 | pub mod accessor; 3 | 4 | /// Contains `Animation` and other related data structures. 5 | pub mod animation; 6 | 7 | /// Contains `Asset` metadata. 8 | pub mod asset; 9 | 10 | /// Contains `Buffer`, `View`, and other related data structures. 11 | pub mod buffer; 12 | 13 | /// Contains `Camera` and other related data structures. 14 | pub mod camera; 15 | 16 | /// Contains `Image` and other related data structures. 17 | pub mod image; 18 | 19 | /// Contains `Material` and other related data structures. 20 | pub mod material; 21 | 22 | /// Contains `Mesh` and other related data structures. 23 | pub mod mesh; 24 | 25 | /// Contains `Root`. 26 | pub mod root; 27 | 28 | /// Contains `Scene`, `Node`, and other related data structures. 29 | pub mod scene; 30 | 31 | /// Contains `Skin` and other related data structures. 32 | pub mod skin; 33 | 34 | /// Contains `Texture`, `Sampler`, and other related data structures. 35 | pub mod texture; 36 | 37 | pub use self::root::Root; 38 | 39 | /// Names of glTF 2.0 extensions enabled by the user. 40 | pub const ENABLED_EXTENSIONS: &[&str] = &[ 41 | #[cfg(feature = "KHR_lights_punctual")] 42 | "KHR_lights_punctual", 43 | #[cfg(feature = "KHR_materials_pbrSpecularGlossiness")] 44 | "KHR_materials_pbrSpecularGlossiness", 45 | #[cfg(feature = "KHR_materials_unlit")] 46 | "KHR_materials_unlit", 47 | #[cfg(feature = "KHR_texture_transform")] 48 | "KHR_texture_transform", 49 | #[cfg(feature = "KHR_materials_transmission")] 50 | "KHR_materials_transmission", 51 | #[cfg(feature = "KHR_materials_ior")] 52 | "KHR_materials_ior", 53 | #[cfg(feature = "KHR_materials_emissive_strength")] 54 | "KHR_materials_emissive_strength", 55 | #[cfg(feature = "KHR_materials_clearcoat")] 56 | "KHR_materials_clearcoat", 57 | #[cfg(feature = "KHR_materials_sheen")] 58 | "KHR_materials_sheen", 59 | // Allowlisted texture extensions. Processing is delegated to the user. 60 | #[cfg(feature = "allow_empty_texture")] 61 | "KHR_texture_basisu", 62 | #[cfg(feature = "EXT_texture_webp")] 63 | "EXT_texture_webp", 64 | #[cfg(feature = "allow_empty_texture")] 65 | "MSFT_texture_dds", 66 | ]; 67 | 68 | /// Names of glTF 2.0 extensions supported by the library. 69 | pub const SUPPORTED_EXTENSIONS: &[&str] = &[ 70 | "KHR_lights_punctual", 71 | "KHR_materials_pbrSpecularGlossiness", 72 | "KHR_materials_unlit", 73 | "KHR_texture_transform", 74 | "KHR_materials_transmission", 75 | "KHR_materials_ior", 76 | "KHR_materials_emissive_strength", 77 | "KHR_materials_clearcoat", 78 | "KHR_materials_sheen", 79 | "EXT_texture_webp", 80 | ]; 81 | -------------------------------------------------------------------------------- /gltf-json/src/path.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// An immutable JSON source path. 4 | #[derive(Default, Clone, Debug, PartialEq)] 5 | pub struct Path(pub String); 6 | 7 | impl Path { 8 | /// Creates an empty JSON source path. 9 | /// 10 | /// # Examples 11 | /// 12 | /// Basic usage 13 | /// 14 | /// ```rust 15 | /// # use gltf_json::Path; 16 | /// let path = Path::new(); 17 | /// assert_eq!("", path.as_str()); 18 | /// ``` 19 | pub fn new() -> Self { 20 | Path(String::new()) 21 | } 22 | 23 | /// Returns a new path ending with the given field. 24 | /// 25 | /// # Examples 26 | /// 27 | /// Basic usage 28 | /// 29 | /// ```rust 30 | /// # use gltf_json::Path; 31 | /// let path = Path::new().field("foo"); 32 | /// assert_eq!("foo", path.as_str()); 33 | /// assert_eq!("foo.bar", path.field("bar").as_str()); 34 | /// ``` 35 | pub fn field(&self, name: &str) -> Self { 36 | if self.0.is_empty() { 37 | Path(name.to_string()) 38 | } else { 39 | Path(format!("{}.{}", self.0, name)) 40 | } 41 | } 42 | 43 | /// Returns a new path ending with the given array index. 44 | /// 45 | /// # Examples 46 | /// 47 | /// Basic usage 48 | /// 49 | /// ```rust 50 | /// # use gltf_json::Path; 51 | /// let path = Path::new().field("foo"); 52 | /// assert_eq!("foo[123]", path.index(123).as_str()); 53 | /// ``` 54 | pub fn index(&self, index: usize) -> Self { 55 | Path(format!("{}[{}]", self.0, index)) 56 | } 57 | 58 | /// Returns a new path ending with the given object key. 59 | /// 60 | /// # Examples 61 | /// 62 | /// Basic usage 63 | /// 64 | /// ```rust 65 | /// # use gltf_json::Path; 66 | /// let path = Path::new().field("foo"); 67 | /// assert_eq!("foo[\"bar\"]", path.key("bar").as_str()); 68 | /// ``` 69 | pub fn key(&self, key: &str) -> Self { 70 | Path(format!("{}[\"{}\"]", self.0, key)) 71 | } 72 | 73 | /// Provides a string value for a JSON path. 74 | /// 75 | /// # Examples 76 | /// 77 | /// Basic usage 78 | /// 79 | /// ```rust 80 | /// # use gltf_json::Path; 81 | /// let path = Path::new().field("foo").index(0).value_str("baz"); 82 | /// assert_eq!("foo[0] = \"baz\"", path.as_str()); 83 | /// ``` 84 | pub fn value_str(&self, value: &str) -> Self { 85 | Path(format!("{} = \"{}\"", self.0, value)) 86 | } 87 | 88 | /// Returns a view into the internal representation. 89 | pub fn as_str(&self) -> &str { 90 | &self.0 91 | } 92 | } 93 | 94 | impl fmt::Display for Path { 95 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 96 | write!(f, "{}", self.0) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/mesh/util/indices.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use super::ReadIndices; 4 | 5 | /// Casting iterator for `Indices`. 6 | #[derive(Clone, Debug)] 7 | pub struct CastingIter<'a, T>(ReadIndices<'a>, PhantomData); 8 | 9 | /// Type which describes how to cast any index into u32. 10 | #[derive(Clone, Debug)] 11 | pub struct U32; 12 | 13 | /// Trait for types which describe casting behaviour. 14 | pub trait Cast { 15 | /// Output type. 16 | type Output; 17 | 18 | /// Cast from u8. 19 | fn cast_u8(x: u8) -> Self::Output; 20 | 21 | /// Cast from u16. 22 | fn cast_u16(x: u16) -> Self::Output; 23 | 24 | /// Cast from u32. 25 | fn cast_u32(x: u32) -> Self::Output; 26 | } 27 | 28 | impl<'a, A> CastingIter<'a, A> { 29 | pub(crate) fn new(iter: ReadIndices<'a>) -> Self { 30 | CastingIter(iter, PhantomData) 31 | } 32 | 33 | /// Unwrap underlying `Indices` object. 34 | pub fn unwrap(self) -> ReadIndices<'a> { 35 | self.0 36 | } 37 | } 38 | 39 | impl<'a, A: Cast> ExactSizeIterator for CastingIter<'a, A> {} 40 | impl<'a, A: Cast> Iterator for CastingIter<'a, A> { 41 | type Item = A::Output; 42 | 43 | #[inline] 44 | fn next(&mut self) -> Option { 45 | match self.0 { 46 | ReadIndices::U8(ref mut i) => i.next().map(A::cast_u8), 47 | ReadIndices::U16(ref mut i) => i.next().map(A::cast_u16), 48 | ReadIndices::U32(ref mut i) => i.next().map(A::cast_u32), 49 | } 50 | } 51 | 52 | #[inline] 53 | fn nth(&mut self, x: usize) -> Option { 54 | match self.0 { 55 | ReadIndices::U8(ref mut i) => i.nth(x).map(A::cast_u8), 56 | ReadIndices::U16(ref mut i) => i.nth(x).map(A::cast_u16), 57 | ReadIndices::U32(ref mut i) => i.nth(x).map(A::cast_u32), 58 | } 59 | } 60 | 61 | fn last(self) -> Option { 62 | match self.0 { 63 | ReadIndices::U8(i) => i.last().map(A::cast_u8), 64 | ReadIndices::U16(i) => i.last().map(A::cast_u16), 65 | ReadIndices::U32(i) => i.last().map(A::cast_u32), 66 | } 67 | } 68 | 69 | fn count(self) -> usize { 70 | self.size_hint().0 71 | } 72 | 73 | #[inline] 74 | fn size_hint(&self) -> (usize, Option) { 75 | match self.0 { 76 | ReadIndices::U8(ref i) => i.size_hint(), 77 | ReadIndices::U16(ref i) => i.size_hint(), 78 | ReadIndices::U32(ref i) => i.size_hint(), 79 | } 80 | } 81 | } 82 | 83 | impl Cast for U32 { 84 | type Output = u32; 85 | 86 | fn cast_u8(x: u8) -> Self::Output { 87 | x as Self::Output 88 | } 89 | fn cast_u16(x: u16) -> Self::Output { 90 | x as Self::Output 91 | } 92 | fn cast_u32(x: u32) -> Self::Output { 93 | x 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gltf" 3 | version = "1.4.1" 4 | authors = ["David Harvey-Macaulay "] 5 | description = "glTF 2.0 loader" 6 | documentation = "https://docs.rs/gltf" 7 | repository = "https://github.com/gltf-rs/gltf" 8 | homepage = "https://github.com/gltf-rs/gltf" 9 | readme = "README.md" 10 | keywords = ["glTF", "3D", "asset", "model", "scene"] 11 | license = "MIT OR Apache-2.0" 12 | include = ["**/*.rs", "Cargo.toml", "LICENSE-*"] 13 | edition = "2021" 14 | rust-version = "1.61" 15 | 16 | [badges] 17 | travis-ci = { repository = "gltf-rs/gltf" } 18 | 19 | [workspace] 20 | members = ["gltf-derive", "gltf-json"] 21 | 22 | [dev-dependencies] 23 | approx = "0.5" 24 | bytemuck = { version = "1.21.0", features = ["derive"] } 25 | 26 | [dependencies] 27 | base64 = { optional = true, version = "0.13" } 28 | byteorder = "1.3" 29 | gltf-json = { path = "gltf-json", version = "=1.4.1" } 30 | urlencoding = { optional = true, version = "2.1" } 31 | serde_json = { features = ["raw_value"], version = "1.0" } 32 | 33 | [dependencies.image] 34 | default-features = false 35 | features = ["jpeg", "png"] 36 | optional = true 37 | version = "0.25" 38 | 39 | [features] 40 | default = ["import", "utils", "names"] 41 | allow_empty_animation_target_node = ["gltf-json/allow_empty_animation_target_node"] 42 | allow_empty_texture = ["gltf-json/allow_empty_texture"] 43 | extensions = ["gltf-json/extensions"] 44 | extras = ["gltf-json/extras"] 45 | names = ["gltf-json/names"] 46 | utils = [] 47 | import = ["base64", "image", "urlencoding"] 48 | KHR_lights_punctual = ["gltf-json/KHR_lights_punctual"] 49 | KHR_materials_pbrSpecularGlossiness = ["gltf-json/KHR_materials_pbrSpecularGlossiness"] 50 | KHR_materials_unlit = ["gltf-json/KHR_materials_unlit"] 51 | KHR_texture_transform = ["gltf-json/KHR_texture_transform"] 52 | KHR_materials_transmission = ["gltf-json/KHR_materials_transmission"] 53 | KHR_materials_ior = ["gltf-json/KHR_materials_ior"] 54 | KHR_materials_variants = ["gltf-json/KHR_materials_variants"] 55 | KHR_materials_volume = ["gltf-json/KHR_materials_volume"] 56 | KHR_materials_specular = ["gltf-json/KHR_materials_specular"] 57 | KHR_materials_sheen = ["gltf-json/KHR_materials_sheen"] 58 | KHR_materials_clearcoat = ["gltf-json/KHR_materials_clearcoat"] 59 | KHR_materials_emissive_strength = ["gltf-json/KHR_materials_emissive_strength"] 60 | EXT_texture_webp = ["gltf-json/EXT_texture_webp", "image/webp"] 61 | guess_mime_type = [] 62 | 63 | [[example]] 64 | name = "gltf-display" 65 | path = "examples/display/main.rs" 66 | 67 | [[example]] 68 | name = "gltf-export" 69 | path = "examples/export/main.rs" 70 | 71 | [[example]] 72 | name = "gltf-roundtrip" 73 | path = "examples/roundtrip/main.rs" 74 | 75 | [[example]] 76 | name = "gltf-tree" 77 | path = "examples/tree/main.rs" 78 | 79 | [package.metadata.docs.rs] 80 | all-features = true 81 | rustdoc-args = ["--cfg", "docsrs"] 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | gltf 3 |

4 |

5 | 6 | crates.io 7 | 8 | 9 | docs.rs 10 | 11 |

12 | 13 | --- 14 | 15 | This crate is intended to load [glTF 2.0](https://www.khronos.org/gltf), a file format designed for the efficient transmission of 3D assets. 16 | 17 | `rustc` version 1.61 or above is required. 18 | 19 | ### Reference infographic 20 | 21 | ![infographic](https://raw.githubusercontent.com/KhronosGroup/glTF/main/specification/2.0/figures/gltfOverview-2.0.0d.png) 22 | 23 |

From javagl/gltfOverview

24 |

PDF version

25 | 26 | ### Usage 27 | 28 | See the [crate documentation](https://docs.rs/gltf) for example usage. 29 | 30 | ### Features 31 | 32 | #### Extras and names 33 | 34 | By default, `gltf` ignores all `extras` and `names` included with glTF assets. You can negate this by enabling the `extras` and `names` features, respectively. 35 | 36 | ```toml 37 | [dependencies.gltf] 38 | version = "1.4" 39 | features = ["extras", "names"] 40 | ``` 41 | 42 | #### glTF extensions 43 | 44 | The following glTF extensions are supported by the crate: 45 | 46 | - `KHR_lights_punctual` 47 | - `KHR_materials_pbrSpecularGlossiness` 48 | - `KHR_materials_unlit` 49 | - `KHR_texture_transform` 50 | - `KHR_materials_variants` 51 | - `KHR_materials_volume` 52 | - `KHR_materials_specular` 53 | - `KHR_materials_transmission` 54 | - `KHR_materials_ior` 55 | - `KHR_materials_emissive_strength ` 56 | - `EXT_texture_webp` 57 | 58 | To use an extension, list its name in the `features` section. 59 | 60 | ```toml 61 | [dependencies.gltf] 62 | features = ["KHR_materials_unlit"] 63 | ``` 64 | 65 | ### Examples 66 | 67 | #### gltf-display 68 | 69 | Demonstrates how the glTF JSON is deserialized. 70 | 71 | ```sh 72 | cargo run --example gltf-display path/to/asset.gltf 73 | ``` 74 | 75 | #### gltf-export 76 | 77 | Demonstrates how glTF JSON can be built and exported using the `gltf-json` crate. 78 | 79 | ```sh 80 | cargo run --example gltf-export 81 | ``` 82 | 83 | #### gltf-roundtrip 84 | 85 | Deserializes and serializes the JSON part of a glTF asset. 86 | 87 | ```sh 88 | cargo run --example gltf-roundtrip path/to/asset.gltf 89 | ``` 90 | 91 | #### gltf-tree 92 | 93 | Visualises the scene heirarchy of a glTF asset, which is a strict tree of nodes. 94 | 95 | ```sh 96 | cargo run --example gltf-tree path/to/asset.gltf 97 | ``` 98 | 99 | ### Tests 100 | 101 | Running tests locally requires to clone the [`glTF-Sample-Models`](https://github.com/KhronosGroup/glTF-Sample-Models) repository first. 102 | 103 | ```sh 104 | git clone https://github.com/KhronosGroup/glTF-Sample-Models.git 105 | ``` 106 | -------------------------------------------------------------------------------- /gltf-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Adapted from `validator_derive` (https://github.com/Keats/validator). 2 | // 3 | // See LICENSE for details. 4 | 5 | #![recursion_limit = "128"] 6 | 7 | extern crate proc_macro; 8 | 9 | use proc_macro::TokenStream; 10 | use syn::DeriveInput; 11 | 12 | #[proc_macro_derive(Validate, attributes(gltf))] 13 | pub fn derive_validate(input: TokenStream) -> TokenStream { 14 | expand(&syn::parse_macro_input!(input as DeriveInput)).into() 15 | } 16 | 17 | struct ValidateHook(pub syn::Ident); 18 | 19 | impl syn::parse::Parse for ValidateHook { 20 | fn parse(input: syn::parse::ParseStream<'_>) -> syn::parse::Result { 21 | let tag = input.parse::()?; 22 | if tag == "validate_hook" { 23 | let _eq = input.parse::()?; 24 | let literal = input.parse::()?; 25 | let ident = syn::Ident::new(&literal.value(), tag.span()); 26 | Ok(ValidateHook(ident)) 27 | } else { 28 | panic!("unrecognized gltf attribute"); 29 | } 30 | } 31 | } 32 | 33 | fn expand(ast: &DeriveInput) -> proc_macro2::TokenStream { 34 | use proc_macro2::TokenStream; 35 | use quote::quote; 36 | 37 | let mut validate_hook = quote! {}; 38 | for attr in &ast.attrs { 39 | if attr.path().is_ident("gltf") { 40 | let ValidateHook(ident) = attr 41 | .parse_args::() 42 | .expect("failed to parse attribute"); 43 | validate_hook = quote! { 44 | #ident(self, _root, _path, _report); 45 | }; 46 | } 47 | } 48 | 49 | let fields = match ast.data { 50 | syn::Data::Struct(ref data_struct) => &data_struct.fields, 51 | _ => panic!("#[derive(Validate)] only works on `struct`s"), 52 | }; 53 | let ident = &ast.ident; 54 | let validations: Vec = fields 55 | .iter() 56 | .map(|f| f.ident.as_ref().unwrap()) 57 | .map(|ident| { 58 | use inflections::Inflect; 59 | let field = ident.to_string().to_camel_case(); 60 | quote!( 61 | self.#ident.validate( 62 | _root, 63 | || _path().field(#field), 64 | _report, 65 | ) 66 | ) 67 | }) 68 | .collect(); 69 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 70 | quote!( 71 | impl #impl_generics crate::validation::Validate 72 | for #ident #ty_generics #where_clause 73 | { 74 | fn validate( 75 | &self, 76 | _root: &crate::Root, 77 | _path: P, 78 | _report: &mut R 79 | ) where 80 | P: Fn() -> crate::Path, 81 | R: FnMut(&dyn Fn() -> crate::Path, crate::validation::Error), 82 | { 83 | #( 84 | #validations; 85 | )* 86 | 87 | #validate_hook 88 | } 89 | } 90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /tests/roundtrip_binary_gltf.rs: -------------------------------------------------------------------------------- 1 | //! Roundtrip test. 2 | //! 3 | //! Read some binary glTF, write it to disk, and compare to the original. 4 | //! The test will succeed if the output is the same as the original. 5 | 6 | use std::io::Read; 7 | use std::{boxed, error, fs, io, path}; 8 | 9 | const SAMPLE_MODELS_DIRECTORY_PATH: &str = "glTF-Sample-Assets/Models"; 10 | 11 | fn run() -> Result<(), boxed::Box> { 12 | let mut all_tests_passed = true; 13 | let mut nr_test_cases = 0; 14 | for entry in fs::read_dir(SAMPLE_MODELS_DIRECTORY_PATH)? { 15 | let entry = entry?; 16 | let metadata = entry.metadata()?; 17 | if metadata.is_dir() { 18 | let entry_path = entry.path(); 19 | if let Some(file_name) = entry_path.file_name() { 20 | let mut path = entry_path.join("glTF-Binary").join(file_name); 21 | path.set_extension("glb"); 22 | if path.exists() { 23 | // not all models have binary versions 24 | if let Err(err) = test(&path) { 25 | println!("{:?}: error: {:?}", path, err); 26 | all_tests_passed = false; 27 | } else { 28 | println!("{:?}: ok", path); 29 | nr_test_cases += 1; 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | if sparse_accessor_without_buffer_view_test() { 37 | nr_test_cases += 1; 38 | } else { 39 | all_tests_passed = false; 40 | } 41 | 42 | assert!(all_tests_passed); 43 | assert!(nr_test_cases >= 25); 44 | Ok(()) 45 | } 46 | 47 | fn test(path: &path::Path) -> Result<(), boxed::Box> { 48 | let file = fs::File::open(path)?; 49 | let length = file.metadata()?.len() as usize; 50 | let mut reader = io::BufReader::new(file); 51 | let mut original = Vec::with_capacity(length); 52 | reader.read_to_end(&mut original)?; 53 | 54 | // Check from_reader/to_vec implementation. 55 | { 56 | let glb = gltf::binary::Glb::from_reader(io::Cursor::new(&original))?; 57 | let output = glb.to_vec()?; 58 | assert_eq!(&original, &output); 59 | } 60 | 61 | // Check from_slice/to_writer implementation. 62 | { 63 | let glb = gltf::binary::Glb::from_slice(&original)?; 64 | let mut output = Vec::with_capacity(length); 65 | glb.to_writer(&mut output as &mut dyn io::Write)?; 66 | assert_eq!(&original, &output); 67 | } 68 | 69 | Ok(()) 70 | } 71 | 72 | /// Test a file with a sparse accessor with no buffer view. 73 | /// 74 | /// Return true if the test passes, and false otherwise. 75 | fn sparse_accessor_without_buffer_view_test() -> bool { 76 | let path = path::Path::new("tests/box_sparse.glb"); 77 | if let Err(err) = test(path) { 78 | println!("{:?}: error: {:?}", path, err); 79 | false 80 | } else { 81 | println!("{:?}: ok", path); 82 | true 83 | } 84 | } 85 | 86 | #[test] 87 | fn roundtrip_binary_gltf() { 88 | run().expect("test failure"); 89 | } 90 | -------------------------------------------------------------------------------- /gltf-json/src/lib.rs: -------------------------------------------------------------------------------- 1 | /// Contains `Accessor` and other related data structures. 2 | pub mod accessor; 3 | 4 | /// Contains `Animation` and other related data structures. 5 | pub mod animation; 6 | 7 | /// Contains `Asset` metadata. 8 | pub mod asset; 9 | 10 | /// Contains `Buffer`, `View`, and other related data structures. 11 | pub mod buffer; 12 | 13 | /// Contains `Camera` and other related data structures. 14 | pub mod camera; 15 | 16 | /// Contains extension specific data structures and the names of all 17 | /// 2.0 extensions supported by the library. 18 | pub mod extensions; 19 | 20 | /// Contains `Extras`. 21 | pub mod extras; 22 | 23 | /// Contains `Image` and other related data structures. 24 | pub mod image; 25 | 26 | /// Contains `Material` and other related data structures. 27 | pub mod material; 28 | 29 | /// Contains `Mesh` and other related data structures. 30 | pub mod mesh; 31 | 32 | /// Contains `Path`. 33 | pub mod path; 34 | 35 | /// Contains `Root`. 36 | pub mod root; 37 | 38 | /// Contains `Scene`, `Node`, and other related data structures. 39 | pub mod scene; 40 | 41 | /// Contains `Skin` and other related data structures. 42 | pub mod skin; 43 | 44 | /// Contains `Texture`, `Sampler`, and other related data structures. 45 | pub mod texture; 46 | 47 | /// Contains functions that validate glTF JSON data against the specification. 48 | pub mod validation; 49 | 50 | #[doc(inline)] 51 | pub use accessor::Accessor; 52 | #[doc(inline)] 53 | pub use animation::Animation; 54 | #[doc(inline)] 55 | pub use asset::Asset; 56 | #[doc(inline)] 57 | pub use buffer::Buffer; 58 | #[doc(inline)] 59 | pub use camera::Camera; 60 | #[doc(inline)] 61 | pub use image::Image; 62 | #[doc(inline)] 63 | pub use material::Material; 64 | #[doc(inline)] 65 | pub use mesh::Mesh; 66 | #[doc(inline)] 67 | pub use scene::Node; 68 | #[doc(inline)] 69 | pub use scene::Scene; 70 | #[doc(inline)] 71 | pub use skin::Skin; 72 | #[doc(inline)] 73 | pub use texture::Texture; 74 | 75 | #[doc(inline)] 76 | pub use self::extras::Extras; 77 | #[doc(inline)] 78 | pub use self::path::Path; 79 | #[doc(inline)] 80 | pub use self::root::Index; 81 | #[doc(inline)] 82 | pub use self::root::Root; 83 | 84 | #[doc(inline)] 85 | pub use serde_json::Error; 86 | #[doc(inline)] 87 | pub use serde_json::Value; 88 | 89 | /// Re-exports of `serde_json` deserialization functions. 90 | /// 91 | /// This module re-exports the generic serde deserialization functions 92 | /// so that one can deserialize data structures other than `Root` without 93 | /// being bound to a specific version of `serde_json`. 94 | pub mod deserialize { 95 | pub use serde_json::{from_reader, from_slice, from_str, from_value}; 96 | } 97 | 98 | /// Re-exports of `serde_json` serialization functions. 99 | /// 100 | /// This module re-exports the generic serde serialization functions 101 | /// so that one can serialize data structures other than `Root` without 102 | /// being bound to a specific version of `serde_json`. 103 | pub mod serialize { 104 | pub use serde_json::{ 105 | to_string, to_string_pretty, to_value, to_vec, to_vec_pretty, to_writer, to_writer_pretty, 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /examples/Box.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "generator": "COLLADA2GLTF", 4 | "version": "2.0" 5 | }, 6 | "scene": 0, 7 | "scenes": [ 8 | { 9 | "nodes": [ 10 | 0 11 | ] 12 | } 13 | ], 14 | "nodes": [ 15 | { 16 | "children": [ 17 | 1 18 | ], 19 | "matrix": [ 20 | 1.0, 21 | 0.0, 22 | 0.0, 23 | 0.0, 24 | 0.0, 25 | 0.0, 26 | -1.0, 27 | 0.0, 28 | 0.0, 29 | 1.0, 30 | 0.0, 31 | 0.0, 32 | 0.0, 33 | 0.0, 34 | 0.0, 35 | 1.0 36 | ] 37 | }, 38 | { 39 | "mesh": 0 40 | } 41 | ], 42 | "meshes": [ 43 | { 44 | "primitives": [ 45 | { 46 | "attributes": { 47 | "NORMAL": 1, 48 | "POSITION": 2 49 | }, 50 | "indices": 0, 51 | "mode": 4, 52 | "material": 0 53 | } 54 | ], 55 | "name": "Mesh" 56 | } 57 | ], 58 | "accessors": [ 59 | { 60 | "bufferView": 0, 61 | "byteOffset": 0, 62 | "componentType": 5123, 63 | "count": 36, 64 | "max": [ 65 | 23 66 | ], 67 | "min": [ 68 | 0 69 | ], 70 | "type": "SCALAR" 71 | }, 72 | { 73 | "bufferView": 1, 74 | "byteOffset": 0, 75 | "componentType": 5126, 76 | "count": 24, 77 | "max": [ 78 | 1.0, 79 | 1.0, 80 | 1.0 81 | ], 82 | "min": [ 83 | -1.0, 84 | -1.0, 85 | -1.0 86 | ], 87 | "type": "VEC3" 88 | }, 89 | { 90 | "bufferView": 1, 91 | "byteOffset": 288, 92 | "componentType": 5126, 93 | "count": 24, 94 | "max": [ 95 | 0.5, 96 | 0.5, 97 | 0.5 98 | ], 99 | "min": [ 100 | -0.5, 101 | -0.5, 102 | -0.5 103 | ], 104 | "type": "VEC3" 105 | } 106 | ], 107 | "materials": [ 108 | { 109 | "pbrMetallicRoughness": { 110 | "baseColorFactor": [ 111 | 0.800000011920929, 112 | 0.0, 113 | 0.0, 114 | 1.0 115 | ], 116 | "metallicFactor": 0.0 117 | }, 118 | "name": "Red" 119 | } 120 | ], 121 | "bufferViews": [ 122 | { 123 | "buffer": 0, 124 | "byteOffset": 576, 125 | "byteLength": 72, 126 | "target": 34963 127 | }, 128 | { 129 | "buffer": 0, 130 | "byteOffset": 0, 131 | "byteLength": 576, 132 | "byteStride": 12, 133 | "target": 34962 134 | } 135 | ], 136 | "buffers": [ 137 | { 138 | "byteLength": 648, 139 | "uri": "Box0.bin" 140 | } 141 | ] 142 | } 143 | -------------------------------------------------------------------------------- /tests/import_sample_models.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::{fs, path}; 3 | 4 | const SAMPLE_MODELS_DIRECTORY_PATH: &str = "glTF-Sample-Assets/Models"; 5 | 6 | fn check_import_result( 7 | result: gltf::Result<( 8 | gltf::Document, 9 | Vec, 10 | Vec, 11 | )>, 12 | ) { 13 | use gltf::json::validation::Error; 14 | match result { 15 | Err(gltf::Error::Validation(errors)) => { 16 | let all_unsupported = errors 17 | .iter() 18 | .all(|(_path, error)| *error == Error::Unsupported); 19 | if !all_unsupported { 20 | println!("{errors:#?}"); 21 | } 22 | assert!(all_unsupported); 23 | println!("skipped"); 24 | } 25 | Err(otherwise) => { 26 | panic!("{otherwise:#?}"); 27 | } 28 | Ok((document, buffer_data, image_data)) => { 29 | // Check buffers. 30 | assert_eq!(document.buffers().len(), buffer_data.len()); 31 | 32 | for (buf, data) in document.buffers().zip(buffer_data.iter()) { 33 | assert!((buf.length() + 3) & !3 <= data.0.len()) 34 | } 35 | 36 | // Check images. 37 | assert_eq!(document.images().len(), image_data.len()); 38 | 39 | println!("ok"); 40 | } 41 | } 42 | } 43 | 44 | fn run() -> Result<(), Box> { 45 | let sample_dir_path = path::Path::new(SAMPLE_MODELS_DIRECTORY_PATH); 46 | for entry in fs::read_dir(sample_dir_path)? { 47 | let entry = entry?; 48 | let metadata = entry.metadata()?; 49 | if metadata.is_dir() { 50 | let entry_path = entry.path(); 51 | if let Some(file_name) = entry_path.file_name() { 52 | // Import standard glTF. 53 | let mut gltf_path = entry_path.join("glTF").join(file_name); 54 | gltf_path.set_extension("gltf"); 55 | if gltf_path.exists() { 56 | print!("{}: ", gltf_path.display()); 57 | check_import_result(gltf::import(&gltf_path)); 58 | } 59 | 60 | // Import standard glTF with embedded buffer and image data. 61 | let mut gle_path = entry_path.join("glTF-Embedded").join(file_name); 62 | gle_path.set_extension("gltf"); 63 | if gle_path.exists() { 64 | print!("{}: ", gle_path.display()); 65 | check_import_result(gltf::import(&gle_path)); 66 | } 67 | 68 | // Import binary glTF. 69 | let mut glb_path = entry_path.join("glTF-Binary").join(file_name); 70 | glb_path.set_extension("glb"); 71 | if glb_path.exists() { 72 | print!("{}: ", glb_path.display()); 73 | check_import_result(gltf::import(&glb_path)); 74 | } 75 | } 76 | } 77 | } 78 | 79 | sparse_accessor_without_buffer_view_test() 80 | } 81 | 82 | /// Test a file with a sparse accessor with no buffer view 83 | fn sparse_accessor_without_buffer_view_test() -> Result<(), Box> { 84 | let glb_path = path::Path::new("tests/box_sparse.glb"); 85 | print!("{}: ", glb_path.display()); 86 | check_import_result(gltf::import(glb_path)); 87 | 88 | let gltf_path = path::Path::new("tests/box_sparse.gltf"); 89 | print!("{}: ", gltf_path.display()); 90 | check_import_result(gltf::import(gltf_path)); 91 | Ok(()) 92 | } 93 | 94 | #[test] 95 | fn import_sample_models() { 96 | if let Err(error) = run() { 97 | panic!("import failed: {:?}", error); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/skin/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "extensions")] 2 | use serde_json::{Map, Value}; 3 | 4 | use crate::{Accessor, Document, Node}; 5 | 6 | #[cfg(feature = "utils")] 7 | use crate::Buffer; 8 | 9 | /// Iterators. 10 | pub mod iter; 11 | 12 | /// Utility functions. 13 | #[cfg(feature = "utils")] 14 | #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] 15 | pub mod util; 16 | 17 | #[cfg(feature = "utils")] 18 | #[doc(inline)] 19 | pub use self::util::Reader; 20 | 21 | /// Joints and matrices defining a skin. 22 | #[derive(Clone, Debug)] 23 | pub struct Skin<'a> { 24 | /// The parent `Document` struct. 25 | document: &'a Document, 26 | 27 | /// The corresponding JSON index. 28 | index: usize, 29 | 30 | /// The corresponding JSON struct. 31 | json: &'a json::skin::Skin, 32 | } 33 | 34 | impl<'a> Skin<'a> { 35 | /// Constructs a `Skin`. 36 | pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::skin::Skin) -> Self { 37 | Self { 38 | document, 39 | index, 40 | json, 41 | } 42 | } 43 | 44 | /// Returns the internal JSON index. 45 | pub fn index(&self) -> usize { 46 | self.index 47 | } 48 | 49 | /// Returns extension data unknown to this crate version. 50 | #[cfg(feature = "extensions")] 51 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 52 | pub fn extensions(&self) -> Option<&Map> { 53 | let ext = self.json.extensions.as_ref()?; 54 | Some(&ext.others) 55 | } 56 | 57 | /// Queries extension data unknown to this crate version. 58 | #[cfg(feature = "extensions")] 59 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 60 | pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { 61 | let ext = self.json.extensions.as_ref()?; 62 | ext.others.get(ext_name) 63 | } 64 | 65 | /// Optional application specific data. 66 | pub fn extras(&self) -> &'a json::Extras { 67 | &self.json.extras 68 | } 69 | 70 | /// Returns the accessor containing the 4x4 inverse-bind matrices. 71 | /// 72 | /// When `None`, each matrix is assumed to be the 4x4 identity matrix which 73 | /// implies that the inverse-bind matrices were pre-applied. 74 | pub fn inverse_bind_matrices(&self) -> Option> { 75 | self.json 76 | .inverse_bind_matrices 77 | .as_ref() 78 | .map(|index| self.document.accessors().nth(index.value()).unwrap()) 79 | } 80 | 81 | /// Constructs a skin reader. 82 | #[cfg(feature = "utils")] 83 | #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] 84 | pub fn reader<'s, F>(&'a self, get_buffer_data: F) -> Reader<'a, 's, F> 85 | where 86 | F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, 87 | { 88 | Reader { 89 | skin: self.clone(), 90 | get_buffer_data, 91 | } 92 | } 93 | 94 | /// Returns an `Iterator` that visits the skeleton nodes used as joints in 95 | /// this skin. 96 | pub fn joints(&self) -> iter::Joints<'a> { 97 | iter::Joints { 98 | document: self.document, 99 | iter: self.json.joints.iter(), 100 | } 101 | } 102 | 103 | /// Optional user-defined name for this object. 104 | #[cfg(feature = "names")] 105 | #[cfg_attr(docsrs, doc(cfg(feature = "names")))] 106 | pub fn name(&self) -> Option<&'a str> { 107 | self.json.name.as_deref() 108 | } 109 | 110 | /// Returns the node used as the skeleton root. When `None`, joints 111 | /// transforms resolve to scene root. 112 | pub fn skeleton(&self) -> Option> { 113 | self.json 114 | .skeleton 115 | .as_ref() 116 | .map(|index| self.document.nodes().nth(index.value()).unwrap()) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/mesh/util/weights.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::Normalize; 4 | 5 | use super::ReadWeights; 6 | 7 | /// Casting iterator for `Weights`. 8 | #[derive(Clone, Debug)] 9 | pub struct CastingIter<'a, T>(ReadWeights<'a>, PhantomData); 10 | 11 | /// Type which describes how to cast any weight into u8. 12 | #[derive(Clone, Debug)] 13 | pub struct U8; 14 | 15 | /// Type which describes how to cast any weight into u16. 16 | #[derive(Clone, Debug)] 17 | pub struct U16; 18 | 19 | /// Type which describes how to cast any weight into f32. 20 | #[derive(Clone, Debug)] 21 | pub struct F32; 22 | 23 | /// Trait for types which describe casting behaviour. 24 | pub trait Cast { 25 | /// Output type. 26 | type Output; 27 | 28 | /// Cast from u8. 29 | fn cast_u8(x: [u8; 4]) -> Self::Output; 30 | 31 | /// Cast from u16. 32 | fn cast_u16(x: [u16; 4]) -> Self::Output; 33 | 34 | /// Cast from f32. 35 | fn cast_f32(x: [f32; 4]) -> Self::Output; 36 | } 37 | 38 | impl<'a, A> CastingIter<'a, A> { 39 | pub(crate) fn new(iter: ReadWeights<'a>) -> Self { 40 | CastingIter(iter, PhantomData) 41 | } 42 | 43 | /// Unwrap underlying `Weights` object. 44 | pub fn unwrap(self) -> ReadWeights<'a> { 45 | self.0 46 | } 47 | } 48 | 49 | impl<'a, A: Cast> ExactSizeIterator for CastingIter<'a, A> {} 50 | impl<'a, A: Cast> Iterator for CastingIter<'a, A> { 51 | type Item = A::Output; 52 | 53 | #[inline] 54 | fn next(&mut self) -> Option { 55 | match self.0 { 56 | ReadWeights::U8(ref mut i) => i.next().map(A::cast_u8), 57 | ReadWeights::U16(ref mut i) => i.next().map(A::cast_u16), 58 | ReadWeights::F32(ref mut i) => i.next().map(A::cast_f32), 59 | } 60 | } 61 | 62 | #[inline] 63 | fn nth(&mut self, x: usize) -> Option { 64 | match self.0 { 65 | ReadWeights::U8(ref mut i) => i.nth(x).map(A::cast_u8), 66 | ReadWeights::U16(ref mut i) => i.nth(x).map(A::cast_u16), 67 | ReadWeights::F32(ref mut i) => i.nth(x).map(A::cast_f32), 68 | } 69 | } 70 | 71 | fn last(self) -> Option { 72 | match self.0 { 73 | ReadWeights::U8(i) => i.last().map(A::cast_u8), 74 | ReadWeights::U16(i) => i.last().map(A::cast_u16), 75 | ReadWeights::F32(i) => i.last().map(A::cast_f32), 76 | } 77 | } 78 | 79 | fn count(self) -> usize { 80 | self.size_hint().0 81 | } 82 | 83 | #[inline] 84 | fn size_hint(&self) -> (usize, Option) { 85 | match self.0 { 86 | ReadWeights::U8(ref i) => i.size_hint(), 87 | ReadWeights::U16(ref i) => i.size_hint(), 88 | ReadWeights::F32(ref i) => i.size_hint(), 89 | } 90 | } 91 | } 92 | 93 | impl Cast for U8 { 94 | type Output = [u8; 4]; 95 | 96 | fn cast_u8(x: [u8; 4]) -> Self::Output { 97 | x.normalize() 98 | } 99 | 100 | fn cast_u16(x: [u16; 4]) -> Self::Output { 101 | x.normalize() 102 | } 103 | 104 | fn cast_f32(x: [f32; 4]) -> Self::Output { 105 | x.normalize() 106 | } 107 | } 108 | 109 | impl Cast for U16 { 110 | type Output = [u16; 4]; 111 | 112 | fn cast_u8(x: [u8; 4]) -> Self::Output { 113 | x.normalize() 114 | } 115 | 116 | fn cast_u16(x: [u16; 4]) -> Self::Output { 117 | x.normalize() 118 | } 119 | 120 | fn cast_f32(x: [f32; 4]) -> Self::Output { 121 | x.normalize() 122 | } 123 | } 124 | 125 | impl Cast for F32 { 126 | type Output = [f32; 4]; 127 | 128 | fn cast_u8(x: [u8; 4]) -> Self::Output { 129 | x.normalize() 130 | } 131 | 132 | fn cast_u16(x: [u16; 4]) -> Self::Output { 133 | x.normalize() 134 | } 135 | 136 | fn cast_f32(x: [f32; 4]) -> Self::Output { 137 | x.normalize() 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/mesh/util/tex_coords.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::Normalize; 4 | 5 | use super::ReadTexCoords; 6 | 7 | /// Casting iterator for `TexCoords`. 8 | #[derive(Clone, Debug)] 9 | pub struct CastingIter<'a, T>(ReadTexCoords<'a>, PhantomData); 10 | 11 | /// Type which describes how to cast any texture coordinate into pair of u8. 12 | #[derive(Clone, Debug)] 13 | pub struct U8; 14 | 15 | /// Type which describes how to cast any texture coordinate into pair of u16. 16 | #[derive(Clone, Debug)] 17 | pub struct U16; 18 | 19 | /// Type which describes how to cast any texture coordinate into pair of f32. 20 | #[derive(Clone, Debug)] 21 | pub struct F32; 22 | 23 | /// Trait for types which describe casting behaviour. 24 | pub trait Cast { 25 | /// Output type. 26 | type Output; 27 | 28 | /// Cast from u8 pair. 29 | fn cast_u8(x: [u8; 2]) -> Self::Output; 30 | 31 | /// Cast from u16 pair. 32 | fn cast_u16(x: [u16; 2]) -> Self::Output; 33 | 34 | /// Cast from f32 pair. 35 | fn cast_f32(x: [f32; 2]) -> Self::Output; 36 | } 37 | 38 | impl<'a, A> CastingIter<'a, A> { 39 | pub(crate) fn new(iter: ReadTexCoords<'a>) -> Self { 40 | CastingIter(iter, PhantomData) 41 | } 42 | 43 | /// Unwrap underlying `TexCoords` object. 44 | pub fn unwrap(self) -> ReadTexCoords<'a> { 45 | self.0 46 | } 47 | } 48 | 49 | impl<'a, A: Cast> ExactSizeIterator for CastingIter<'a, A> {} 50 | impl<'a, A: Cast> Iterator for CastingIter<'a, A> { 51 | type Item = A::Output; 52 | 53 | #[inline] 54 | fn next(&mut self) -> Option { 55 | match self.0 { 56 | ReadTexCoords::U8(ref mut i) => i.next().map(A::cast_u8), 57 | ReadTexCoords::U16(ref mut i) => i.next().map(A::cast_u16), 58 | ReadTexCoords::F32(ref mut i) => i.next().map(A::cast_f32), 59 | } 60 | } 61 | 62 | #[inline] 63 | fn nth(&mut self, x: usize) -> Option { 64 | match self.0 { 65 | ReadTexCoords::U8(ref mut i) => i.nth(x).map(A::cast_u8), 66 | ReadTexCoords::U16(ref mut i) => i.nth(x).map(A::cast_u16), 67 | ReadTexCoords::F32(ref mut i) => i.nth(x).map(A::cast_f32), 68 | } 69 | } 70 | 71 | fn last(self) -> Option { 72 | match self.0 { 73 | ReadTexCoords::U8(i) => i.last().map(A::cast_u8), 74 | ReadTexCoords::U16(i) => i.last().map(A::cast_u16), 75 | ReadTexCoords::F32(i) => i.last().map(A::cast_f32), 76 | } 77 | } 78 | 79 | fn count(self) -> usize { 80 | self.size_hint().0 81 | } 82 | 83 | #[inline] 84 | fn size_hint(&self) -> (usize, Option) { 85 | match self.0 { 86 | ReadTexCoords::U8(ref i) => i.size_hint(), 87 | ReadTexCoords::U16(ref i) => i.size_hint(), 88 | ReadTexCoords::F32(ref i) => i.size_hint(), 89 | } 90 | } 91 | } 92 | 93 | impl Cast for U8 { 94 | type Output = [u8; 2]; 95 | 96 | fn cast_u8(x: [u8; 2]) -> Self::Output { 97 | x.normalize() 98 | } 99 | 100 | fn cast_u16(x: [u16; 2]) -> Self::Output { 101 | x.normalize() 102 | } 103 | 104 | fn cast_f32(x: [f32; 2]) -> Self::Output { 105 | x.normalize() 106 | } 107 | } 108 | 109 | impl Cast for U16 { 110 | type Output = [u16; 2]; 111 | 112 | fn cast_u8(x: [u8; 2]) -> Self::Output { 113 | x.normalize() 114 | } 115 | 116 | fn cast_u16(x: [u16; 2]) -> Self::Output { 117 | x.normalize() 118 | } 119 | 120 | fn cast_f32(x: [f32; 2]) -> Self::Output { 121 | x.normalize() 122 | } 123 | } 124 | 125 | impl Cast for F32 { 126 | type Output = [f32; 2]; 127 | 128 | fn cast_u8(x: [u8; 2]) -> Self::Output { 129 | x.normalize() 130 | } 131 | 132 | fn cast_u16(x: [u16; 2]) -> Self::Output { 133 | x.normalize() 134 | } 135 | 136 | fn cast_f32(x: [f32; 2]) -> Self::Output { 137 | x.normalize() 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/box_sparse.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": [ 3 | { 4 | "bufferView": 0, 5 | "byteOffset": 0, 6 | "count": 36, 7 | "componentType": 5125, 8 | "extras": {}, 9 | "type": "SCALAR", 10 | "min": [ 11 | 0 12 | ], 13 | "max": [ 14 | 7 15 | ] 16 | }, 17 | { 18 | "bufferView": 1, 19 | "byteOffset": 0, 20 | "count": 8, 21 | "componentType": 5126, 22 | "extras": {}, 23 | "type": "VEC3", 24 | "min": [ 25 | -0.5, 26 | -0.5, 27 | -0.5 28 | ], 29 | "max": [ 30 | 0.5, 31 | 0.5, 32 | 0.5 33 | ] 34 | }, 35 | { 36 | "byteOffset": 0, 37 | "count": 2, 38 | "componentType": 5126, 39 | "extras": {}, 40 | "type": "SCALAR", 41 | "min": [ 42 | 0.0 43 | ], 44 | "max": [ 45 | 1.0 46 | ], 47 | "sparse": { 48 | "count": 1, 49 | "indices": { 50 | "bufferView": 2, 51 | "byteOffset": 0, 52 | "componentType": 5125, 53 | "extras": {} 54 | }, 55 | "values": { 56 | "bufferView": 3, 57 | "byteOffset": 0, 58 | "extras": {} 59 | }, 60 | "extras": {} 61 | } 62 | }, 63 | { 64 | "bufferView": 4, 65 | "byteOffset": 0, 66 | "count": 2, 67 | "componentType": 5126, 68 | "extras": {}, 69 | "type": "SCALAR", 70 | "min": [ 71 | 1.0 72 | ], 73 | "max": [ 74 | 2.0 75 | ] 76 | }, 77 | { 78 | "bufferView": 5, 79 | "byteOffset": 0, 80 | "count": 8, 81 | "componentType": 5126, 82 | "extras": {}, 83 | "type": "VEC3", 84 | "min": [ 85 | -0.5, 86 | -0.5, 87 | -0.5 88 | ], 89 | "max": [ 90 | 0.0, 91 | 0.0, 92 | 0.0 93 | ] 94 | } 95 | ], 96 | "animations": [ 97 | { 98 | "extras": {}, 99 | "channels": [ 100 | { 101 | "sampler": 0, 102 | "target": { 103 | "extras": {}, 104 | "node": 0, 105 | "path": "weights" 106 | }, 107 | "extras": {} 108 | } 109 | ], 110 | "samplers": [ 111 | { 112 | "extras": {}, 113 | "input": 3, 114 | "interpolation": "LINEAR", 115 | "output": 2 116 | } 117 | ] 118 | } 119 | ], 120 | "asset": { 121 | "extras": {}, 122 | "generator": "gltfgen v0.2.0", 123 | "version": "2.0" 124 | }, 125 | "buffers": [ 126 | { 127 | "byteLength": 352, 128 | "uri": "box_sparse.bin", 129 | "extras": {} 130 | } 131 | ], 132 | "bufferViews": [ 133 | { 134 | "buffer": 0, 135 | "byteLength": 144, 136 | "byteOffset": 0, 137 | "target": 34963, 138 | "extras": {} 139 | }, 140 | { 141 | "buffer": 0, 142 | "byteLength": 96, 143 | "byteOffset": 144, 144 | "byteStride": 12, 145 | "target": 34962, 146 | "extras": {} 147 | }, 148 | { 149 | "buffer": 0, 150 | "byteLength": 4, 151 | "byteOffset": 240, 152 | "extras": {} 153 | }, 154 | { 155 | "buffer": 0, 156 | "byteLength": 4, 157 | "byteOffset": 244, 158 | "extras": {} 159 | }, 160 | { 161 | "buffer": 0, 162 | "byteLength": 8, 163 | "byteOffset": 248, 164 | "extras": {} 165 | }, 166 | { 167 | "buffer": 0, 168 | "byteLength": 96, 169 | "byteOffset": 256, 170 | "byteStride": 12, 171 | "target": 34962, 172 | "extras": {} 173 | } 174 | ], 175 | "extras": {}, 176 | "meshes": [ 177 | { 178 | "extras": {}, 179 | "primitives": [ 180 | { 181 | "attributes": { 182 | "POSITION": 1 183 | }, 184 | "extras": {}, 185 | "indices": 0, 186 | "targets": [ 187 | { 188 | "POSITION": 4 189 | } 190 | ] 191 | } 192 | ] 193 | } 194 | ], 195 | "nodes": [ 196 | { 197 | "extras": {}, 198 | "mesh": 0, 199 | "name": "box" 200 | } 201 | ], 202 | "scenes": [ 203 | { 204 | "extras": {}, 205 | "nodes": [ 206 | 0 207 | ] 208 | } 209 | ] 210 | } 211 | -------------------------------------------------------------------------------- /gltf-json/src/extensions/root.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | #[cfg(feature = "extensions")] 4 | use serde_json::{Map, Value}; 5 | 6 | /// The root object of a glTF 2.0 asset. 7 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 8 | pub struct Root { 9 | #[cfg(feature = "KHR_lights_punctual")] 10 | #[serde( 11 | default, 12 | rename = "KHR_lights_punctual", 13 | skip_serializing_if = "Option::is_none" 14 | )] 15 | pub khr_lights_punctual: Option, 16 | 17 | #[cfg(feature = "KHR_materials_variants")] 18 | #[serde( 19 | default, 20 | rename = "KHR_materials_variants", 21 | skip_serializing_if = "Option::is_none" 22 | )] 23 | pub khr_materials_variants: Option, 24 | 25 | #[cfg(feature = "extensions")] 26 | #[serde(default, flatten)] 27 | pub others: Map, 28 | } 29 | 30 | #[cfg(feature = "KHR_lights_punctual")] 31 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 32 | pub struct KhrLightsPunctual { 33 | /// Lights at this node. 34 | pub lights: Vec, 35 | } 36 | 37 | #[cfg(feature = "KHR_lights_punctual")] 38 | impl crate::root::Get for crate::Root { 39 | fn get( 40 | &self, 41 | id: crate::Index, 42 | ) -> Option<&crate::extensions::scene::khr_lights_punctual::Light> { 43 | if let Some(extensions) = self.extensions.as_ref() { 44 | if let Some(khr_lights_punctual) = extensions.khr_lights_punctual.as_ref() { 45 | khr_lights_punctual.lights.get(id.value()) 46 | } else { 47 | None 48 | } 49 | } else { 50 | None 51 | } 52 | } 53 | } 54 | 55 | #[cfg(feature = "KHR_lights_punctual")] 56 | impl AsRef<[crate::extensions::scene::khr_lights_punctual::Light]> for crate::Root { 57 | fn as_ref(&self) -> &[crate::extensions::scene::khr_lights_punctual::Light] { 58 | self.extensions 59 | .as_ref() 60 | .and_then(|extensions| extensions.khr_lights_punctual.as_ref()) 61 | .map(|khr_lights_punctual| khr_lights_punctual.lights.as_slice()) 62 | .unwrap_or(&[]) 63 | } 64 | } 65 | #[cfg(feature = "KHR_lights_punctual")] 66 | impl AsMut> for crate::Root { 67 | fn as_mut(&mut self) -> &mut Vec { 68 | &mut self 69 | .extensions 70 | .get_or_insert_with(Default::default) 71 | .khr_lights_punctual 72 | .get_or_insert_with(Default::default) 73 | .lights 74 | } 75 | } 76 | 77 | #[cfg(feature = "KHR_materials_variants")] 78 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 79 | pub struct KhrMaterialsVariants { 80 | pub variants: Vec, 81 | } 82 | 83 | #[cfg(feature = "KHR_materials_variants")] 84 | impl crate::root::Get for crate::Root { 85 | fn get( 86 | &self, 87 | id: crate::Index, 88 | ) -> Option<&crate::extensions::scene::khr_materials_variants::Variant> { 89 | self.extensions 90 | .as_ref()? 91 | .khr_materials_variants 92 | .as_ref()? 93 | .variants 94 | .get(id.value()) 95 | } 96 | } 97 | 98 | #[cfg(feature = "KHR_materials_variants")] 99 | impl AsRef<[crate::extensions::scene::khr_materials_variants::Variant]> for crate::Root { 100 | fn as_ref(&self) -> &[crate::extensions::scene::khr_materials_variants::Variant] { 101 | self.extensions 102 | .as_ref() 103 | .and_then(|extensions| extensions.khr_materials_variants.as_ref()) 104 | .map(|khr_materials_variants| khr_materials_variants.variants.as_slice()) 105 | .unwrap_or(&[]) 106 | } 107 | } 108 | #[cfg(feature = "KHR_materials_variants")] 109 | impl AsMut> for crate::Root { 110 | fn as_mut(&mut self) -> &mut Vec { 111 | &mut self 112 | .extensions 113 | .get_or_insert_with(Default::default) 114 | .khr_materials_variants 115 | .get_or_insert_with(Default::default) 116 | .variants 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/accessor/sparse.rs: -------------------------------------------------------------------------------- 1 | use crate::{buffer, Document}; 2 | 3 | /// The index data type. 4 | #[derive(Clone, Debug)] 5 | pub enum IndexType { 6 | /// Corresponds to `GL_UNSIGNED_BYTE`. 7 | U8 = 5121, 8 | 9 | /// Corresponds to `GL_UNSIGNED_SHORT`. 10 | U16 = 5123, 11 | 12 | /// Corresponds to `GL_UNSIGNED_INT`. 13 | U32 = 5125, 14 | } 15 | 16 | /// Indices of those attributes that deviate from their initialization value. 17 | pub struct Indices<'a> { 18 | /// The parent `Document` struct. 19 | document: &'a Document, 20 | 21 | /// The corresponding JSON struct. 22 | json: &'a json::accessor::sparse::Indices, 23 | } 24 | 25 | impl<'a> Indices<'a> { 26 | /// Constructs `sparse::Indices`. 27 | pub(crate) fn new(document: &'a Document, json: &'a json::accessor::sparse::Indices) -> Self { 28 | Self { document, json } 29 | } 30 | 31 | /// Returns the buffer view containing the sparse indices. 32 | pub fn view(&self) -> buffer::View<'a> { 33 | self.document 34 | .views() 35 | .nth(self.json.buffer_view.value()) 36 | .unwrap() 37 | } 38 | 39 | /// The offset relative to the start of the parent buffer view in bytes. 40 | pub fn offset(&self) -> usize { 41 | self.json.byte_offset.0 as usize 42 | } 43 | 44 | /// The data type of each index. 45 | pub fn index_type(&self) -> IndexType { 46 | match self.json.component_type.unwrap().0 { 47 | json::accessor::ComponentType::U8 => IndexType::U8, 48 | json::accessor::ComponentType::U16 => IndexType::U16, 49 | json::accessor::ComponentType::U32 => IndexType::U32, 50 | _ => unreachable!(), 51 | } 52 | } 53 | 54 | /// Optional application specific data. 55 | pub fn extras(&self) -> &'a json::Extras { 56 | &self.json.extras 57 | } 58 | } 59 | 60 | /// Sparse storage of attributes that deviate from their initialization value. 61 | pub struct Sparse<'a> { 62 | /// The parent `Document` struct. 63 | document: &'a Document, 64 | 65 | /// The corresponding JSON struct. 66 | json: &'a json::accessor::sparse::Sparse, 67 | } 68 | 69 | impl<'a> Sparse<'a> { 70 | /// Constructs `Sparse`. 71 | pub(crate) fn new(document: &'a Document, json: &'a json::accessor::sparse::Sparse) -> Self { 72 | Self { document, json } 73 | } 74 | 75 | /// Returns the number of attributes encoded in this sparse accessor. 76 | pub fn count(&self) -> usize { 77 | self.json.count.0 as usize 78 | } 79 | 80 | /// Returns an index array of size `count` that points to those accessor 81 | /// attributes that deviate from their initialization value. 82 | pub fn indices(&self) -> Indices<'a> { 83 | Indices::new(self.document, &self.json.indices) 84 | } 85 | 86 | /// Returns an array of size `count * number_of_components`, storing the 87 | /// displaced accessor attributes pointed by `indices`. 88 | pub fn values(&self) -> Values<'a> { 89 | Values::new(self.document, &self.json.values) 90 | } 91 | 92 | /// Optional application specific data. 93 | pub fn extras(&self) -> &'a json::Extras { 94 | &self.json.extras 95 | } 96 | } 97 | 98 | /// Array of size `count * number_of_components` storing the displaced accessor 99 | /// attributes pointed by `accessor::sparse::Indices`. 100 | pub struct Values<'a> { 101 | /// The parent `Document` struct. 102 | document: &'a Document, 103 | 104 | /// The corresponding JSON struct. 105 | json: &'a json::accessor::sparse::Values, 106 | } 107 | 108 | impl<'a> Values<'a> { 109 | /// Constructs `sparse::Values`. 110 | pub(crate) fn new(document: &'a Document, json: &'a json::accessor::sparse::Values) -> Self { 111 | Self { document, json } 112 | } 113 | 114 | /// Returns the buffer view containing the sparse values. 115 | pub fn view(&self) -> buffer::View<'a> { 116 | self.document 117 | .views() 118 | .nth(self.json.buffer_view.value()) 119 | .unwrap() 120 | } 121 | 122 | /// The offset relative to the start of the parent buffer view in bytes. 123 | pub fn offset(&self) -> usize { 124 | self.json.byte_offset.0 as usize 125 | } 126 | 127 | /// Optional application specific data. 128 | pub fn extras(&self) -> &'a json::Extras { 129 | &self.json.extras 130 | } 131 | } 132 | 133 | impl IndexType { 134 | /// Returns the number of bytes this value represents. 135 | pub fn size(&self) -> usize { 136 | use self::IndexType::*; 137 | match *self { 138 | U8 => 1, 139 | U16 => 2, 140 | U32 => 4, 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/khr_lights_punctual.rs: -------------------------------------------------------------------------------- 1 | use crate::Document; 2 | use gltf_json::Extras; 3 | 4 | /// A light in the scene. 5 | pub struct Light<'a> { 6 | /// The parent `Document` struct. 7 | #[allow(dead_code)] 8 | document: &'a Document, 9 | 10 | /// The corresponding JSON index. 11 | index: usize, 12 | 13 | /// The corresponding JSON struct. 14 | json: &'a json::extensions::scene::khr_lights_punctual::Light, 15 | } 16 | 17 | impl<'a> Light<'a> { 18 | /// Constructs a `Light`. 19 | pub(crate) fn new( 20 | document: &'a Document, 21 | index: usize, 22 | json: &'a json::extensions::scene::khr_lights_punctual::Light, 23 | ) -> Self { 24 | Self { 25 | document, 26 | index, 27 | json, 28 | } 29 | } 30 | 31 | /// Color of the light source. 32 | pub fn color(&self) -> [f32; 3] { 33 | self.json.color 34 | } 35 | 36 | /// Returns the internal JSON index. 37 | pub fn index(&self) -> usize { 38 | self.index 39 | } 40 | 41 | /// Optional user-defined name for this object. 42 | #[cfg(feature = "names")] 43 | pub fn name(&self) -> Option<&'a str> { 44 | self.json.name.as_deref() 45 | } 46 | 47 | /// Optional application specific data. 48 | pub fn extras(&self) -> &'a Extras { 49 | &self.json.extras 50 | } 51 | 52 | /// Intensity of the light source. `point` and `spot` lights use luminous intensity 53 | /// in candela (lm/sr) while `directional` lights use illuminance in lux (lm/m^2). 54 | pub fn intensity(&self) -> f32 { 55 | self.json.intensity 56 | } 57 | 58 | /// A distance cutoff at which the light's intensity may be considered to have reached 59 | /// zero. 60 | pub fn range(&self) -> Option { 61 | self.json.range 62 | } 63 | 64 | /// Specifies the light subcategory. 65 | pub fn kind(&self) -> Kind { 66 | use json::extensions::scene::khr_lights_punctual::Type; 67 | match self.json.type_.unwrap() { 68 | Type::Directional => Kind::Directional, 69 | Type::Point => Kind::Point, 70 | Type::Spot => { 71 | let args = self.json.spot.as_ref().unwrap(); 72 | Kind::Spot { 73 | inner_cone_angle: args.inner_cone_angle, 74 | outer_cone_angle: args.outer_cone_angle, 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | /// Light subcategory. 82 | pub enum Kind { 83 | /// Directional lights are light sources that act as though they are infinitely far away 84 | /// and emit light in the direction of the local -z axis. This light type inherits the 85 | /// orientation of the node that it belongs to; position and scale are ignored except for 86 | /// their effect on the inherited node orientation. Because it is at an infinite distance, 87 | /// the light is not attenuated. Its intensity is defined in lumens per metre squared, or 88 | /// lux (lm/m2). 89 | Directional, 90 | 91 | /// Point lights emit light in all directions from their position in space; rotation and 92 | /// scale are ignored except for their effect on the inherited node position. The 93 | /// brightness of the light attenuates in a physically correct manner as distance 94 | /// increases from the light's position (i.e. brightness goes like the inverse square of 95 | /// the distance). Point light intensity is defined in candela, which is lumens per square 96 | /// radian (lm/sr). 97 | Point, 98 | 99 | /// Spot lights emit light in a cone in the direction of the local -z axis. The angle and 100 | /// falloff of the cone is defined using two numbers, the `inner_cone_angle` and 101 | /// `outer_cone_angle`. As with point lights, the brightness also attenuates in a 102 | /// physically correct manner as distance increases from the light's position (i.e. 103 | /// brightness goes like the inverse square of the distance). Spot light intensity refers 104 | /// to the brightness inside the `inner_cone_angle` (and at the location of the light) and 105 | /// is defined in candela, which is lumens per square radian (lm/sr). Engines that don't 106 | /// support two angles for spotlights should use `outer_cone_angle` as the spotlight angle 107 | /// (leaving `inner_cone_angle` to implicitly be 0). 108 | /// 109 | /// A spot light's position and orientation are inherited from its node transform. 110 | /// Inherited scale does not affect cone shape, and is ignored except for its effect on 111 | /// position and orientation. 112 | Spot { 113 | /// Angle in radians from centre of spotlight where falloff begins. 114 | inner_cone_angle: f32, 115 | 116 | /// Angle in radians from centre of spotlight where falloff ends. 117 | outer_cone_angle: f32, 118 | }, 119 | } 120 | -------------------------------------------------------------------------------- /gltf-json/src/scene.rs: -------------------------------------------------------------------------------- 1 | use crate::validation::Validate; 2 | use crate::{camera, extensions, mesh, scene, skin, Extras, Index}; 3 | use gltf_derive::Validate; 4 | use serde_derive::{Deserialize, Serialize}; 5 | 6 | /// A node in the node hierarchy. When the node contains `skin`, all 7 | /// `mesh.primitives` must contain `JOINTS_0` and `WEIGHTS_0` attributes. 8 | /// A node can have either a `matrix` or any combination of 9 | /// `translation`/`rotation`/`scale` (TRS) properties. TRS properties are converted 10 | /// to matrices and postmultiplied in the `T * R * S` order to compose the 11 | /// transformation matrix; first the scale is applied to the vertices, then the 12 | /// rotation, and then the translation. If none are provided, the transform is the 13 | /// identity. When a node is targeted for animation (referenced by an 14 | /// animation.channel.target), only TRS properties may be present; `matrix` will not 15 | /// be present. 16 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 17 | pub struct Node { 18 | /// The index of the camera referenced by this node. 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub camera: Option>, 21 | 22 | /// The indices of this node's children. 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | pub children: Option>>, 25 | 26 | /// Extension specific data. 27 | #[serde(default, skip_serializing_if = "Option::is_none")] 28 | pub extensions: Option, 29 | 30 | /// Optional application specific data. 31 | #[serde(default)] 32 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 33 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 34 | pub extras: Extras, 35 | 36 | /// 4x4 column-major transformation matrix. 37 | /// 38 | /// glTF 2.0 specification: 39 | /// When a node is targeted for animation (referenced by an 40 | /// animation.channel.target), only TRS properties may be present; 41 | /// matrix will not be present. 42 | /// 43 | /// TODO: Ensure that .matrix is set to None or otherwise skipped during 44 | /// serialization, if the node is targeted for animation. 45 | /// 46 | #[serde(skip_serializing_if = "Option::is_none")] 47 | pub matrix: Option<[f32; 16]>, 48 | 49 | /// The index of the mesh in this node. 50 | #[serde(skip_serializing_if = "Option::is_none")] 51 | pub mesh: Option>, 52 | 53 | /// Optional user-defined name for this object. 54 | #[cfg(feature = "names")] 55 | #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] 56 | pub name: Option, 57 | 58 | /// The node's unit quaternion rotation in the order (x, y, z, w), where w is 59 | /// the scalar. 60 | #[serde(skip_serializing_if = "Option::is_none")] 61 | pub rotation: Option, 62 | 63 | /// The node's non-uniform scale. 64 | #[serde(skip_serializing_if = "Option::is_none")] 65 | pub scale: Option<[f32; 3]>, 66 | 67 | /// The node's translation. 68 | #[serde(skip_serializing_if = "Option::is_none")] 69 | pub translation: Option<[f32; 3]>, 70 | 71 | /// The index of the skin referenced by this node. 72 | #[serde(skip_serializing_if = "Option::is_none")] 73 | pub skin: Option>, 74 | 75 | /// The weights of the instantiated Morph Target. Number of elements must match 76 | /// the number of Morph Targets of used mesh. 77 | #[serde(skip_serializing_if = "Option::is_none")] 78 | pub weights: Option>, 79 | } 80 | 81 | /// The root `Node`s of a scene. 82 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 83 | pub struct Scene { 84 | /// Extension specific data. 85 | #[serde(default, skip_serializing_if = "Option::is_none")] 86 | pub extensions: Option, 87 | 88 | /// Optional application specific data. 89 | #[serde(default)] 90 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 91 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 92 | pub extras: Extras, 93 | 94 | /// Optional user-defined name for this object. 95 | #[cfg(feature = "names")] 96 | #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] 97 | pub name: Option, 98 | 99 | /// The indices of each root node. 100 | #[serde(skip_serializing_if = "Vec::is_empty")] 101 | pub nodes: Vec>, 102 | } 103 | 104 | /// Unit quaternion rotation in the order (x, y, z, w), where w is the scalar. 105 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 106 | pub struct UnitQuaternion(pub [f32; 4]); 107 | 108 | impl Default for UnitQuaternion { 109 | fn default() -> Self { 110 | UnitQuaternion([0.0, 0.0, 0.0, 1.0]) 111 | } 112 | } 113 | 114 | impl Validate for UnitQuaternion {} 115 | -------------------------------------------------------------------------------- /gltf-json/src/extensions/texture.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "KHR_texture_transform", feature = "EXT_texture_webp"))] 2 | use crate::{extras::Extras, validation::Validate}; 3 | #[cfg(feature = "EXT_texture_webp")] 4 | use crate::{image, Index}; 5 | 6 | use gltf_derive::Validate; 7 | use serde_derive::{Deserialize, Serialize}; 8 | #[cfg(feature = "extensions")] 9 | use serde_json::{Map, Value}; 10 | 11 | /// Texture sampler properties for filtering and wrapping modes. 12 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 13 | pub struct Sampler { 14 | #[cfg(feature = "extensions")] 15 | #[serde(default, flatten)] 16 | pub others: Map, 17 | } 18 | 19 | /// A texture and its sampler. 20 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 21 | pub struct Texture { 22 | #[cfg(feature = "extensions")] 23 | #[serde(default, flatten)] 24 | pub others: Map, 25 | 26 | #[cfg(feature = "EXT_texture_webp")] 27 | #[serde( 28 | default, 29 | rename = "EXT_texture_webp", 30 | skip_serializing_if = "Option::is_none" 31 | )] 32 | pub texture_webp: Option, 33 | } 34 | 35 | #[cfg(feature = "EXT_texture_webp")] 36 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 37 | pub struct TextureWebp { 38 | /// The index of the webp image used by the texture. 39 | pub source: Index, 40 | } 41 | 42 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 43 | /// Reference to a `Texture`. 44 | pub struct Info { 45 | #[cfg(feature = "KHR_texture_transform")] 46 | #[serde( 47 | default, 48 | rename = "KHR_texture_transform", 49 | skip_serializing_if = "Option::is_none" 50 | )] 51 | pub texture_transform: Option, 52 | #[cfg(feature = "extensions")] 53 | #[serde(default, flatten)] 54 | pub others: Map, 55 | } 56 | 57 | /// Many techniques can be used to optimize resource usage for a 3d scene. 58 | /// Chief among them is the ability to minimize the number of textures the GPU must load. 59 | /// To achieve this, many engines encourage packing many objects' low-resolution textures into a single large texture atlas. 60 | /// The region of the resulting atlas that corresponds with each object is then defined by vertical and horizontal offsets, 61 | /// and the width and height of the region. 62 | /// 63 | /// To support this use case, this extension adds `offset`, `rotation`, and `scale` properties to textureInfo structures. 64 | /// These properties would typically be implemented as an affine transform on the UV coordinates. 65 | #[cfg(feature = "KHR_texture_transform")] 66 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 67 | #[serde(default, rename_all = "camelCase")] 68 | pub struct TextureTransform { 69 | // The offset of the UV coordinate origin as a factor of the texture dimensions. 70 | pub offset: TextureTransformOffset, 71 | 72 | /// Rotate the UVs by this many radians counter-clockwise around the origin. 73 | /// This is equivalent to a similar rotation of the image clockwise. 74 | pub rotation: TextureTransformRotation, 75 | 76 | /// The scale factor applied to the components of the UV coordinates. 77 | pub scale: TextureTransformScale, 78 | 79 | /// Overrides the textureInfo texCoord value if supplied, and if this extension is supported. 80 | pub tex_coord: Option, 81 | 82 | /// Optional application specific data. 83 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 84 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 85 | pub extras: Extras, 86 | } 87 | 88 | /// The offset of the UV coordinate origin as a factor of the texture dimensions. 89 | #[cfg(feature = "KHR_texture_transform")] 90 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 91 | pub struct TextureTransformOffset(pub [f32; 2]); 92 | 93 | #[cfg(feature = "KHR_texture_transform")] 94 | impl Default for TextureTransformOffset { 95 | fn default() -> Self { 96 | Self([0.0, 0.0]) 97 | } 98 | } 99 | 100 | #[cfg(feature = "KHR_texture_transform")] 101 | impl Validate for TextureTransformOffset {} 102 | 103 | /// Rotate the UVs by this many radians counter-clockwise around the origin. 104 | /// This is equivalent to a similar rotation of the image clockwise. 105 | #[cfg(feature = "KHR_texture_transform")] 106 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 107 | pub struct TextureTransformRotation(pub f32); 108 | 109 | #[cfg(feature = "KHR_texture_transform")] 110 | impl Default for TextureTransformRotation { 111 | fn default() -> Self { 112 | Self(0.0) 113 | } 114 | } 115 | 116 | #[cfg(feature = "KHR_texture_transform")] 117 | impl Validate for TextureTransformRotation {} 118 | 119 | /// The scale factor applied to the components of the UV coordinates. 120 | #[cfg(feature = "KHR_texture_transform")] 121 | #[derive(Clone, Copy, Debug, Deserialize, Serialize)] 122 | pub struct TextureTransformScale(pub [f32; 2]); 123 | 124 | #[cfg(feature = "KHR_texture_transform")] 125 | impl Default for TextureTransformScale { 126 | fn default() -> Self { 127 | Self([1.0, 1.0]) 128 | } 129 | } 130 | 131 | #[cfg(feature = "KHR_texture_transform")] 132 | impl Validate for TextureTransformScale {} 133 | -------------------------------------------------------------------------------- /tests/test_wrapper.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::{fs, io}; 3 | 4 | use gltf::mesh::Bounds; 5 | 6 | #[test] 7 | fn test_accessor_bounds() { 8 | // file derived from minimal.gltf with changed min/max values 9 | let file = fs::File::open("tests/minimal_accessor_min_max.gltf").unwrap(); 10 | let mut reader = io::BufReader::new(file); 11 | let mut buffer = vec![]; 12 | reader.read_to_end(&mut buffer).unwrap(); 13 | let gltf = gltf::Gltf::from_slice(&buffer).unwrap(); 14 | let mesh = &gltf.meshes().next().unwrap(); 15 | let prim = mesh.primitives().next().unwrap(); 16 | let bounds = prim.bounding_box(); 17 | assert_eq!( 18 | bounds, 19 | Bounds { 20 | min: [-0.03, -0.04, -0.05], 21 | max: [1.0, 1.01, 0.02] 22 | } 23 | ); 24 | } 25 | 26 | /// "SimpleSparseAccessor.gltf" contains positions specified with a sparse accessor. 27 | /// The accessor use a base `bufferView` that contains 14 `Vec3`s and the sparse 28 | /// section overwrites 3 of these with other values when read. 29 | const SIMPLE_SPARSE_ACCESSOR_GLTF: &str = 30 | "glTF-Sample-Assets/Models/SimpleSparseAccessor/glTF-Embedded/SimpleSparseAccessor.gltf"; 31 | 32 | #[test] 33 | fn test_sparse_accessor_with_base_buffer_view_yield_exact_size_hints() { 34 | let (document, buffers, _) = gltf::import(SIMPLE_SPARSE_ACCESSOR_GLTF).unwrap(); 35 | 36 | let mesh = document.meshes().next().unwrap(); 37 | let primitive = mesh.primitives().next().unwrap(); 38 | let reader = primitive 39 | .reader(|buffer: gltf::Buffer| buffers.get(buffer.index()).map(|data| &data.0[..])); 40 | let mut positions = reader.read_positions().unwrap(); 41 | 42 | const EXPECTED_POSITION_COUNT: usize = 14; 43 | for i in (0..=EXPECTED_POSITION_COUNT).rev() { 44 | assert_eq!(positions.size_hint(), (i, Some(i))); 45 | positions.next(); 46 | } 47 | } 48 | 49 | #[test] 50 | fn test_sparse_accessor_with_base_buffer_view_yield_all_values() { 51 | let (document, buffers, _) = gltf::import(SIMPLE_SPARSE_ACCESSOR_GLTF).unwrap(); 52 | 53 | let mesh = document.meshes().next().unwrap(); 54 | let primitive = mesh.primitives().next().unwrap(); 55 | let reader = primitive 56 | .reader(|buffer: gltf::Buffer| buffers.get(buffer.index()).map(|data| &data.0[..])); 57 | let positions: Vec<[f32; 3]> = reader.read_positions().unwrap().collect::>(); 58 | 59 | const EXPECTED_POSITIONS: [[f32; 3]; 14] = [ 60 | [0.0, 0.0, 0.0], 61 | [1.0, 0.0, 0.0], 62 | [2.0, 0.0, 0.0], 63 | [3.0, 0.0, 0.0], 64 | [4.0, 0.0, 0.0], 65 | [5.0, 0.0, 0.0], 66 | [6.0, 0.0, 0.0], 67 | [0.0, 1.0, 0.0], 68 | [1.0, 2.0, 0.0], // Sparse value #1 69 | [2.0, 1.0, 0.0], 70 | [3.0, 3.0, 0.0], // Sparse value #2 71 | [4.0, 1.0, 0.0], 72 | [5.0, 4.0, 0.0], // Sparse value #3 73 | [6.0, 1.0, 0.0], 74 | ]; 75 | assert_eq!(positions.len(), EXPECTED_POSITIONS.len()); 76 | for (i, p) in positions.iter().enumerate() { 77 | for (j, q) in p.iter().enumerate() { 78 | assert_eq!(q - EXPECTED_POSITIONS[i][j], 0.0); 79 | } 80 | } 81 | } 82 | 83 | /// "box_sparse.gltf" contains an animation with a sampler with output of two values. 84 | /// The values are specified with a sparse accessor that is missing a base `bufferView` field. 85 | /// Which means that each value in it will be 0.0, except the values contained in the sparse 86 | /// buffer view itself. In this case the second value is read from the sparse accessor (1.0), 87 | /// while the first is left at the default zero. 88 | const BOX_SPARSE_GLTF: &str = "tests/box_sparse.gltf"; 89 | 90 | #[test] 91 | fn test_sparse_accessor_without_base_buffer_view_yield_exact_size_hints() { 92 | let (document, buffers, _) = gltf::import(BOX_SPARSE_GLTF).unwrap(); 93 | 94 | let animation = document.animations().next().unwrap(); 95 | let sampler = animation.samplers().next().unwrap(); 96 | let output_accessor = sampler.output(); 97 | let mut outputs_iter = 98 | gltf::accessor::Iter::::new(output_accessor, |buffer: gltf::Buffer| { 99 | buffers.get(buffer.index()).map(|data| &data.0[..]) 100 | }) 101 | .unwrap(); 102 | 103 | const EXPECTED_OUTPUT_COUNT: usize = 2; 104 | for i in (0..=EXPECTED_OUTPUT_COUNT).rev() { 105 | assert_eq!(outputs_iter.size_hint(), (i, Some(i))); 106 | outputs_iter.next(); 107 | } 108 | } 109 | 110 | #[test] 111 | fn test_sparse_accessor_without_base_buffer_view_yield_all_values() { 112 | let (document, buffers, _) = gltf::import(BOX_SPARSE_GLTF).unwrap(); 113 | 114 | let animation = document.animations().next().unwrap(); 115 | let sampler = animation.samplers().next().unwrap(); 116 | let output_accessor = sampler.output(); 117 | let output_iter = gltf::accessor::Iter::::new(output_accessor, |buffer: gltf::Buffer| { 118 | buffers.get(buffer.index()).map(|data| &data.0[..]) 119 | }) 120 | .unwrap(); 121 | let outputs = output_iter.collect::>(); 122 | 123 | const EXPECTED_OUTPUTS: [f32; 2] = [0.0, 1.0]; 124 | assert_eq!(outputs.len(), EXPECTED_OUTPUTS.len()); 125 | for (i, o) in outputs.iter().enumerate() { 126 | assert_eq!(o - EXPECTED_OUTPUTS[i], 0.0); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /gltf-json/src/camera.rs: -------------------------------------------------------------------------------- 1 | use crate::validation::{Checked, Error}; 2 | use crate::{extensions, Extras, Path, Root}; 3 | use gltf_derive::Validate; 4 | use serde::{de, ser}; 5 | use serde_derive::{Deserialize, Serialize}; 6 | use std::fmt; 7 | 8 | /// All valid camera types. 9 | pub const VALID_CAMERA_TYPES: &[&str] = &["perspective", "orthographic"]; 10 | 11 | /// Specifies the camera type. 12 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 13 | pub enum Type { 14 | /// A perspective projection. 15 | Perspective = 1, 16 | 17 | /// An orthographic projection. 18 | Orthographic, 19 | } 20 | 21 | /// A camera's projection. 22 | /// 23 | /// A node can reference a camera to apply a transform to place the camera in the 24 | /// scene. 25 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 26 | #[gltf(validate_hook = "camera_validate_hook")] 27 | pub struct Camera { 28 | /// Optional user-defined name for this object. 29 | #[cfg(feature = "names")] 30 | #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] 31 | pub name: Option, 32 | 33 | /// An orthographic camera containing properties to create an orthographic 34 | /// projection matrix. 35 | #[serde(skip_serializing_if = "Option::is_none")] 36 | pub orthographic: Option, 37 | 38 | /// A perspective camera containing properties to create a perspective 39 | /// projection matrix. 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | pub perspective: Option, 42 | 43 | /// Specifies if the camera uses a perspective or orthographic projection. 44 | #[serde(rename = "type")] 45 | pub type_: Checked, 46 | 47 | /// Extension specific data. 48 | #[serde(default, skip_serializing_if = "Option::is_none")] 49 | pub extensions: Option, 50 | 51 | /// Optional application specific data. 52 | #[serde(default)] 53 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 54 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 55 | pub extras: Extras, 56 | } 57 | 58 | fn camera_validate_hook(camera: &Camera, _root: &Root, path: P, report: &mut R) 59 | where 60 | P: Fn() -> Path, 61 | R: FnMut(&dyn Fn() -> Path, Error), 62 | { 63 | if camera.orthographic.is_none() && camera.perspective.is_none() { 64 | report(&path, Error::Missing); 65 | } 66 | } 67 | 68 | /// Values for an orthographic camera. 69 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 70 | pub struct Orthographic { 71 | /// The horizontal magnification of the view. 72 | pub xmag: f32, 73 | 74 | /// The vertical magnification of the view. 75 | pub ymag: f32, 76 | 77 | /// The distance to the far clipping plane. 78 | pub zfar: f32, 79 | 80 | /// The distance to the near clipping plane. 81 | pub znear: f32, 82 | 83 | /// Extension specific data. 84 | #[serde(default, skip_serializing_if = "Option::is_none")] 85 | pub extensions: Option, 86 | 87 | /// Optional application specific data. 88 | #[serde(default)] 89 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 90 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 91 | pub extras: Extras, 92 | } 93 | 94 | /// Values for a perspective camera. 95 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 96 | pub struct Perspective { 97 | /// Aspect ratio of the field of view. 98 | #[serde(rename = "aspectRatio")] 99 | #[serde(skip_serializing_if = "Option::is_none")] 100 | pub aspect_ratio: Option, 101 | 102 | /// The vertical field of view in radians. 103 | pub yfov: f32, 104 | 105 | /// The distance to the far clipping plane. 106 | #[serde(skip_serializing_if = "Option::is_none")] 107 | pub zfar: Option, 108 | 109 | /// The distance to the near clipping plane. 110 | pub znear: f32, 111 | 112 | /// Extension specific data. 113 | #[serde(default, skip_serializing_if = "Option::is_none")] 114 | pub extensions: Option, 115 | 116 | /// Optional application specific data. 117 | #[serde(default)] 118 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 119 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 120 | pub extras: Extras, 121 | } 122 | 123 | impl<'de> de::Deserialize<'de> for Checked { 124 | fn deserialize(deserializer: D) -> Result 125 | where 126 | D: de::Deserializer<'de>, 127 | { 128 | struct Visitor; 129 | impl de::Visitor<'_> for Visitor { 130 | type Value = Checked; 131 | 132 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 133 | write!(f, "any of: {:?}", VALID_CAMERA_TYPES) 134 | } 135 | 136 | fn visit_str(self, value: &str) -> Result 137 | where 138 | E: de::Error, 139 | { 140 | use self::Type::*; 141 | use crate::validation::Checked::*; 142 | Ok(match value { 143 | "perspective" => Valid(Perspective), 144 | "orthographic" => Valid(Orthographic), 145 | _ => Invalid, 146 | }) 147 | } 148 | } 149 | deserializer.deserialize_str(Visitor) 150 | } 151 | } 152 | 153 | impl ser::Serialize for Type { 154 | fn serialize(&self, serializer: S) -> Result 155 | where 156 | S: ser::Serializer, 157 | { 158 | match *self { 159 | Type::Perspective => serializer.serialize_str("perspective"), 160 | Type::Orthographic => serializer.serialize_str("orthographic"), 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/image.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | use crate::{buffer, Document, Error, Result}; 3 | 4 | #[cfg(feature = "import")] 5 | #[cfg_attr(docsrs, doc(cfg(feature = "import")))] 6 | use image_crate::DynamicImage; 7 | #[cfg(feature = "extensions")] 8 | use serde_json::{Map, Value}; 9 | 10 | /// Format of image pixel data. 11 | #[cfg(feature = "import")] 12 | #[cfg_attr(docsrs, doc(cfg(feature = "import")))] 13 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 14 | pub enum Format { 15 | /// Red only. 16 | R8, 17 | 18 | /// Red, green. 19 | R8G8, 20 | 21 | /// Red, green, blue. 22 | R8G8B8, 23 | 24 | /// Red, green, blue, alpha. 25 | R8G8B8A8, 26 | 27 | /// Red only (16 bits). 28 | R16, 29 | 30 | /// Red, green (16 bits). 31 | R16G16, 32 | 33 | /// Red, green, blue (16 bits). 34 | R16G16B16, 35 | 36 | /// Red, green, blue, alpha (16 bits). 37 | R16G16B16A16, 38 | 39 | /// Red, green, blue (32 bits float) 40 | R32G32B32FLOAT, 41 | 42 | /// Red, green, blue, alpha (32 bits float) 43 | R32G32B32A32FLOAT, 44 | } 45 | 46 | /// Describes an image data source. 47 | #[derive(Clone, Debug)] 48 | pub enum Source<'a> { 49 | /// Image data is contained in a buffer view. 50 | View { 51 | /// The buffer view containing the encoded image data. 52 | view: buffer::View<'a>, 53 | 54 | /// The image data MIME type. 55 | mime_type: &'a str, 56 | }, 57 | 58 | /// Image data is contained in an external data source. 59 | Uri { 60 | /// The URI of the external data source. 61 | uri: &'a str, 62 | 63 | /// The image data MIME type, if provided. 64 | mime_type: Option<&'a str>, 65 | }, 66 | } 67 | 68 | /// Image data used to create a texture. 69 | #[derive(Clone, Debug)] 70 | pub struct Image<'a> { 71 | /// The parent `Document` struct. 72 | document: &'a Document, 73 | 74 | /// The corresponding JSON index. 75 | index: usize, 76 | 77 | /// The corresponding JSON struct. 78 | json: &'a json::image::Image, 79 | } 80 | 81 | /// Image data belonging to an imported glTF asset. 82 | #[cfg(feature = "import")] 83 | #[cfg_attr(docsrs, doc(cfg(feature = "import")))] 84 | #[derive(Clone, Debug)] 85 | pub struct Data { 86 | /// The image pixel data bytes. 87 | pub pixels: Vec, 88 | 89 | /// The image pixel data format. 90 | pub format: Format, 91 | 92 | /// The image width in pixels. 93 | pub width: u32, 94 | 95 | /// The image height in pixels. 96 | pub height: u32, 97 | } 98 | 99 | impl<'a> Image<'a> { 100 | /// Constructs an `Image` from owned data. 101 | pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::image::Image) -> Self { 102 | Self { 103 | document, 104 | index, 105 | json, 106 | } 107 | } 108 | 109 | /// Returns the internal JSON index. 110 | pub fn index(&self) -> usize { 111 | self.index 112 | } 113 | 114 | /// Optional user-defined name for this object. 115 | #[cfg(feature = "names")] 116 | #[cfg_attr(docsrs, doc(cfg(feature = "names")))] 117 | pub fn name(&self) -> Option<&'a str> { 118 | self.json.name.as_deref() 119 | } 120 | 121 | /// Returns the image data source. 122 | pub fn source(&self) -> Source<'a> { 123 | if let Some(index) = self.json.buffer_view.as_ref() { 124 | let view = self.document.views().nth(index.value()).unwrap(); 125 | let mime_type = self.json.mime_type.as_ref().map(|x| x.0.as_str()).unwrap(); 126 | Source::View { view, mime_type } 127 | } else { 128 | let uri = self.json.uri.as_ref().unwrap(); 129 | let mime_type = self.json.mime_type.as_ref().map(|x| x.0.as_str()); 130 | Source::Uri { uri, mime_type } 131 | } 132 | } 133 | 134 | /// Returns extension data unknown to this crate version. 135 | #[cfg(feature = "extensions")] 136 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 137 | pub fn extensions(&self) -> Option<&Map> { 138 | let ext = self.json.extensions.as_ref()?; 139 | Some(&ext.others) 140 | } 141 | 142 | /// Queries extension data unknown to this crate version. 143 | #[cfg(feature = "extensions")] 144 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 145 | pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { 146 | let ext = self.json.extensions.as_ref()?; 147 | ext.others.get(ext_name) 148 | } 149 | 150 | /// Optional application specific data. 151 | pub fn extras(&self) -> &'a json::Extras { 152 | &self.json.extras 153 | } 154 | } 155 | 156 | #[cfg(feature = "import")] 157 | impl Data { 158 | /// Note: We don't implement `From` since we don't want 159 | /// to expose such functionality to the user. 160 | pub(crate) fn new(image: DynamicImage) -> Result { 161 | use image_crate::GenericImageView; 162 | let format = match image { 163 | DynamicImage::ImageLuma8(_) => Format::R8, 164 | DynamicImage::ImageLumaA8(_) => Format::R8G8, 165 | DynamicImage::ImageRgb8(_) => Format::R8G8B8, 166 | DynamicImage::ImageRgba8(_) => Format::R8G8B8A8, 167 | DynamicImage::ImageLuma16(_) => Format::R16, 168 | DynamicImage::ImageLumaA16(_) => Format::R16G16, 169 | DynamicImage::ImageRgb16(_) => Format::R16G16B16, 170 | DynamicImage::ImageRgba16(_) => Format::R16G16B16A16, 171 | DynamicImage::ImageRgb32F(_) => Format::R32G32B32FLOAT, 172 | DynamicImage::ImageRgba32F(_) => Format::R32G32B32A32FLOAT, 173 | image => return Err(Error::UnsupportedImageFormat(image)), 174 | }; 175 | let (width, height) = image.dimensions(); 176 | let pixels = image.into_bytes(); 177 | Ok(Data { 178 | format, 179 | width, 180 | height, 181 | pixels, 182 | }) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/mesh/iter.rs: -------------------------------------------------------------------------------- 1 | use std::{collections, iter, slice}; 2 | 3 | use super::{Attribute, Mesh, MorphTarget, Primitive}; 4 | use crate::Document; 5 | 6 | /// An `Iterator` that visits the morph targets of a `Primitive`. 7 | #[derive(Clone, Debug)] 8 | pub struct MorphTargets<'a> { 9 | /// The parent `Document` struct. 10 | pub(crate) document: &'a Document, 11 | 12 | /// The internal JSON iterator. 13 | pub(crate) iter: slice::Iter<'a, json::mesh::MorphTarget>, 14 | } 15 | 16 | /// An `Iterator` that visits the attributes of a `Primitive`. 17 | #[derive(Clone, Debug)] 18 | pub struct Attributes<'a> { 19 | /// The parent `Document` struct. 20 | pub(crate) document: &'a Document, 21 | 22 | /// The parent `Primitive` struct. 23 | #[allow(dead_code)] 24 | pub(crate) prim: Primitive<'a>, 25 | 26 | /// The internal attribute iterator. 27 | pub(crate) iter: collections::btree_map::Iter< 28 | 'a, 29 | json::validation::Checked, 30 | json::Index, 31 | >, 32 | } 33 | 34 | /// An `Iterator` that visits the primitives of a `Mesh`. 35 | #[derive(Clone, Debug)] 36 | pub struct Primitives<'a> { 37 | /// The parent `Mesh` struct. 38 | pub(crate) mesh: Mesh<'a>, 39 | 40 | /// The internal JSON primitive iterator. 41 | pub(crate) iter: iter::Enumerate>, 42 | } 43 | 44 | impl<'a> ExactSizeIterator for Attributes<'a> {} 45 | impl<'a> Iterator for Attributes<'a> { 46 | type Item = Attribute<'a>; 47 | fn next(&mut self) -> Option { 48 | self.iter.next().map(|(key, index)| { 49 | let semantic = key.as_ref().unwrap().clone(); 50 | let accessor = self.document.accessors().nth(index.value()).unwrap(); 51 | (semantic, accessor) 52 | }) 53 | } 54 | 55 | fn size_hint(&self) -> (usize, Option) { 56 | self.iter.size_hint() 57 | } 58 | } 59 | 60 | impl<'a> ExactSizeIterator for Primitives<'a> {} 61 | impl<'a> Iterator for Primitives<'a> { 62 | type Item = Primitive<'a>; 63 | fn next(&mut self) -> Option { 64 | self.iter 65 | .next() 66 | .map(|(index, json)| Primitive::new(self.mesh.clone(), index, json)) 67 | } 68 | fn size_hint(&self) -> (usize, Option) { 69 | self.iter.size_hint() 70 | } 71 | fn count(self) -> usize { 72 | self.iter.count() 73 | } 74 | fn last(self) -> Option { 75 | let mesh = self.mesh; 76 | self.iter 77 | .last() 78 | .map(|(index, json)| Primitive::new(mesh, index, json)) 79 | } 80 | fn nth(&mut self, n: usize) -> Option { 81 | self.iter 82 | .nth(n) 83 | .map(|(index, json)| Primitive::new(self.mesh.clone(), index, json)) 84 | } 85 | } 86 | 87 | fn map_morph_target<'a>( 88 | document: &'a crate::Document, 89 | json: &json::mesh::MorphTarget, 90 | ) -> MorphTarget<'a> { 91 | let positions = json 92 | .positions 93 | .as_ref() 94 | .map(|index| document.accessors().nth(index.value()).unwrap()); 95 | let normals = json 96 | .normals 97 | .as_ref() 98 | .map(|index| document.accessors().nth(index.value()).unwrap()); 99 | let tangents = json 100 | .tangents 101 | .as_ref() 102 | .map(|index| document.accessors().nth(index.value()).unwrap()); 103 | MorphTarget { 104 | positions, 105 | normals, 106 | tangents, 107 | } 108 | } 109 | 110 | impl<'a> ExactSizeIterator for MorphTargets<'a> {} 111 | impl<'a> Iterator for MorphTargets<'a> { 112 | type Item = MorphTarget<'a>; 113 | fn next(&mut self) -> Option { 114 | self.iter 115 | .next() 116 | .map(|json| map_morph_target(self.document, json)) 117 | } 118 | fn size_hint(&self) -> (usize, Option) { 119 | self.iter.size_hint() 120 | } 121 | fn count(self) -> usize { 122 | self.iter.count() 123 | } 124 | fn last(self) -> Option { 125 | let document = self.document; 126 | self.iter 127 | .last() 128 | .map(|json| map_morph_target(document, json)) 129 | } 130 | fn nth(&mut self, n: usize) -> Option { 131 | self.iter 132 | .nth(n) 133 | .map(|json| map_morph_target(self.document, json)) 134 | } 135 | } 136 | 137 | /// An `Iterator` that visits the variant mappings of a `Mesh`. 138 | #[cfg(feature = "KHR_materials_variants")] 139 | #[derive(Clone, Debug)] 140 | pub struct Mappings<'a> { 141 | /// Internal mapping iterator. 142 | pub(crate) iter: slice::Iter<'a, json::extensions::mesh::Mapping>, 143 | 144 | /// The internal root glTF object. 145 | pub(crate) document: &'a Document, 146 | } 147 | 148 | #[cfg(feature = "KHR_materials_variants")] 149 | impl<'a> ExactSizeIterator for Mappings<'a> {} 150 | #[cfg(feature = "KHR_materials_variants")] 151 | impl<'a> Iterator for Mappings<'a> { 152 | type Item = crate::khr_materials_variants::Mapping<'a>; 153 | fn next(&mut self) -> Option { 154 | let document = self.document; 155 | self.iter 156 | .next() 157 | .map(|json| crate::khr_materials_variants::Mapping::new(document, json)) 158 | } 159 | fn size_hint(&self) -> (usize, Option) { 160 | self.iter.size_hint() 161 | } 162 | fn count(self) -> usize { 163 | self.iter.count() 164 | } 165 | fn last(self) -> Option { 166 | let document = self.document; 167 | self.iter 168 | .last() 169 | .map(|json| crate::khr_materials_variants::Mapping::new(document, json)) 170 | } 171 | fn nth(&mut self, n: usize) -> Option { 172 | let document = self.document; 173 | self.iter 174 | .nth(n) 175 | .map(|json| crate::khr_materials_variants::Mapping::new(document, json)) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /gltf-json/src/buffer.rs: -------------------------------------------------------------------------------- 1 | use crate::validation::{Checked, Error, USize64, Validate}; 2 | use crate::{extensions, Extras, Index, Path, Root}; 3 | use gltf_derive::Validate; 4 | use serde::{de, ser}; 5 | use serde_derive::{Deserialize, Serialize}; 6 | use std::fmt; 7 | 8 | /// Corresponds to `GL_ARRAY_BUFFER`. 9 | pub const ARRAY_BUFFER: u32 = 34_962; 10 | 11 | /// Corresponds to `GL_ELEMENT_ARRAY_BUFFER`. 12 | pub const ELEMENT_ARRAY_BUFFER: u32 = 34_963; 13 | 14 | /// The minimum byte stride. 15 | pub const MIN_BYTE_STRIDE: usize = 4; 16 | 17 | /// The maximum byte stride. 18 | pub const MAX_BYTE_STRIDE: usize = 252; 19 | 20 | /// All valid GPU buffer targets. 21 | pub const VALID_TARGETS: &[u32] = &[ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER]; 22 | 23 | /// Specifies the target a GPU buffer should be bound to. 24 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 25 | pub enum Target { 26 | /// Corresponds to `GL_ARRAY_BUFFER`. 27 | ArrayBuffer = 1, 28 | 29 | /// Corresponds to `GL_ELEMENT_ARRAY_BUFFER`. 30 | ElementArrayBuffer, 31 | } 32 | 33 | impl ser::Serialize for Target { 34 | fn serialize(&self, serializer: S) -> Result 35 | where 36 | S: ser::Serializer, 37 | { 38 | match *self { 39 | Target::ArrayBuffer => serializer.serialize_u32(ARRAY_BUFFER), 40 | Target::ElementArrayBuffer => serializer.serialize_u32(ELEMENT_ARRAY_BUFFER), 41 | } 42 | } 43 | } 44 | 45 | /// Distance between individual items in a buffer view, measured in bytes. 46 | #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] 47 | pub struct Stride(pub usize); 48 | 49 | impl Validate for Stride { 50 | fn validate(&self, _root: &Root, path: P, report: &mut R) 51 | where 52 | P: Fn() -> Path, 53 | R: FnMut(&dyn Fn() -> Path, Error), 54 | { 55 | if self.0 < MIN_BYTE_STRIDE || self.0 > MAX_BYTE_STRIDE { 56 | report(&path, Error::Invalid); 57 | } 58 | } 59 | } 60 | 61 | /// A buffer points to binary data representing geometry, animations, or skins. 62 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 63 | pub struct Buffer { 64 | /// The length of the buffer in bytes. 65 | #[serde(default, rename = "byteLength")] 66 | pub byte_length: USize64, 67 | 68 | /// Optional user-defined name for this object. 69 | #[cfg(feature = "names")] 70 | #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] 71 | pub name: Option, 72 | 73 | /// The uri of the buffer. Relative paths are relative to the .gltf file. 74 | /// Instead of referencing an external file, the uri can also be a data-uri. 75 | #[serde(skip_serializing_if = "Option::is_none")] 76 | pub uri: Option, 77 | 78 | /// Extension specific data. 79 | #[serde(default, skip_serializing_if = "Option::is_none")] 80 | pub extensions: Option, 81 | 82 | /// Optional application specific data. 83 | #[serde(default)] 84 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 85 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 86 | pub extras: Extras, 87 | } 88 | 89 | /// A view into a buffer generally representing a subset of the buffer. 90 | /// 91 | /// 92 | /// 93 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 94 | pub struct View { 95 | /// The parent `Buffer`. 96 | pub buffer: Index, 97 | 98 | /// The length of the `BufferView` in bytes. 99 | #[serde(rename = "byteLength")] 100 | pub byte_length: USize64, 101 | 102 | /// Offset into the parent buffer in bytes. 103 | #[serde( 104 | default, 105 | rename = "byteOffset", 106 | skip_serializing_if = "Option::is_none" 107 | )] 108 | pub byte_offset: Option, 109 | 110 | /// The stride in bytes between vertex attributes or other interleavable data. 111 | /// 112 | /// When zero, data is assumed to be tightly packed. 113 | #[serde(rename = "byteStride")] 114 | #[serde(skip_serializing_if = "Option::is_none")] 115 | pub byte_stride: Option, 116 | 117 | /// Optional user-defined name for this object. 118 | #[cfg(feature = "names")] 119 | #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] 120 | pub name: Option, 121 | 122 | /// Optional target the buffer should be bound to. 123 | #[serde(skip_serializing_if = "Option::is_none")] 124 | pub target: Option>, 125 | 126 | /// Extension specific data. 127 | #[serde(default, skip_serializing_if = "Option::is_none")] 128 | pub extensions: Option, 129 | 130 | /// Optional application specific data. 131 | #[serde(default)] 132 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 133 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 134 | pub extras: Extras, 135 | } 136 | 137 | impl<'de> de::Deserialize<'de> for Checked { 138 | fn deserialize(deserializer: D) -> Result 139 | where 140 | D: de::Deserializer<'de>, 141 | { 142 | struct Visitor; 143 | impl de::Visitor<'_> for Visitor { 144 | type Value = Checked; 145 | 146 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 147 | write!(f, "any of: {:?}", VALID_TARGETS) 148 | } 149 | 150 | fn visit_u64(self, value: u64) -> Result 151 | where 152 | E: de::Error, 153 | { 154 | use self::Target::*; 155 | use crate::validation::Checked::*; 156 | Ok(match value as u32 { 157 | ARRAY_BUFFER => Valid(ArrayBuffer), 158 | ELEMENT_ARRAY_BUFFER => Valid(ElementArrayBuffer), 159 | _ => Invalid, 160 | }) 161 | } 162 | } 163 | deserializer.deserialize_u64(Visitor) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/animation/util/rotations.rs: -------------------------------------------------------------------------------- 1 | use super::Rotations; 2 | use crate::Normalize; 3 | use std::marker::PhantomData; 4 | 5 | /// Casting iterator for `Rotations`. 6 | #[derive(Clone, Debug)] 7 | pub struct CastingIter<'a, T>(Rotations<'a>, PhantomData); 8 | 9 | /// Type which describes how to cast any weight into i8. 10 | #[derive(Clone, Debug)] 11 | pub struct I8; 12 | 13 | /// Type which describes how to cast any weight into u8. 14 | #[derive(Clone, Debug)] 15 | pub struct U8; 16 | 17 | /// Type which describes how to cast any weight into i16. 18 | #[derive(Clone, Debug)] 19 | pub struct I16; 20 | 21 | /// Type which describes how to cast any weight into u16. 22 | #[derive(Clone, Debug)] 23 | pub struct U16; 24 | 25 | /// Type which describes how to cast any weight into f32. 26 | #[derive(Clone, Debug)] 27 | pub struct F32; 28 | 29 | /// Trait for types which describe casting behaviour. 30 | pub trait Cast { 31 | /// Output type. 32 | type Output; 33 | 34 | /// Cast from i8. 35 | fn cast_i8(x: [i8; 4]) -> Self::Output; 36 | 37 | /// Cast from u8. 38 | fn cast_u8(x: [u8; 4]) -> Self::Output; 39 | 40 | /// Cast from i16. 41 | fn cast_i16(x: [i16; 4]) -> Self::Output; 42 | 43 | /// Cast from u16. 44 | fn cast_u16(x: [u16; 4]) -> Self::Output; 45 | 46 | /// Cast from f32. 47 | fn cast_f32(x: [f32; 4]) -> Self::Output; 48 | } 49 | 50 | impl<'a, A> CastingIter<'a, A> { 51 | pub(crate) fn new(iter: Rotations<'a>) -> Self { 52 | CastingIter(iter, PhantomData) 53 | } 54 | 55 | /// Unwrap underlying `Rotations` object. 56 | pub fn unwrap(self) -> Rotations<'a> { 57 | self.0 58 | } 59 | } 60 | 61 | impl<'a, A: Cast> ExactSizeIterator for CastingIter<'a, A> {} 62 | impl<'a, A: Cast> Iterator for CastingIter<'a, A> { 63 | type Item = A::Output; 64 | 65 | #[inline] 66 | fn next(&mut self) -> Option { 67 | match self.0 { 68 | Rotations::I8(ref mut i) => i.next().map(A::cast_i8), 69 | Rotations::U8(ref mut i) => i.next().map(A::cast_u8), 70 | Rotations::I16(ref mut i) => i.next().map(A::cast_i16), 71 | Rotations::U16(ref mut i) => i.next().map(A::cast_u16), 72 | Rotations::F32(ref mut i) => i.next().map(A::cast_f32), 73 | } 74 | } 75 | 76 | #[inline] 77 | fn nth(&mut self, x: usize) -> Option { 78 | match self.0 { 79 | Rotations::I8(ref mut i) => i.nth(x).map(A::cast_i8), 80 | Rotations::U8(ref mut i) => i.nth(x).map(A::cast_u8), 81 | Rotations::I16(ref mut i) => i.nth(x).map(A::cast_i16), 82 | Rotations::U16(ref mut i) => i.nth(x).map(A::cast_u16), 83 | Rotations::F32(ref mut i) => i.nth(x).map(A::cast_f32), 84 | } 85 | } 86 | 87 | fn last(self) -> Option { 88 | match self.0 { 89 | Rotations::I8(i) => i.last().map(A::cast_i8), 90 | Rotations::U8(i) => i.last().map(A::cast_u8), 91 | Rotations::I16(i) => i.last().map(A::cast_i16), 92 | Rotations::U16(i) => i.last().map(A::cast_u16), 93 | Rotations::F32(i) => i.last().map(A::cast_f32), 94 | } 95 | } 96 | 97 | fn count(self) -> usize { 98 | self.size_hint().0 99 | } 100 | 101 | #[inline] 102 | fn size_hint(&self) -> (usize, Option) { 103 | match self.0 { 104 | Rotations::I8(ref i) => i.size_hint(), 105 | Rotations::U8(ref i) => i.size_hint(), 106 | Rotations::I16(ref i) => i.size_hint(), 107 | Rotations::U16(ref i) => i.size_hint(), 108 | Rotations::F32(ref i) => i.size_hint(), 109 | } 110 | } 111 | } 112 | 113 | impl Cast for I8 { 114 | type Output = [i8; 4]; 115 | 116 | fn cast_i8(x: [i8; 4]) -> Self::Output { 117 | x.normalize() 118 | } 119 | 120 | fn cast_u8(x: [u8; 4]) -> Self::Output { 121 | x.normalize() 122 | } 123 | 124 | fn cast_i16(x: [i16; 4]) -> Self::Output { 125 | x.normalize() 126 | } 127 | 128 | fn cast_u16(x: [u16; 4]) -> Self::Output { 129 | x.normalize() 130 | } 131 | 132 | fn cast_f32(x: [f32; 4]) -> Self::Output { 133 | x.normalize() 134 | } 135 | } 136 | 137 | impl Cast for i8 { 138 | type Output = [u8; 4]; 139 | 140 | fn cast_i8(x: [i8; 4]) -> Self::Output { 141 | x.normalize() 142 | } 143 | 144 | fn cast_u8(x: [u8; 4]) -> Self::Output { 145 | x.normalize() 146 | } 147 | 148 | fn cast_i16(x: [i16; 4]) -> Self::Output { 149 | x.normalize() 150 | } 151 | 152 | fn cast_u16(x: [u16; 4]) -> Self::Output { 153 | x.normalize() 154 | } 155 | 156 | fn cast_f32(x: [f32; 4]) -> Self::Output { 157 | x.normalize() 158 | } 159 | } 160 | 161 | impl Cast for I16 { 162 | type Output = [i16; 4]; 163 | 164 | fn cast_i8(x: [i8; 4]) -> Self::Output { 165 | x.normalize() 166 | } 167 | 168 | fn cast_u8(x: [u8; 4]) -> Self::Output { 169 | x.normalize() 170 | } 171 | 172 | fn cast_i16(x: [i16; 4]) -> Self::Output { 173 | x.normalize() 174 | } 175 | 176 | fn cast_u16(x: [u16; 4]) -> Self::Output { 177 | x.normalize() 178 | } 179 | 180 | fn cast_f32(x: [f32; 4]) -> Self::Output { 181 | x.normalize() 182 | } 183 | } 184 | 185 | impl Cast for U16 { 186 | type Output = [u16; 4]; 187 | 188 | fn cast_i8(x: [i8; 4]) -> Self::Output { 189 | x.normalize() 190 | } 191 | 192 | fn cast_u8(x: [u8; 4]) -> Self::Output { 193 | x.normalize() 194 | } 195 | 196 | fn cast_i16(x: [i16; 4]) -> Self::Output { 197 | x.normalize() 198 | } 199 | 200 | fn cast_u16(x: [u16; 4]) -> Self::Output { 201 | x.normalize() 202 | } 203 | 204 | fn cast_f32(x: [f32; 4]) -> Self::Output { 205 | x.normalize() 206 | } 207 | } 208 | 209 | impl Cast for F32 { 210 | type Output = [f32; 4]; 211 | 212 | fn cast_i8(x: [i8; 4]) -> Self::Output { 213 | x.normalize() 214 | } 215 | 216 | fn cast_u8(x: [u8; 4]) -> Self::Output { 217 | x.normalize() 218 | } 219 | 220 | fn cast_i16(x: [i16; 4]) -> Self::Output { 221 | x.normalize() 222 | } 223 | 224 | fn cast_u16(x: [u16; 4]) -> Self::Output { 225 | x.normalize() 226 | } 227 | 228 | fn cast_f32(x: [f32; 4]) -> Self::Output { 229 | x.normalize() 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/animation/util/morph_target_weights.rs: -------------------------------------------------------------------------------- 1 | use super::MorphTargetWeights; 2 | use crate::Normalize; 3 | use std::marker::PhantomData; 4 | 5 | /// Casting iterator for `MorphTargetWeights`. 6 | #[derive(Clone, Debug)] 7 | pub struct CastingIter<'a, T>(MorphTargetWeights<'a>, PhantomData); 8 | 9 | /// Type which describes how to cast any weight into i8. 10 | #[derive(Clone, Debug)] 11 | pub struct I8; 12 | 13 | /// Type which describes how to cast any weight into u8. 14 | #[derive(Clone, Debug)] 15 | pub struct U8; 16 | 17 | /// Type which describes how to cast any weight into i16. 18 | #[derive(Clone, Debug)] 19 | pub struct I16; 20 | 21 | /// Type which describes how to cast any weight into u16. 22 | #[derive(Clone, Debug)] 23 | pub struct U16; 24 | 25 | /// Type which describes how to cast any weight into f32. 26 | #[derive(Clone, Debug)] 27 | pub struct F32; 28 | 29 | /// Trait for types which describe casting behaviour. 30 | pub trait Cast { 31 | /// Output type. 32 | type Output; 33 | 34 | /// Cast from i8. 35 | fn cast_i8(x: i8) -> Self::Output; 36 | 37 | /// Cast from u8. 38 | fn cast_u8(x: u8) -> Self::Output; 39 | 40 | /// Cast from i16. 41 | fn cast_i16(x: i16) -> Self::Output; 42 | 43 | /// Cast from u16. 44 | fn cast_u16(x: u16) -> Self::Output; 45 | 46 | /// Cast from f32. 47 | fn cast_f32(x: f32) -> Self::Output; 48 | } 49 | 50 | impl<'a, A> CastingIter<'a, A> { 51 | pub(crate) fn new(iter: MorphTargetWeights<'a>) -> Self { 52 | CastingIter(iter, PhantomData) 53 | } 54 | 55 | /// Unwrap underlying `MorphTargetWeights` object. 56 | pub fn unwrap(self) -> MorphTargetWeights<'a> { 57 | self.0 58 | } 59 | } 60 | 61 | impl<'a, A: Cast> ExactSizeIterator for CastingIter<'a, A> {} 62 | impl<'a, A: Cast> Iterator for CastingIter<'a, A> { 63 | type Item = A::Output; 64 | 65 | #[inline] 66 | fn next(&mut self) -> Option { 67 | match self.0 { 68 | MorphTargetWeights::I8(ref mut i) => i.next().map(A::cast_i8), 69 | MorphTargetWeights::U8(ref mut i) => i.next().map(A::cast_u8), 70 | MorphTargetWeights::I16(ref mut i) => i.next().map(A::cast_i16), 71 | MorphTargetWeights::U16(ref mut i) => i.next().map(A::cast_u16), 72 | MorphTargetWeights::F32(ref mut i) => i.next().map(A::cast_f32), 73 | } 74 | } 75 | 76 | #[inline] 77 | fn nth(&mut self, x: usize) -> Option { 78 | match self.0 { 79 | MorphTargetWeights::I8(ref mut i) => i.nth(x).map(A::cast_i8), 80 | MorphTargetWeights::U8(ref mut i) => i.nth(x).map(A::cast_u8), 81 | MorphTargetWeights::I16(ref mut i) => i.nth(x).map(A::cast_i16), 82 | MorphTargetWeights::U16(ref mut i) => i.nth(x).map(A::cast_u16), 83 | MorphTargetWeights::F32(ref mut i) => i.nth(x).map(A::cast_f32), 84 | } 85 | } 86 | 87 | fn last(self) -> Option { 88 | match self.0 { 89 | MorphTargetWeights::I8(i) => i.last().map(A::cast_i8), 90 | MorphTargetWeights::U8(i) => i.last().map(A::cast_u8), 91 | MorphTargetWeights::I16(i) => i.last().map(A::cast_i16), 92 | MorphTargetWeights::U16(i) => i.last().map(A::cast_u16), 93 | MorphTargetWeights::F32(i) => i.last().map(A::cast_f32), 94 | } 95 | } 96 | 97 | fn count(self) -> usize { 98 | self.size_hint().0 99 | } 100 | 101 | #[inline] 102 | fn size_hint(&self) -> (usize, Option) { 103 | match self.0 { 104 | MorphTargetWeights::I8(ref i) => i.size_hint(), 105 | MorphTargetWeights::U8(ref i) => i.size_hint(), 106 | MorphTargetWeights::I16(ref i) => i.size_hint(), 107 | MorphTargetWeights::U16(ref i) => i.size_hint(), 108 | MorphTargetWeights::F32(ref i) => i.size_hint(), 109 | } 110 | } 111 | } 112 | 113 | impl Cast for I8 { 114 | type Output = i8; 115 | 116 | fn cast_i8(x: i8) -> Self::Output { 117 | x.normalize() 118 | } 119 | 120 | fn cast_u8(x: u8) -> Self::Output { 121 | x.normalize() 122 | } 123 | 124 | fn cast_i16(x: i16) -> Self::Output { 125 | x.normalize() 126 | } 127 | 128 | fn cast_u16(x: u16) -> Self::Output { 129 | x.normalize() 130 | } 131 | 132 | fn cast_f32(x: f32) -> Self::Output { 133 | x.normalize() 134 | } 135 | } 136 | 137 | impl Cast for U8 { 138 | type Output = u8; 139 | 140 | fn cast_i8(x: i8) -> Self::Output { 141 | x.normalize() 142 | } 143 | 144 | fn cast_u8(x: u8) -> Self::Output { 145 | x.normalize() 146 | } 147 | 148 | fn cast_i16(x: i16) -> Self::Output { 149 | x.normalize() 150 | } 151 | 152 | fn cast_u16(x: u16) -> Self::Output { 153 | x.normalize() 154 | } 155 | 156 | fn cast_f32(x: f32) -> Self::Output { 157 | x.normalize() 158 | } 159 | } 160 | 161 | impl Cast for I16 { 162 | type Output = i16; 163 | 164 | fn cast_i8(x: i8) -> Self::Output { 165 | x.normalize() 166 | } 167 | 168 | fn cast_u8(x: u8) -> Self::Output { 169 | x.normalize() 170 | } 171 | 172 | fn cast_i16(x: i16) -> Self::Output { 173 | x.normalize() 174 | } 175 | 176 | fn cast_u16(x: u16) -> Self::Output { 177 | x.normalize() 178 | } 179 | 180 | fn cast_f32(x: f32) -> Self::Output { 181 | x.normalize() 182 | } 183 | } 184 | 185 | impl Cast for U16 { 186 | type Output = u16; 187 | 188 | fn cast_i8(x: i8) -> Self::Output { 189 | x.normalize() 190 | } 191 | 192 | fn cast_u8(x: u8) -> Self::Output { 193 | x.normalize() 194 | } 195 | 196 | fn cast_i16(x: i16) -> Self::Output { 197 | x.normalize() 198 | } 199 | 200 | fn cast_u16(x: u16) -> Self::Output { 201 | x.normalize() 202 | } 203 | 204 | fn cast_f32(x: f32) -> Self::Output { 205 | x.normalize() 206 | } 207 | } 208 | 209 | impl Cast for F32 { 210 | type Output = f32; 211 | 212 | fn cast_i8(x: i8) -> Self::Output { 213 | x.normalize() 214 | } 215 | 216 | fn cast_u8(x: u8) -> Self::Output { 217 | x.normalize() 218 | } 219 | 220 | fn cast_i16(x: i16) -> Self::Output { 221 | x.normalize() 222 | } 223 | 224 | fn cast_u16(x: u16) -> Self::Output { 225 | x.normalize() 226 | } 227 | 228 | fn cast_f32(x: f32) -> Self::Output { 229 | x.normalize() 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "import")] 2 | use std::ops; 3 | 4 | use crate::Document; 5 | 6 | pub use json::buffer::Target; 7 | #[cfg(feature = "extensions")] 8 | use serde_json::{Map, Value}; 9 | 10 | /// A buffer points to binary data representing geometry, animations, or skins. 11 | #[derive(Clone, Debug)] 12 | pub struct Buffer<'a> { 13 | /// The parent `Document` struct. 14 | #[allow(dead_code)] 15 | document: &'a Document, 16 | 17 | /// The corresponding JSON index. 18 | index: usize, 19 | 20 | /// The corresponding JSON struct. 21 | json: &'a json::buffer::Buffer, 22 | } 23 | 24 | /// A view into a buffer generally representing a subset of the buffer. 25 | #[derive(Clone, Debug)] 26 | pub struct View<'a> { 27 | /// The parent `Document` struct. 28 | document: &'a Document, 29 | 30 | /// The corresponding JSON index. 31 | index: usize, 32 | 33 | /// The corresponding JSON struct. 34 | json: &'a json::buffer::View, 35 | 36 | /// The parent `Buffer`. 37 | #[allow(dead_code)] 38 | parent: Buffer<'a>, 39 | } 40 | 41 | /// Describes a buffer data source. 42 | #[derive(Clone, Debug)] 43 | pub enum Source<'a> { 44 | /// Buffer data is contained in the `BIN` section of binary glTF. 45 | Bin, 46 | 47 | /// Buffer data is contained in an external data source. 48 | Uri(&'a str), 49 | } 50 | 51 | /// Buffer data belonging to an imported glTF asset. 52 | #[cfg(feature = "import")] 53 | #[cfg_attr(docsrs, doc(cfg(feature = "import")))] 54 | #[derive(Clone, Debug)] 55 | pub struct Data(pub Vec); 56 | 57 | #[cfg(feature = "import")] 58 | #[cfg_attr(docsrs, doc(cfg(feature = "import")))] 59 | impl ops::Deref for Data { 60 | type Target = [u8]; 61 | fn deref(&self) -> &Self::Target { 62 | self.0.as_slice() 63 | } 64 | } 65 | 66 | impl<'a> Buffer<'a> { 67 | /// Constructs a `Buffer`. 68 | pub(crate) fn new( 69 | document: &'a Document, 70 | index: usize, 71 | json: &'a json::buffer::Buffer, 72 | ) -> Self { 73 | Self { 74 | document, 75 | index, 76 | json, 77 | } 78 | } 79 | 80 | /// Returns the internal JSON index. 81 | pub fn index(&self) -> usize { 82 | self.index 83 | } 84 | 85 | /// Returns the buffer data source. 86 | pub fn source(&self) -> Source<'a> { 87 | if let Some(uri) = self.json.uri.as_deref() { 88 | Source::Uri(uri) 89 | } else { 90 | Source::Bin 91 | } 92 | } 93 | 94 | /// The length of the buffer in bytes. 95 | pub fn length(&self) -> usize { 96 | self.json.byte_length.0 as usize 97 | } 98 | 99 | /// Optional user-defined name for this object. 100 | #[cfg(feature = "names")] 101 | #[cfg_attr(docsrs, doc(cfg(feature = "names")))] 102 | pub fn name(&self) -> Option<&'a str> { 103 | self.json.name.as_deref() 104 | } 105 | 106 | /// Returns extension data unknown to this crate version. 107 | #[cfg(feature = "extensions")] 108 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 109 | pub fn extensions(&self) -> Option<&Map> { 110 | let ext = self.json.extensions.as_ref()?; 111 | Some(&ext.others) 112 | } 113 | 114 | /// Queries extension data unknown to this crate version. 115 | #[cfg(feature = "extensions")] 116 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 117 | pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { 118 | let ext = self.json.extensions.as_ref()?; 119 | ext.others.get(ext_name) 120 | } 121 | 122 | /// Optional application specific data. 123 | pub fn extras(&self) -> &'a json::Extras { 124 | &self.json.extras 125 | } 126 | } 127 | 128 | impl<'a> View<'a> { 129 | /// Constructs a `View`. 130 | pub(crate) fn new(document: &'a Document, index: usize, json: &'a json::buffer::View) -> Self { 131 | let parent = document.buffers().nth(json.buffer.value()).unwrap(); 132 | Self { 133 | document, 134 | index, 135 | json, 136 | parent, 137 | } 138 | } 139 | 140 | /// Returns the internal JSON index. 141 | pub fn index(&self) -> usize { 142 | self.index 143 | } 144 | 145 | /// Returns the parent `Buffer`. 146 | pub fn buffer(&self) -> Buffer<'a> { 147 | self.document 148 | .buffers() 149 | .nth(self.json.buffer.value()) 150 | .unwrap() 151 | } 152 | 153 | /// Returns the length of the buffer view in bytes. 154 | pub fn length(&self) -> usize { 155 | self.json.byte_length.0 as usize 156 | } 157 | 158 | /// Returns the offset into the parent buffer in bytes. 159 | pub fn offset(&self) -> usize { 160 | self.json.byte_offset.unwrap_or_default().0 as usize 161 | } 162 | 163 | /// Returns the stride in bytes between vertex attributes or other interleavable 164 | /// data. When `None`, data is assumed to be tightly packed. 165 | pub fn stride(&self) -> Option { 166 | self.json.byte_stride.and_then(|x| { 167 | // Treat byte_stride == 0 same as not specifying stride. 168 | // This is technically a validation error, but best way we can handle it here 169 | if x.0 == 0 { 170 | None 171 | } else { 172 | Some(x.0) 173 | } 174 | }) 175 | } 176 | 177 | /// Optional user-defined name for this object. 178 | #[cfg(feature = "names")] 179 | #[cfg_attr(docsrs, doc(cfg(feature = "names")))] 180 | pub fn name(&self) -> Option<&'a str> { 181 | self.json.name.as_deref() 182 | } 183 | 184 | /// Optional target the buffer should be bound to. 185 | pub fn target(&self) -> Option { 186 | self.json.target.map(|target| target.unwrap()) 187 | } 188 | 189 | /// Returns extension data unknown to this crate version. 190 | #[cfg(feature = "extensions")] 191 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 192 | pub fn extensions(&self) -> Option<&Map> { 193 | let ext = self.json.extensions.as_ref()?; 194 | Some(&ext.others) 195 | } 196 | 197 | /// Queries extension data unknown to this crate version. 198 | #[cfg(feature = "extensions")] 199 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 200 | pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { 201 | let ext = self.json.extensions.as_ref()?; 202 | ext.others.get(ext_name) 203 | } 204 | 205 | /// Optional application specific data. 206 | pub fn extras(&self) -> &'a json::Extras { 207 | &self.json.extras 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/accessor/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Basic usage 2 | //! 3 | //! Visiting the accessors of a glTF asset. 4 | //! 5 | //! ``` 6 | //! # fn run() -> Result<(), Box> { 7 | //! # let gltf = gltf::Gltf::open("examples/Box.gltf")?; 8 | //! for accessor in gltf.accessors() { 9 | //! println!("Accessor #{}", accessor.index()); 10 | //! println!("offset: {:?}", accessor.offset()); 11 | //! println!("count: {}", accessor.count()); 12 | //! println!("data_type: {:?}", accessor.data_type()); 13 | //! println!("dimensions: {:?}", accessor.dimensions()); 14 | //! } 15 | //! # Ok(()) 16 | //! # } 17 | //! # fn main() { 18 | //! # let _ = run().expect("runtime error"); 19 | //! # } 20 | //! ``` 21 | //! 22 | //! # Utility functions 23 | //! 24 | //! Reading the values from the `vec3` accessors of a glTF asset. 25 | //! 26 | //! ## Note 27 | //! 28 | //! The [`Iter`] utility is a low-level iterator intended for use in special 29 | //! cases. The average user is expected to use reader abstractions such as 30 | //! [`mesh::Reader`]. 31 | //! 32 | //! [`Iter`]: struct.Iter.html 33 | //! [`mesh::Reader`]: ../mesh/struct.Reader.html 34 | //! 35 | //! ``` 36 | //! # fn run() -> Result<(), Box> { 37 | //! # use gltf::accessor::{DataType, Dimensions, Iter}; 38 | //! let (gltf, buffers, _) = gltf::import("examples/Box.gltf")?; 39 | //! let get_buffer_data = |buffer: gltf::Buffer| buffers.get(buffer.index()).map(|x| &*x.0); 40 | //! for accessor in gltf.accessors() { 41 | //! match (accessor.data_type(), accessor.dimensions()) { 42 | //! (DataType::F32, Dimensions::Vec3) => { 43 | //! if let Some(iter) = Iter::<[f32; 3]>::new(accessor, get_buffer_data) { 44 | //! for item in iter { 45 | //! println!("{:?}", item); 46 | //! } 47 | //! } 48 | //! } 49 | //! _ => {}, 50 | //! } 51 | //! } 52 | //! # Ok(()) 53 | //! # } 54 | //! # fn main() { 55 | //! # let _ = run().expect("runtime error"); 56 | //! # } 57 | //! ``` 58 | 59 | use crate::{buffer, Document}; 60 | 61 | pub use json::accessor::ComponentType as DataType; 62 | pub use json::accessor::Type as Dimensions; 63 | #[cfg(feature = "extensions")] 64 | use serde_json::{Map, Value}; 65 | 66 | /// Utility functions. 67 | #[cfg(feature = "utils")] 68 | #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] 69 | pub mod util; 70 | 71 | /// Contains data structures for sparse storage. 72 | pub mod sparse; 73 | 74 | #[cfg(feature = "utils")] 75 | #[doc(inline)] 76 | pub use self::util::{Item, Iter}; 77 | 78 | /// A typed view into a buffer view. 79 | #[derive(Clone, Debug)] 80 | pub struct Accessor<'a> { 81 | /// The parent `Document` struct. 82 | document: &'a Document, 83 | 84 | /// The corresponding JSON index. 85 | index: usize, 86 | 87 | /// The corresponding JSON struct. 88 | json: &'a json::accessor::Accessor, 89 | } 90 | 91 | impl<'a> Accessor<'a> { 92 | /// Constructs an `Accessor`. 93 | pub(crate) fn new( 94 | document: &'a Document, 95 | index: usize, 96 | json: &'a json::accessor::Accessor, 97 | ) -> Self { 98 | Self { 99 | document, 100 | index, 101 | json, 102 | } 103 | } 104 | 105 | /// Returns the internal JSON index. 106 | pub fn index(&self) -> usize { 107 | self.index 108 | } 109 | 110 | /// Returns the size of each component that this accessor describes. 111 | pub fn size(&self) -> usize { 112 | self.data_type().size() * self.dimensions().multiplicity() 113 | } 114 | 115 | /// Returns the buffer view this accessor reads from. 116 | /// 117 | /// This may be `None` if the corresponding accessor is sparse. 118 | pub fn view(&self) -> Option> { 119 | self.json 120 | .buffer_view 121 | .map(|view| self.document.views().nth(view.value()).unwrap()) 122 | } 123 | 124 | /// Returns the offset relative to the start of the parent buffer view in bytes. 125 | /// 126 | /// This will be 0 if the corresponding accessor is sparse. 127 | pub fn offset(&self) -> usize { 128 | // TODO: Change this function to return Option in the next 129 | // version and return None for sparse accessors. 130 | self.json.byte_offset.unwrap_or_default().0 as usize 131 | } 132 | 133 | /// Returns the number of components within the buffer view - not to be confused 134 | /// with the number of bytes in the buffer view. 135 | pub fn count(&self) -> usize { 136 | self.json.count.0 as usize 137 | } 138 | 139 | /// Returns the data type of components in the attribute. 140 | pub fn data_type(&self) -> DataType { 141 | self.json.component_type.unwrap().0 142 | } 143 | 144 | /// Returns extension data unknown to this crate version. 145 | #[cfg(feature = "extensions")] 146 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 147 | pub fn extensions(&self) -> Option<&Map> { 148 | let ext = self.json.extensions.as_ref()?; 149 | Some(&ext.others) 150 | } 151 | 152 | /// Queries extension data unknown to this crate version. 153 | #[cfg(feature = "extensions")] 154 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 155 | pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { 156 | let ext = self.json.extensions.as_ref()?; 157 | ext.others.get(ext_name) 158 | } 159 | 160 | /// Optional application specific data. 161 | pub fn extras(&self) -> &'a json::Extras { 162 | &self.json.extras 163 | } 164 | 165 | /// Specifies if the attribute is a scalar, vector, or matrix. 166 | pub fn dimensions(&self) -> Dimensions { 167 | self.json.type_.unwrap() 168 | } 169 | 170 | /// Returns the minimum value of each component in this attribute. 171 | pub fn min(&self) -> Option { 172 | self.json.min.clone() 173 | } 174 | 175 | /// Returns the maximum value of each component in this attribute. 176 | pub fn max(&self) -> Option { 177 | self.json.max.clone() 178 | } 179 | 180 | /// Optional user-defined name for this object. 181 | #[cfg(feature = "names")] 182 | #[cfg_attr(docsrs, doc(cfg(feature = "names")))] 183 | pub fn name(&self) -> Option<&'a str> { 184 | self.json.name.as_deref() 185 | } 186 | 187 | /// Specifies whether integer data values should be normalized. 188 | pub fn normalized(&self) -> bool { 189 | self.json.normalized 190 | } 191 | 192 | /// Returns sparse storage of attributes that deviate from their initialization 193 | /// value. 194 | pub fn sparse(&self) -> Option> { 195 | self.json 196 | .sparse 197 | .as_ref() 198 | .map(|json| sparse::Sparse::new(self.document, json)) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/camera.rs: -------------------------------------------------------------------------------- 1 | use crate::Document; 2 | 3 | #[cfg(feature = "extensions")] 4 | use serde_json::{Map, Value}; 5 | 6 | /// A camera's projection. 7 | #[derive(Clone, Debug)] 8 | pub enum Projection<'a> { 9 | /// Describes an orthographic projection. 10 | Orthographic(Orthographic<'a>), 11 | 12 | /// Describes a perspective projection. 13 | Perspective(Perspective<'a>), 14 | } 15 | 16 | /// A camera's projection. A node can reference a camera to apply a transform to 17 | /// place the camera in the scene. 18 | #[derive(Clone, Debug)] 19 | pub struct Camera<'a> { 20 | /// The parent `Document` struct. 21 | document: &'a Document, 22 | 23 | /// The corresponding JSON index. 24 | index: usize, 25 | 26 | /// The corresponding JSON struct. 27 | json: &'a json::camera::Camera, 28 | } 29 | 30 | /// Values for an orthographic camera projection. 31 | #[derive(Clone, Debug)] 32 | pub struct Orthographic<'a> { 33 | /// The parent `Document` struct. 34 | #[allow(dead_code)] 35 | document: &'a Document, 36 | 37 | /// The corresponding JSON struct. 38 | json: &'a json::camera::Orthographic, 39 | } 40 | 41 | /// Values for a perspective camera projection. 42 | #[derive(Clone, Debug)] 43 | pub struct Perspective<'a> { 44 | /// The parent `Document` struct. 45 | #[allow(dead_code)] 46 | document: &'a Document, 47 | 48 | /// The corresponding JSON struct. 49 | json: &'a json::camera::Perspective, 50 | } 51 | 52 | impl<'a> Camera<'a> { 53 | /// Constructs a `Camera`. 54 | pub(crate) fn new( 55 | document: &'a Document, 56 | index: usize, 57 | json: &'a json::camera::Camera, 58 | ) -> Self { 59 | Self { 60 | document, 61 | index, 62 | json, 63 | } 64 | } 65 | 66 | /// Returns the internal JSON index. 67 | pub fn index(&self) -> usize { 68 | self.index 69 | } 70 | 71 | /// Optional user-defined name for this object. 72 | #[cfg(feature = "names")] 73 | #[cfg_attr(docsrs, doc(cfg(feature = "names")))] 74 | pub fn name(&self) -> Option<&'a str> { 75 | self.json.name.as_deref() 76 | } 77 | 78 | /// Returns the camera's projection. 79 | pub fn projection(&self) -> Projection { 80 | match self.json.type_.unwrap() { 81 | json::camera::Type::Orthographic => { 82 | let json = self.json.orthographic.as_ref().unwrap(); 83 | Projection::Orthographic(Orthographic::new(self.document, json)) 84 | } 85 | json::camera::Type::Perspective => { 86 | let json = self.json.perspective.as_ref().unwrap(); 87 | Projection::Perspective(Perspective::new(self.document, json)) 88 | } 89 | } 90 | } 91 | 92 | /// Returns extension data unknown to this crate version. 93 | #[cfg(feature = "extensions")] 94 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 95 | pub fn extensions(&self) -> Option<&Map> { 96 | let ext = self.json.extensions.as_ref()?; 97 | Some(&ext.others) 98 | } 99 | 100 | /// Queries extension data unknown to this crate version. 101 | #[cfg(feature = "extensions")] 102 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 103 | pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { 104 | let ext = self.json.extensions.as_ref()?; 105 | ext.others.get(ext_name) 106 | } 107 | 108 | /// Optional application specific data. 109 | pub fn extras(&self) -> &'a json::Extras { 110 | &self.json.extras 111 | } 112 | } 113 | 114 | impl<'a> Orthographic<'a> { 115 | /// Constructs a `Orthographic` camera projection. 116 | pub(crate) fn new(document: &'a Document, json: &'a json::camera::Orthographic) -> Self { 117 | Self { document, json } 118 | } 119 | 120 | /// The horizontal magnification of the view. 121 | pub fn xmag(&self) -> f32 { 122 | self.json.xmag 123 | } 124 | 125 | /// The vertical magnification of the view. 126 | pub fn ymag(&self) -> f32 { 127 | self.json.ymag 128 | } 129 | 130 | /// The distance to the far clipping plane. 131 | pub fn zfar(&self) -> f32 { 132 | self.json.zfar 133 | } 134 | 135 | /// The distance to the near clipping plane. 136 | pub fn znear(&self) -> f32 { 137 | self.json.znear 138 | } 139 | 140 | /// Returns extension data unknown to this crate version. 141 | #[cfg(feature = "extensions")] 142 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 143 | pub fn extensions(&self) -> Option<&Map> { 144 | let ext = self.json.extensions.as_ref()?; 145 | Some(&ext.others) 146 | } 147 | 148 | /// Queries extension data unknown to this crate version. 149 | #[cfg(feature = "extensions")] 150 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 151 | pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { 152 | let ext = self.json.extensions.as_ref()?; 153 | ext.others.get(ext_name) 154 | } 155 | 156 | /// Optional application specific data. 157 | pub fn extras(&self) -> &'a json::Extras { 158 | &self.json.extras 159 | } 160 | } 161 | 162 | impl<'a> Perspective<'a> { 163 | /// Constructs a `Perspective` camera projection. 164 | pub(crate) fn new(document: &'a Document, json: &'a json::camera::Perspective) -> Self { 165 | Self { document, json } 166 | } 167 | 168 | /// Aspect ratio of the field of view. 169 | pub fn aspect_ratio(&self) -> Option { 170 | self.json.aspect_ratio 171 | } 172 | 173 | /// The vertical field of view in radians. 174 | pub fn yfov(&self) -> f32 { 175 | self.json.yfov 176 | } 177 | 178 | /// The distance to the far clipping plane. 179 | pub fn zfar(&self) -> Option { 180 | self.json.zfar 181 | } 182 | 183 | /// The distance to the near clipping plane. 184 | pub fn znear(&self) -> f32 { 185 | self.json.znear 186 | } 187 | 188 | /// Returns extension data unknown to this crate version. 189 | #[cfg(feature = "extensions")] 190 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 191 | pub fn extensions(&self) -> Option<&Map> { 192 | let ext = self.json.extensions.as_ref()?; 193 | Some(&ext.others) 194 | } 195 | 196 | /// Queries extension data unknown to this crate version. 197 | #[cfg(feature = "extensions")] 198 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 199 | pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { 200 | let ext = self.json.extensions.as_ref()?; 201 | ext.others.get(ext_name) 202 | } 203 | 204 | /// Optional application specific data. 205 | pub fn extras(&self) -> &'a json::Extras { 206 | &self.json.extras 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /examples/export/main.rs: -------------------------------------------------------------------------------- 1 | use gltf_json as json; 2 | 3 | use std::{fs, mem}; 4 | 5 | use json::validation::Checked::Valid; 6 | use json::validation::USize64; 7 | use std::borrow::Cow; 8 | use std::io::Write; 9 | 10 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 11 | enum Output { 12 | /// Output standard glTF. 13 | Standard, 14 | 15 | /// Output binary glTF. 16 | Binary, 17 | } 18 | 19 | #[derive(Copy, Clone, Debug, bytemuck::NoUninit)] 20 | #[repr(C)] 21 | struct Vertex { 22 | position: [f32; 3], 23 | color: [f32; 3], 24 | } 25 | 26 | /// Calculate bounding coordinates of a list of vertices, used for the clipping distance of the model 27 | fn bounding_coords(points: &[Vertex]) -> ([f32; 3], [f32; 3]) { 28 | let mut min = [f32::MAX, f32::MAX, f32::MAX]; 29 | let mut max = [f32::MIN, f32::MIN, f32::MIN]; 30 | 31 | for point in points { 32 | let p = point.position; 33 | for i in 0..3 { 34 | min[i] = f32::min(min[i], p[i]); 35 | max[i] = f32::max(max[i], p[i]); 36 | } 37 | } 38 | (min, max) 39 | } 40 | 41 | fn align_to_multiple_of_four(n: &mut usize) { 42 | *n = (*n + 3) & !3; 43 | } 44 | 45 | fn to_padded_byte_vector(data: &[T]) -> Vec { 46 | let byte_slice: &[u8] = bytemuck::cast_slice(data); 47 | let mut new_vec: Vec = byte_slice.to_owned(); 48 | 49 | while new_vec.len() % 4 != 0 { 50 | new_vec.push(0); // pad to multiple of four bytes 51 | } 52 | 53 | new_vec 54 | } 55 | 56 | fn export(output: Output) { 57 | let triangle_vertices = vec![ 58 | Vertex { 59 | position: [0.0, 0.5, 0.0], 60 | color: [1.0, 0.0, 0.0], 61 | }, 62 | Vertex { 63 | position: [-0.5, -0.5, 0.0], 64 | color: [0.0, 1.0, 0.0], 65 | }, 66 | Vertex { 67 | position: [0.5, -0.5, 0.0], 68 | color: [0.0, 0.0, 1.0], 69 | }, 70 | ]; 71 | 72 | let (min, max) = bounding_coords(&triangle_vertices); 73 | 74 | let mut root = gltf_json::Root::default(); 75 | 76 | let buffer_length = triangle_vertices.len() * mem::size_of::(); 77 | let buffer = root.push(json::Buffer { 78 | byte_length: USize64::from(buffer_length), 79 | extensions: Default::default(), 80 | extras: Default::default(), 81 | name: None, 82 | uri: if output == Output::Standard { 83 | Some("buffer0.bin".into()) 84 | } else { 85 | None 86 | }, 87 | }); 88 | let buffer_view = root.push(json::buffer::View { 89 | buffer, 90 | byte_length: USize64::from(buffer_length), 91 | byte_offset: None, 92 | byte_stride: Some(json::buffer::Stride(mem::size_of::())), 93 | extensions: Default::default(), 94 | extras: Default::default(), 95 | name: None, 96 | target: Some(Valid(json::buffer::Target::ArrayBuffer)), 97 | }); 98 | let positions = root.push(json::Accessor { 99 | buffer_view: Some(buffer_view), 100 | byte_offset: Some(USize64(0)), 101 | count: USize64::from(triangle_vertices.len()), 102 | component_type: Valid(json::accessor::GenericComponentType( 103 | json::accessor::ComponentType::F32, 104 | )), 105 | extensions: Default::default(), 106 | extras: Default::default(), 107 | type_: Valid(json::accessor::Type::Vec3), 108 | min: Some(json::Value::from(Vec::from(min))), 109 | max: Some(json::Value::from(Vec::from(max))), 110 | name: None, 111 | normalized: false, 112 | sparse: None, 113 | }); 114 | let colors = root.push(json::Accessor { 115 | buffer_view: Some(buffer_view), 116 | byte_offset: Some(USize64::from(3 * mem::size_of::())), 117 | count: USize64::from(triangle_vertices.len()), 118 | component_type: Valid(json::accessor::GenericComponentType( 119 | json::accessor::ComponentType::F32, 120 | )), 121 | extensions: Default::default(), 122 | extras: Default::default(), 123 | type_: Valid(json::accessor::Type::Vec3), 124 | min: None, 125 | max: None, 126 | name: None, 127 | normalized: false, 128 | sparse: None, 129 | }); 130 | 131 | let primitive = json::mesh::Primitive { 132 | attributes: { 133 | let mut map = std::collections::BTreeMap::new(); 134 | map.insert(Valid(json::mesh::Semantic::Positions), positions); 135 | map.insert(Valid(json::mesh::Semantic::Colors(0)), colors); 136 | map 137 | }, 138 | extensions: Default::default(), 139 | extras: Default::default(), 140 | indices: None, 141 | material: None, 142 | mode: Valid(json::mesh::Mode::Triangles), 143 | targets: None, 144 | }; 145 | 146 | let mesh = root.push(json::Mesh { 147 | extensions: Default::default(), 148 | extras: Default::default(), 149 | name: None, 150 | primitives: vec![primitive], 151 | weights: None, 152 | }); 153 | 154 | let node = root.push(json::Node { 155 | mesh: Some(mesh), 156 | ..Default::default() 157 | }); 158 | 159 | root.push(json::Scene { 160 | extensions: Default::default(), 161 | extras: Default::default(), 162 | name: None, 163 | nodes: vec![node], 164 | }); 165 | 166 | match output { 167 | Output::Standard => { 168 | let _ = fs::create_dir("triangle"); 169 | 170 | let writer = fs::File::create("triangle/triangle.gltf").expect("I/O error"); 171 | json::serialize::to_writer_pretty(writer, &root).expect("Serialization error"); 172 | 173 | let bin = to_padded_byte_vector(&triangle_vertices); 174 | let mut writer = fs::File::create("triangle/buffer0.bin").expect("I/O error"); 175 | writer.write_all(&bin).expect("I/O error"); 176 | } 177 | Output::Binary => { 178 | let json_string = json::serialize::to_string(&root).expect("Serialization error"); 179 | let mut json_offset = json_string.len(); 180 | align_to_multiple_of_four(&mut json_offset); 181 | let glb = gltf::binary::Glb { 182 | header: gltf::binary::Header { 183 | magic: *b"glTF", 184 | version: 2, 185 | // N.B., the size of binary glTF file is limited to range of `u32`. 186 | length: (json_offset + buffer_length) 187 | .try_into() 188 | .expect("file size exceeds binary glTF limit"), 189 | }, 190 | bin: Some(Cow::Owned(to_padded_byte_vector(&triangle_vertices))), 191 | json: Cow::Owned(json_string.into_bytes()), 192 | }; 193 | let writer = std::fs::File::create("triangle.glb").expect("I/O error"); 194 | glb.to_writer(writer).expect("glTF binary output error"); 195 | } 196 | } 197 | } 198 | 199 | fn main() { 200 | export(Output::Standard); 201 | export(Output::Binary); 202 | } 203 | -------------------------------------------------------------------------------- /gltf-json/src/validation.rs: -------------------------------------------------------------------------------- 1 | use serde::{ser, Serialize, Serializer}; 2 | use std::collections::BTreeMap; 3 | use std::hash::Hash; 4 | 5 | use crate::{Path, Root}; 6 | 7 | /// Trait for validating glTF JSON data so that the library can function without panicking. 8 | pub trait Validate { 9 | /// Validates the invariants required for the library to function safely. 10 | fn validate(&self, _root: &Root, _path: P, _report: &mut R) 11 | where 12 | P: Fn() -> Path, 13 | R: FnMut(&dyn Fn() -> Path, Error), 14 | { 15 | // nop 16 | } 17 | } 18 | 19 | /// Specifies what kind of error occured during validation. 20 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 21 | pub enum Error { 22 | /// An index was found to be out of bounds. 23 | IndexOutOfBounds, 24 | 25 | /// An invalid value was identified. 26 | Invalid, 27 | 28 | /// Some required data has been omitted. 29 | Missing, 30 | 31 | /// A memory size or offset exceeds the system limits. 32 | Oversize, 33 | 34 | /// One of more required extensions is not supported by this crate version. 35 | Unsupported, 36 | } 37 | 38 | /// Specifies a type that has been pre-validated during deserialization or otherwise. 39 | #[derive(Debug, Eq, Hash, PartialEq, Ord, PartialOrd)] 40 | pub enum Checked { 41 | /// The item is valid. 42 | Valid(T), 43 | 44 | /// The item is invalid. 45 | Invalid, 46 | } 47 | 48 | impl Checked { 49 | /// Converts from `Checked` to `Checked<&T>`. 50 | pub fn as_ref(&self) -> Checked<&T> { 51 | match *self { 52 | Checked::Valid(ref item) => Checked::Valid(item), 53 | Checked::Invalid => Checked::Invalid, 54 | } 55 | } 56 | 57 | /// Takes ownership of the contained item if it is `Valid`. 58 | /// 59 | /// # Panics 60 | /// 61 | /// Panics if called on an `Invalid` item. 62 | pub fn unwrap(self) -> T { 63 | match self { 64 | Checked::Valid(item) => item, 65 | Checked::Invalid => panic!("attempted to unwrap an invalid item"), 66 | } 67 | } 68 | } 69 | 70 | impl Serialize for Checked { 71 | fn serialize(&self, serializer: S) -> Result 72 | where 73 | S: Serializer, 74 | { 75 | match *self { 76 | Checked::Valid(ref item) => item.serialize(serializer), 77 | Checked::Invalid => Err(ser::Error::custom("invalid item")), 78 | } 79 | } 80 | } 81 | 82 | impl Clone for Checked { 83 | fn clone(&self) -> Self { 84 | match *self { 85 | Checked::Valid(ref item) => Checked::Valid(item.clone()), 86 | Checked::Invalid => Checked::Invalid, 87 | } 88 | } 89 | } 90 | 91 | impl Copy for Checked {} 92 | 93 | impl Default for Checked { 94 | fn default() -> Self { 95 | Checked::Valid(T::default()) 96 | } 97 | } 98 | 99 | impl Validate for Checked { 100 | fn validate(&self, _root: &Root, path: P, report: &mut R) 101 | where 102 | P: Fn() -> Path, 103 | R: FnMut(&dyn Fn() -> Path, Error), 104 | { 105 | match *self { 106 | Checked::Valid(_) => {} 107 | Checked::Invalid => report(&path, Error::Invalid), 108 | } 109 | } 110 | } 111 | 112 | /// Validates the suitability of 64-bit byte offsets/sizes on 32-bit systems. 113 | #[derive( 114 | Clone, 115 | Copy, 116 | Debug, 117 | Default, 118 | Eq, 119 | Hash, 120 | PartialEq, 121 | serde_derive::Deserialize, 122 | serde_derive::Serialize, 123 | )] 124 | pub struct USize64(pub u64); 125 | 126 | impl From for USize64 { 127 | fn from(value: u64) -> Self { 128 | Self(value) 129 | } 130 | } 131 | 132 | impl From for USize64 { 133 | fn from(value: usize) -> Self { 134 | Self(value as u64) 135 | } 136 | } 137 | 138 | impl Validate for USize64 { 139 | fn validate(&self, _root: &Root, path: P, report: &mut R) 140 | where 141 | P: Fn() -> Path, 142 | R: FnMut(&dyn Fn() -> Path, Error), 143 | { 144 | if usize::try_from(self.0).is_err() { 145 | report(&path, Error::Oversize); 146 | } 147 | } 148 | } 149 | 150 | impl Validate for BTreeMap { 151 | fn validate(&self, root: &Root, path: P, report: &mut R) 152 | where 153 | P: Fn() -> Path, 154 | R: FnMut(&dyn Fn() -> Path, Error), 155 | { 156 | for (key, value) in self.iter() { 157 | key.validate(root, || path().key(&key.to_string()), report); 158 | value.validate(root, || path().key(&key.to_string()), report); 159 | } 160 | } 161 | } 162 | 163 | impl Validate for serde_json::Map { 164 | fn validate(&self, root: &Root, path: P, report: &mut R) 165 | where 166 | P: Fn() -> Path, 167 | R: FnMut(&dyn Fn() -> Path, Error), 168 | { 169 | for (key, value) in self.iter() { 170 | key.validate(root, || path().key(&key.to_string()), report); 171 | value.validate(root, || path().key(&key.to_string()), report); 172 | } 173 | } 174 | } 175 | 176 | impl Validate for Option { 177 | fn validate(&self, root: &Root, path: P, report: &mut R) 178 | where 179 | P: Fn() -> Path, 180 | R: FnMut(&dyn Fn() -> Path, Error), 181 | { 182 | if let Some(value) = self.as_ref() { 183 | value.validate(root, path, report); 184 | } 185 | } 186 | } 187 | 188 | impl Validate for Vec { 189 | fn validate(&self, root: &Root, path: P, report: &mut R) 190 | where 191 | P: Fn() -> Path, 192 | R: FnMut(&dyn Fn() -> Path, Error), 193 | { 194 | for (index, value) in self.iter().enumerate() { 195 | value.validate(root, || path().index(index), report); 196 | } 197 | } 198 | } 199 | 200 | impl Validate for std::boxed::Box { 201 | fn validate(&self, _: &Root, _: P, _: &mut R) 202 | where 203 | P: Fn() -> Path, 204 | R: FnMut(&dyn Fn() -> Path, Error), 205 | { 206 | // nop 207 | } 208 | } 209 | 210 | impl std::error::Error for Error {} 211 | 212 | impl std::fmt::Display for Error { 213 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 214 | write!( 215 | f, 216 | "{}", 217 | match *self { 218 | Error::IndexOutOfBounds => "Index out of bounds", 219 | Error::Invalid => "Invalid value", 220 | Error::Missing => "Missing data", 221 | Error::Oversize => "Size exceeds system limits", 222 | Error::Unsupported => "Unsupported extension", 223 | } 224 | ) 225 | } 226 | } 227 | 228 | // These types are assumed to be always valid. 229 | impl Validate for bool {} 230 | impl Validate for u32 {} 231 | impl Validate for i32 {} 232 | impl Validate for f32 {} 233 | impl Validate for [f32; 3] {} 234 | impl Validate for [f32; 4] {} 235 | impl Validate for [f32; 16] {} 236 | impl Validate for () {} 237 | impl Validate for String {} 238 | impl Validate for serde_json::Value {} 239 | -------------------------------------------------------------------------------- /src/animation/util/mod.rs: -------------------------------------------------------------------------------- 1 | /// Casting iterator adapters for rotations. 2 | pub mod rotations; 3 | 4 | /// Casting iterator adapters for morph target weights. 5 | pub mod morph_target_weights; 6 | 7 | use crate::accessor; 8 | 9 | use crate::animation::Channel; 10 | use crate::Buffer; 11 | 12 | /// Animation input sampler values of type `f32`. 13 | pub type ReadInputs<'a> = accessor::Iter<'a, f32>; 14 | 15 | /// Animation output sampler values of type `[f32; 3]`. 16 | pub type Translations<'a> = accessor::Iter<'a, [f32; 3]>; 17 | 18 | /// Animation output sampler values of type `[f32; 3]`. 19 | pub type Scales<'a> = accessor::Iter<'a, [f32; 3]>; 20 | 21 | /// Animation channel reader. 22 | #[derive(Clone, Debug)] 23 | pub struct Reader<'a, 's, F> 24 | where 25 | F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, 26 | { 27 | pub(crate) channel: Channel<'a>, 28 | pub(crate) get_buffer_data: F, 29 | } 30 | 31 | /// Rotation animations 32 | #[derive(Clone, Debug)] 33 | pub enum Rotations<'a> { 34 | /// Rotations of type `[i8; 4]`. 35 | I8(accessor::Iter<'a, [i8; 4]>), 36 | /// Rotations of type `[u8; 4]`. 37 | U8(accessor::Iter<'a, [u8; 4]>), 38 | /// Rotations of type `[i16; 4]`. 39 | I16(accessor::Iter<'a, [i16; 4]>), 40 | /// Rotations of type `[u16; 4]`. 41 | U16(accessor::Iter<'a, [u16; 4]>), 42 | /// Rotations of type `[f32; 4]`. 43 | F32(accessor::Iter<'a, [f32; 4]>), 44 | } 45 | 46 | /// Morph-target weight animations. 47 | #[derive(Clone, Debug)] 48 | pub enum MorphTargetWeights<'a> { 49 | /// Weights of type `i8`. 50 | I8(accessor::Iter<'a, i8>), 51 | /// Weights of type `u8`. 52 | U8(accessor::Iter<'a, u8>), 53 | /// Weights of type `i16`. 54 | I16(accessor::Iter<'a, i16>), 55 | /// Weights of type `u16`. 56 | U16(accessor::Iter<'a, u16>), 57 | /// Weights of type `f32`. 58 | F32(accessor::Iter<'a, f32>), 59 | } 60 | 61 | /// Animation output sampler values. 62 | pub enum ReadOutputs<'a> { 63 | /// XYZ translations of type `[f32; 3]`. 64 | Translations(Translations<'a>), 65 | 66 | /// Rotation animations. 67 | Rotations(Rotations<'a>), 68 | 69 | /// XYZ scales of type `[f32; 3]`. 70 | Scales(Scales<'a>), 71 | 72 | /// Morph target animations. 73 | MorphTargetWeights(MorphTargetWeights<'a>), 74 | } 75 | 76 | impl<'a> Rotations<'a> { 77 | /// Reinterpret rotations as u16. Lossy if underlying iterator yields u8, 78 | /// i16, u16 or f32. 79 | pub fn into_i8(self) -> rotations::CastingIter<'a, rotations::I8> { 80 | rotations::CastingIter::new(self) 81 | } 82 | 83 | /// Reinterpret rotations as u16. Lossy if underlying iterator yields i16, 84 | /// u16 or f32. 85 | pub fn into_u8(self) -> rotations::CastingIter<'a, rotations::U8> { 86 | rotations::CastingIter::new(self) 87 | } 88 | 89 | /// Reinterpret rotations as u16. Lossy if underlying iterator yields u16 90 | /// or f32. 91 | pub fn into_i16(self) -> rotations::CastingIter<'a, rotations::I16> { 92 | rotations::CastingIter::new(self) 93 | } 94 | 95 | /// Reinterpret rotations as u16. Lossy if underlying iterator yields f32. 96 | pub fn into_u16(self) -> rotations::CastingIter<'a, rotations::U16> { 97 | rotations::CastingIter::new(self) 98 | } 99 | 100 | /// Reinterpret rotations as f32. Lossy if underlying iterator yields i16 101 | /// or u16. 102 | pub fn into_f32(self) -> rotations::CastingIter<'a, rotations::F32> { 103 | rotations::CastingIter::new(self) 104 | } 105 | } 106 | 107 | impl<'a> MorphTargetWeights<'a> { 108 | /// Reinterpret morph weights as u16. Lossy if underlying iterator yields 109 | /// u8, i16, u16 or f32. 110 | pub fn into_i8(self) -> morph_target_weights::CastingIter<'a, morph_target_weights::I8> { 111 | morph_target_weights::CastingIter::new(self) 112 | } 113 | 114 | /// Reinterpret morph weights as u16. Lossy if underlying iterator yields 115 | /// i16, u16 or f32. 116 | pub fn into_u8(self) -> morph_target_weights::CastingIter<'a, morph_target_weights::U8> { 117 | morph_target_weights::CastingIter::new(self) 118 | } 119 | 120 | /// Reinterpret morph weights as u16. Lossy if underlying iterator yields 121 | /// u16 or f32. 122 | pub fn into_i16(self) -> morph_target_weights::CastingIter<'a, morph_target_weights::I16> { 123 | morph_target_weights::CastingIter::new(self) 124 | } 125 | 126 | /// Reinterpret morph weights as u16. Lossy if underlying iterator yields 127 | /// f32. 128 | pub fn into_u16(self) -> morph_target_weights::CastingIter<'a, morph_target_weights::U16> { 129 | morph_target_weights::CastingIter::new(self) 130 | } 131 | 132 | /// Reinterpret morph weights as f32. Lossy if underlying iterator yields 133 | /// i16 or u16. 134 | pub fn into_f32(self) -> morph_target_weights::CastingIter<'a, morph_target_weights::F32> { 135 | morph_target_weights::CastingIter::new(self) 136 | } 137 | } 138 | 139 | impl<'a, 's, F> Reader<'a, 's, F> 140 | where 141 | F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, 142 | { 143 | /// Visits the input samples of a channel. 144 | pub fn read_inputs(&self) -> Option> { 145 | accessor::Iter::new(self.channel.sampler().input(), self.get_buffer_data.clone()) 146 | } 147 | 148 | /// Visits the output samples of a channel. 149 | pub fn read_outputs(&self) -> Option> { 150 | use crate::animation::Property; 151 | use accessor::{DataType, Iter}; 152 | let output = self.channel.sampler().output(); 153 | match self.channel.target().property() { 154 | Property::Translation => { 155 | Iter::new(output, self.get_buffer_data.clone()).map(ReadOutputs::Translations) 156 | } 157 | Property::Rotation => match output.data_type() { 158 | DataType::I8 => Iter::new(output, self.get_buffer_data.clone()) 159 | .map(|x| ReadOutputs::Rotations(Rotations::I8(x))), 160 | DataType::U8 => Iter::new(output, self.get_buffer_data.clone()) 161 | .map(|x| ReadOutputs::Rotations(Rotations::U8(x))), 162 | DataType::I16 => Iter::new(output, self.get_buffer_data.clone()) 163 | .map(|x| ReadOutputs::Rotations(Rotations::I16(x))), 164 | DataType::U16 => Iter::new(output, self.get_buffer_data.clone()) 165 | .map(|x| ReadOutputs::Rotations(Rotations::U16(x))), 166 | DataType::F32 => Iter::new(output, self.get_buffer_data.clone()) 167 | .map(|x| ReadOutputs::Rotations(Rotations::F32(x))), 168 | _ => unreachable!(), 169 | }, 170 | Property::Scale => { 171 | Iter::new(output, self.get_buffer_data.clone()).map(ReadOutputs::Scales) 172 | } 173 | Property::MorphTargetWeights => match output.data_type() { 174 | DataType::I8 => Iter::new(output, self.get_buffer_data.clone()) 175 | .map(|x| ReadOutputs::MorphTargetWeights(MorphTargetWeights::I8(x))), 176 | DataType::U8 => Iter::new(output, self.get_buffer_data.clone()) 177 | .map(|x| ReadOutputs::MorphTargetWeights(MorphTargetWeights::U8(x))), 178 | DataType::I16 => Iter::new(output, self.get_buffer_data.clone()) 179 | .map(|x| ReadOutputs::MorphTargetWeights(MorphTargetWeights::I16(x))), 180 | DataType::U16 => Iter::new(output, self.get_buffer_data.clone()) 181 | .map(|x| ReadOutputs::MorphTargetWeights(MorphTargetWeights::U16(x))), 182 | DataType::F32 => Iter::new(output, self.get_buffer_data.clone()) 183 | .map(|x| ReadOutputs::MorphTargetWeights(MorphTargetWeights::F32(x))), 184 | _ => unreachable!(), 185 | }, 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/animation/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{accessor, scene, Document}; 2 | 3 | #[cfg(feature = "utils")] 4 | use crate::Buffer; 5 | 6 | pub use json::animation::{Interpolation, Property}; 7 | #[cfg(feature = "extensions")] 8 | use serde_json::{Map, Value}; 9 | 10 | /// Iterators. 11 | pub mod iter; 12 | 13 | /// Utility functions. 14 | #[cfg(feature = "utils")] 15 | #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] 16 | pub mod util; 17 | 18 | #[cfg(feature = "utils")] 19 | #[doc(inline)] 20 | pub use self::util::Reader; 21 | 22 | /// A keyframe animation. 23 | #[derive(Clone, Debug)] 24 | pub struct Animation<'a> { 25 | /// The parent `Document` struct. 26 | document: &'a Document, 27 | 28 | /// The corresponding JSON index. 29 | index: usize, 30 | 31 | /// The corresponding JSON struct. 32 | json: &'a json::animation::Animation, 33 | } 34 | 35 | /// Targets an animation's sampler at a node's property. 36 | #[derive(Clone, Debug)] 37 | pub struct Channel<'a> { 38 | /// The parent `Animation` struct. 39 | anim: Animation<'a>, 40 | 41 | /// The corresponding JSON index. 42 | index: usize, 43 | 44 | /// The corresponding JSON struct. 45 | json: &'a json::animation::Channel, 46 | } 47 | 48 | /// Defines a keyframe graph (but not its target). 49 | #[derive(Clone, Debug)] 50 | pub struct Sampler<'a> { 51 | /// The parent `Animation` struct. 52 | anim: Animation<'a>, 53 | 54 | /// The corresponding JSON index. 55 | index: usize, 56 | 57 | /// The corresponding JSON struct. 58 | json: &'a json::animation::Sampler, 59 | } 60 | 61 | /// The node and TRS property that an animation channel targets. 62 | #[derive(Clone, Debug)] 63 | pub struct Target<'a> { 64 | /// The parent `Animation` struct. 65 | anim: Animation<'a>, 66 | 67 | /// The corresponding JSON struct. 68 | json: &'a json::animation::Target, 69 | } 70 | 71 | impl<'a> Animation<'a> { 72 | /// Constructs an `Animation`. 73 | pub(crate) fn new( 74 | document: &'a Document, 75 | index: usize, 76 | json: &'a json::animation::Animation, 77 | ) -> Self { 78 | Self { 79 | document, 80 | index, 81 | json, 82 | } 83 | } 84 | 85 | /// Returns the internal JSON index. 86 | pub fn index(&self) -> usize { 87 | self.index 88 | } 89 | 90 | /// Optional application specific data. 91 | pub fn extras(&self) -> &'a json::Extras { 92 | &self.json.extras 93 | } 94 | 95 | /// Returns an `Iterator` over the animation channels. 96 | /// 97 | /// Each channel targets an animation's sampler at a node's property. 98 | pub fn channels(&self) -> iter::Channels<'a> { 99 | iter::Channels { 100 | anim: self.clone(), 101 | iter: self.json.channels.iter().enumerate(), 102 | } 103 | } 104 | 105 | /// Optional user-defined name for this object. 106 | #[cfg(feature = "names")] 107 | pub fn name(&self) -> Option<&'a str> { 108 | self.json.name.as_deref() 109 | } 110 | 111 | /// Returns an `Iterator` over the animation samplers. 112 | /// 113 | /// Each sampler combines input and output accessors with an 114 | /// interpolation algorithm to define a keyframe graph (but not its target). 115 | pub fn samplers(&self) -> iter::Samplers<'a> { 116 | iter::Samplers { 117 | anim: self.clone(), 118 | iter: self.json.samplers.iter().enumerate(), 119 | } 120 | } 121 | 122 | /// Returns extension data unknown to this crate version. 123 | #[cfg(feature = "extensions")] 124 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 125 | pub fn extensions(&self) -> Option<&Map> { 126 | let ext = self.json.extensions.as_ref()?; 127 | Some(&ext.others) 128 | } 129 | 130 | /// Queries extension data unknown to this crate version. 131 | #[cfg(feature = "extensions")] 132 | #[cfg_attr(docsrs, doc(cfg(feature = "extensions")))] 133 | pub fn extension_value(&self, ext_name: &str) -> Option<&Value> { 134 | let ext = self.json.extensions.as_ref()?; 135 | ext.others.get(ext_name) 136 | } 137 | } 138 | 139 | impl<'a> Channel<'a> { 140 | /// Constructs a `Channel`. 141 | pub(crate) fn new( 142 | anim: Animation<'a>, 143 | json: &'a json::animation::Channel, 144 | index: usize, 145 | ) -> Self { 146 | Self { anim, json, index } 147 | } 148 | 149 | /// Returns the parent `Animation` struct. 150 | pub fn animation(&self) -> Animation<'a> { 151 | self.anim.clone() 152 | } 153 | 154 | /// Returns the sampler in this animation used to compute the value for the 155 | /// target. 156 | pub fn sampler(&self) -> Sampler<'a> { 157 | self.anim.samplers().nth(self.json.sampler.value()).unwrap() 158 | } 159 | 160 | /// Returns the node and property to target. 161 | pub fn target(&self) -> Target<'a> { 162 | Target::new(self.anim.clone(), &self.json.target) 163 | } 164 | 165 | /// Constructs an animation channel reader. 166 | #[cfg(feature = "utils")] 167 | #[cfg_attr(docsrs, doc(cfg(feature = "utils")))] 168 | pub fn reader<'s, F>(&self, get_buffer_data: F) -> Reader<'a, 's, F> 169 | where 170 | F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, 171 | { 172 | Reader { 173 | channel: self.clone(), 174 | get_buffer_data, 175 | } 176 | } 177 | 178 | /// Optional application specific data. 179 | pub fn extras(&self) -> &'a json::Extras { 180 | &self.json.extras 181 | } 182 | 183 | /// Returns the internal JSON index. 184 | pub fn index(&self) -> usize { 185 | self.index 186 | } 187 | } 188 | 189 | impl<'a> Target<'a> { 190 | /// Constructs a `Target`. 191 | pub(crate) fn new(anim: Animation<'a>, json: &'a json::animation::Target) -> Self { 192 | Self { anim, json } 193 | } 194 | 195 | /// Returns the parent `Animation` struct. 196 | pub fn animation(&self) -> Animation<'a> { 197 | self.anim.clone() 198 | } 199 | 200 | /// Optional application specific data. 201 | pub fn extras(&self) -> &'a json::Extras { 202 | &self.json.extras 203 | } 204 | 205 | /// Returns the target node. 206 | pub fn node(&self) -> scene::Node<'a> { 207 | self.anim 208 | .document 209 | .nodes() 210 | .nth(self.json.node.value()) 211 | .unwrap() 212 | } 213 | 214 | /// Returns the node's property to modify or the 'weights' of the morph 215 | /// targets it instantiates. 216 | pub fn property(&self) -> Property { 217 | self.json.path.unwrap() 218 | } 219 | } 220 | 221 | impl<'a> Sampler<'a> { 222 | /// Constructs a `Sampler`. 223 | pub(crate) fn new( 224 | anim: Animation<'a>, 225 | json: &'a json::animation::Sampler, 226 | index: usize, 227 | ) -> Self { 228 | Self { anim, json, index } 229 | } 230 | 231 | /// Returns the parent `Animation` struct. 232 | pub fn animation(&self) -> Animation<'a> { 233 | self.anim.clone() 234 | } 235 | 236 | /// Optional application specific data. 237 | pub fn extras(&self) -> &'a json::Extras { 238 | &self.json.extras 239 | } 240 | 241 | /// Returns the internal JSON index. 242 | pub fn index(&self) -> usize { 243 | self.index 244 | } 245 | 246 | /// Returns the accessor containing the keyframe input values (e.g. time). 247 | pub fn input(&self) -> accessor::Accessor<'a> { 248 | self.anim 249 | .document 250 | .accessors() 251 | .nth(self.json.input.value()) 252 | .unwrap() 253 | } 254 | 255 | /// Returns the keyframe interpolation algorithm. 256 | pub fn interpolation(&self) -> Interpolation { 257 | self.json.interpolation.unwrap() 258 | } 259 | 260 | /// Returns the accessor containing the keyframe output values. 261 | pub fn output(&self) -> accessor::Accessor<'a> { 262 | self.anim 263 | .document 264 | .accessors() 265 | .nth(self.json.output.value()) 266 | .unwrap() 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/mesh/util/mod.rs: -------------------------------------------------------------------------------- 1 | /// Casting iterator adapters for colors. 2 | pub mod colors; 3 | 4 | /// Casting iterator adapters for vertex indices. 5 | pub mod indices; 6 | 7 | /// Casting iterator adapters for joint indices. 8 | pub mod joints; 9 | 10 | /// Casting iterator adapters for texture co-ordinates. 11 | pub mod tex_coords; 12 | 13 | /// Casting iterator adapters for node weights. 14 | pub mod weights; 15 | 16 | use crate::mesh; 17 | 18 | use crate::accessor::Iter; 19 | use crate::Buffer; 20 | 21 | /// XYZ vertex positions of type `[f32; 3]`. 22 | pub type ReadPositions<'a> = Iter<'a, [f32; 3]>; 23 | 24 | /// XYZ vertex normals of type `[f32; 3]`. 25 | pub type ReadNormals<'a> = Iter<'a, [f32; 3]>; 26 | 27 | /// XYZW vertex tangents of type `[f32; 4]` where the `w` component is a 28 | /// sign value (-1 or +1) indicating the handedness of the tangent basis. 29 | pub type ReadTangents<'a> = Iter<'a, [f32; 4]>; 30 | 31 | /// XYZ vertex position displacements of type `[f32; 3]`. 32 | pub type ReadPositionDisplacements<'a> = Iter<'a, [f32; 3]>; 33 | 34 | /// XYZ vertex normal displacements of type `[f32; 3]`. 35 | pub type ReadNormalDisplacements<'a> = Iter<'a, [f32; 3]>; 36 | 37 | /// XYZ vertex tangent displacements. 38 | pub type ReadTangentDisplacements<'a> = Iter<'a, [f32; 3]>; 39 | 40 | /// Vertex colors. 41 | #[derive(Clone, Debug)] 42 | pub enum ReadColors<'a> { 43 | /// RGB vertex color of type `[u8; 3]>`. 44 | RgbU8(Iter<'a, [u8; 3]>), 45 | /// RGB vertex color of type `[u16; 3]>`. 46 | RgbU16(Iter<'a, [u16; 3]>), 47 | /// RGB vertex color of type `[f32; 3]`. 48 | RgbF32(Iter<'a, [f32; 3]>), 49 | /// RGBA vertex color of type `[u8; 4]>`. 50 | RgbaU8(Iter<'a, [u8; 4]>), 51 | /// RGBA vertex color of type `[u16; 4]>`. 52 | RgbaU16(Iter<'a, [u16; 4]>), 53 | /// RGBA vertex color of type `[f32; 4]`. 54 | RgbaF32(Iter<'a, [f32; 4]>), 55 | } 56 | 57 | /// Index data. 58 | #[derive(Clone, Debug)] 59 | pub enum ReadIndices<'a> { 60 | /// Index data of type U8 61 | U8(Iter<'a, u8>), 62 | /// Index data of type U16 63 | U16(Iter<'a, u16>), 64 | /// Index data of type U32 65 | U32(Iter<'a, u32>), 66 | } 67 | 68 | /// Vertex joints. 69 | #[derive(Clone, Debug)] 70 | pub enum ReadJoints<'a> { 71 | /// Joints of type `[u8; 4]`. 72 | /// Refer to the documentation on morph targets and skins for more 73 | /// information. 74 | U8(Iter<'a, [u8; 4]>), 75 | /// Joints of type `[u16; 4]`. 76 | /// Refer to the documentation on morph targets and skins for more 77 | /// information. 78 | U16(Iter<'a, [u16; 4]>), 79 | } 80 | 81 | /// UV texture co-ordinates. 82 | #[derive(Clone, Debug)] 83 | pub enum ReadTexCoords<'a> { 84 | /// UV texture co-ordinates of type `[u8; 2]>`. 85 | U8(Iter<'a, [u8; 2]>), 86 | /// UV texture co-ordinates of type `[u16; 2]>`. 87 | U16(Iter<'a, [u16; 2]>), 88 | /// UV texture co-ordinates of type `[f32; 2]`. 89 | F32(Iter<'a, [f32; 2]>), 90 | } 91 | 92 | /// Weights. 93 | #[derive(Clone, Debug)] 94 | pub enum ReadWeights<'a> { 95 | /// Weights of type `[u8; 4]`. 96 | U8(Iter<'a, [u8; 4]>), 97 | /// Weights of type `[u16; 4]`. 98 | U16(Iter<'a, [u16; 4]>), 99 | /// Weights of type `[f32; 4]`. 100 | F32(Iter<'a, [f32; 4]>), 101 | } 102 | 103 | /// Morph targets. 104 | #[derive(Clone, Debug)] 105 | pub struct ReadMorphTargets<'a, 's, F> 106 | where 107 | F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, 108 | { 109 | pub(crate) index: usize, 110 | pub(crate) reader: mesh::Reader<'a, 's, F>, 111 | } 112 | 113 | impl<'a, 's, F> ExactSizeIterator for ReadMorphTargets<'a, 's, F> where 114 | F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]> 115 | { 116 | } 117 | 118 | impl<'a, 's, F> Iterator for ReadMorphTargets<'a, 's, F> 119 | where 120 | F: Clone + Fn(Buffer<'a>) -> Option<&'s [u8]>, 121 | { 122 | type Item = ( 123 | Option>, 124 | Option>, 125 | Option>, 126 | ); 127 | fn next(&mut self) -> Option { 128 | self.index += 1; 129 | self.reader 130 | .primitive 131 | .morph_targets() 132 | .nth(self.index - 1) 133 | .map(|morph_target| { 134 | let positions = morph_target 135 | .positions() 136 | .and_then(|accessor| Iter::new(accessor, self.reader.get_buffer_data.clone())); 137 | let normals = morph_target 138 | .normals() 139 | .and_then(|accessor| Iter::new(accessor, self.reader.get_buffer_data.clone())); 140 | let tangents = morph_target 141 | .tangents() 142 | .and_then(|accessor| Iter::new(accessor, self.reader.get_buffer_data.clone())); 143 | (positions, normals, tangents) 144 | }) 145 | } 146 | 147 | fn size_hint(&self) -> (usize, Option) { 148 | self.reader.primitive.morph_targets().size_hint() 149 | } 150 | } 151 | 152 | impl<'a> ReadColors<'a> { 153 | /// Reinterpret colors as RGB u8, discarding alpha, if present. Lossy if 154 | /// the underlying iterator yields u16, f32 or any RGBA. 155 | pub fn into_rgb_u8(self) -> self::colors::CastingIter<'a, self::colors::RgbU8> { 156 | self::colors::CastingIter::new(self) 157 | } 158 | 159 | /// Reinterpret colors as RGB u16, discarding alpha, if present. Lossy if 160 | /// the underlying iterator yields f32 or any RGBA. 161 | pub fn into_rgb_u16(self) -> self::colors::CastingIter<'a, self::colors::RgbU16> { 162 | self::colors::CastingIter::new(self) 163 | } 164 | 165 | /// Reinterpret colors as RGB f32, discarding alpha, if present. Lossy if 166 | /// the underlying iterator yields u16 or any RGBA. 167 | pub fn into_rgb_f32(self) -> self::colors::CastingIter<'a, self::colors::RgbF32> { 168 | self::colors::CastingIter::new(self) 169 | } 170 | 171 | /// Reinterpret colors as RGBA u8, with default alpha 255. Lossy if the 172 | /// underlying iterator yields u16 or f32. 173 | pub fn into_rgba_u8(self) -> self::colors::CastingIter<'a, self::colors::RgbaU8> { 174 | self::colors::CastingIter::new(self) 175 | } 176 | 177 | /// Reinterpret colors as RGBA u16, with default alpha 65535. Lossy if the 178 | /// underlying iterator yields f32. 179 | pub fn into_rgba_u16(self) -> self::colors::CastingIter<'a, self::colors::RgbaU16> { 180 | self::colors::CastingIter::new(self) 181 | } 182 | 183 | /// Reinterpret colors as RGBA f32, with default alpha 1.0. Lossy if the 184 | /// underlying iterator yields u16. 185 | pub fn into_rgba_f32(self) -> self::colors::CastingIter<'a, self::colors::RgbaF32> { 186 | self::colors::CastingIter::new(self) 187 | } 188 | } 189 | 190 | impl<'a> ReadIndices<'a> { 191 | /// Reinterpret indices as u32, which can fit any possible index. 192 | pub fn into_u32(self) -> self::indices::CastingIter<'a, self::indices::U32> { 193 | self::indices::CastingIter::new(self) 194 | } 195 | } 196 | 197 | impl<'a> ReadJoints<'a> { 198 | /// Reinterpret joints as u16, which can fit any possible joint. 199 | pub fn into_u16(self) -> self::joints::CastingIter<'a, self::joints::U16> { 200 | self::joints::CastingIter::new(self) 201 | } 202 | } 203 | 204 | impl<'a> ReadTexCoords<'a> { 205 | /// Reinterpret texture coordinates as u8. Lossy if the underlying iterator 206 | /// yields u16 or f32. 207 | pub fn into_u8(self) -> self::tex_coords::CastingIter<'a, self::tex_coords::U8> { 208 | self::tex_coords::CastingIter::new(self) 209 | } 210 | 211 | /// Reinterpret texture coordinates as u16. Lossy if the underlying 212 | /// iterator yields f32. 213 | pub fn into_u16(self) -> self::tex_coords::CastingIter<'a, self::tex_coords::U16> { 214 | self::tex_coords::CastingIter::new(self) 215 | } 216 | 217 | /// Reinterpret texture coordinates as f32. Lossy if the underlying 218 | /// iterator yields u16. 219 | pub fn into_f32(self) -> self::tex_coords::CastingIter<'a, self::tex_coords::F32> { 220 | self::tex_coords::CastingIter::new(self) 221 | } 222 | } 223 | 224 | impl<'a> ReadWeights<'a> { 225 | /// Reinterpret weights as u8. Lossy if the underlying iterator yields u16 226 | /// or f32. 227 | pub fn into_u8(self) -> self::weights::CastingIter<'a, self::weights::U8> { 228 | self::weights::CastingIter::new(self) 229 | } 230 | 231 | /// Reinterpret weights as u16. Lossy if the underlying iterator yields 232 | /// f32. 233 | pub fn into_u16(self) -> self::weights::CastingIter<'a, self::weights::U16> { 234 | self::weights::CastingIter::new(self) 235 | } 236 | 237 | /// Reinterpret weights as f32. Lossy if the underlying iterator yields 238 | /// u16. 239 | pub fn into_f32(self) -> self::weights::CastingIter<'a, self::weights::F32> { 240 | self::weights::CastingIter::new(self) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /gltf-json/src/extensions/scene.rs: -------------------------------------------------------------------------------- 1 | use gltf_derive::Validate; 2 | use serde_derive::{Deserialize, Serialize}; 3 | #[cfg(feature = "extensions")] 4 | use serde_json::{Map, Value}; 5 | 6 | /// A node in the node hierarchy. When the node contains `skin`, all 7 | /// `mesh.primitives` must contain `JOINTS_0` and `WEIGHTS_0` attributes. 8 | /// A node can have either a `matrix` or any combination of 9 | /// `translation`/`rotation`/`scale` (TRS) properties. TRS properties are converted 10 | /// to matrices and postmultiplied in the `T * R * S` order to compose the 11 | /// transformation matrix; first the scale is applied to the vertices, then the 12 | /// rotation, and then the translation. If none are provided, the transform is the 13 | /// identity. When a node is targeted for animation (referenced by an 14 | /// animation.channel.target), only TRS properties may be present; `matrix` will not 15 | /// be present. 16 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 17 | pub struct Node { 18 | #[cfg(feature = "KHR_lights_punctual")] 19 | #[serde( 20 | default, 21 | rename = "KHR_lights_punctual", 22 | skip_serializing_if = "Option::is_none" 23 | )] 24 | pub khr_lights_punctual: Option, 25 | 26 | #[cfg(feature = "extensions")] 27 | #[serde(default, flatten)] 28 | pub others: Map, 29 | } 30 | 31 | #[cfg(feature = "KHR_lights_punctual")] 32 | pub mod khr_lights_punctual { 33 | use crate::validation::{Checked, Error}; 34 | use crate::{Extras, Index, Path, Root}; 35 | use gltf_derive::Validate; 36 | use serde::{de, ser}; 37 | use serde_derive::{Deserialize, Serialize}; 38 | use std::fmt; 39 | 40 | /// All valid light types. 41 | pub const VALID_TYPES: &[&str] = &["directional", "point", "spot"]; 42 | 43 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 44 | pub struct KhrLightsPunctual { 45 | pub light: Index, 46 | } 47 | 48 | /// Specifies the light type. 49 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 50 | pub enum Type { 51 | /// Directional lights act as though they are infinitely far away and emit light in 52 | /// the direction of the local -z axis. This light type inherits the orientation of 53 | /// the node that it belongs to; position and scale are ignored except for their 54 | /// effect on the inherited node orientation. Because it is at an infinite distance, 55 | /// the light is not attenuated. Its intensity is defined in lumens per metre squared, 56 | /// or lux (lm/m^2). 57 | Directional = 1, 58 | 59 | /// Point lights emit light in all directions from their position in space; rotation 60 | /// and scale are ignored except for their effect on the inherited node position. The 61 | /// brightness of the light attenuates in a physically correct manner as distance 62 | /// increases from the light's position (i.e. brightness goes like the inverse square 63 | /// of the distance). Point light intensity is defined in candela, which is lumens per 64 | /// square radian (lm/sr)." 65 | Point, 66 | 67 | /// Spot lights emit light in a cone in the direction of the local -z axis. The angle 68 | /// and falloff of the cone is defined using two numbers, the innerConeAngle and outer 69 | /// ConeAngle. As with point lights, the brightness also attenuates in a physically 70 | /// correct manner as distance increases from the light's position (i.e. brightness 71 | /// goes like the inverse square of the distance). Spot light intensity refers to the 72 | /// brightness inside the innerConeAngle (and at the location of the light) and is 73 | /// defined in candela, which is lumens per square radian (lm/sr). Engines that don't 74 | /// support two angles for spotlights should use outerConeAngle as the spotlight angle 75 | /// (leaving innerConeAngle to implicitly be 0). 76 | Spot, 77 | } 78 | 79 | #[derive(Clone, Debug, Deserialize, Serialize, Validate)] 80 | #[gltf(validate_hook = "light_validate_hook")] 81 | pub struct Light { 82 | /// Color of the light source. 83 | #[serde(default = "color_default")] 84 | pub color: [f32; 3], 85 | 86 | /// Extension specific data. 87 | #[serde(default, skip_serializing_if = "Option::is_none")] 88 | pub extensions: Option>, 89 | 90 | /// Optional application specific data. 91 | #[serde(default)] 92 | #[cfg_attr(feature = "extras", serde(skip_serializing_if = "Option::is_none"))] 93 | #[cfg_attr(not(feature = "extras"), serde(skip_serializing))] 94 | pub extras: Extras, 95 | 96 | /// Intensity of the light source. `point` and `spot` lights use luminous intensity 97 | /// in candela (lm/sr) while `directional` lights use illuminance in lux (lm/m^2). 98 | #[serde(default = "intensity_default")] 99 | pub intensity: f32, 100 | 101 | /// Optional user-defined name for this object. 102 | #[cfg(feature = "names")] 103 | #[cfg_attr(feature = "names", serde(skip_serializing_if = "Option::is_none"))] 104 | pub name: Option, 105 | 106 | /// A distance cutoff at which the light's intensity may be considered to have reached 107 | /// zero. 108 | #[serde(default, skip_serializing_if = "Option::is_none")] 109 | pub range: Option, 110 | 111 | /// Spot light parameters. 112 | #[serde(default, skip_serializing_if = "Option::is_none")] 113 | pub spot: Option, 114 | 115 | /// Specifies the light type. 116 | #[serde(rename = "type")] 117 | pub type_: Checked, 118 | } 119 | 120 | fn light_validate_hook(light: &Light, _root: &Root, path: P, report: &mut R) 121 | where 122 | P: Fn() -> Path, 123 | R: FnMut(&dyn Fn() -> Path, Error), 124 | { 125 | if let Checked::Valid(ty) = light.type_.as_ref() { 126 | if *ty == Type::Spot && light.spot.is_none() { 127 | report(&|| path().field("spot"), Error::Missing); 128 | } 129 | } 130 | } 131 | 132 | fn color_default() -> [f32; 3] { 133 | [1.0, 1.0, 1.0] 134 | } 135 | 136 | fn intensity_default() -> f32 { 137 | 1.0 138 | } 139 | 140 | /// Spot light parameters. 141 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 142 | #[serde(rename_all = "camelCase")] 143 | pub struct Spot { 144 | /// Angle in radians from centre of spotlight where falloff begins. 145 | #[serde(default)] 146 | pub inner_cone_angle: f32, 147 | 148 | /// Angle in radians from centre of spotlight where falloff ends. 149 | #[serde(default = "outer_cone_angle_default")] 150 | pub outer_cone_angle: f32, 151 | } 152 | 153 | fn outer_cone_angle_default() -> f32 { 154 | std::f32::consts::FRAC_PI_4 155 | } 156 | 157 | impl<'de> de::Deserialize<'de> for Checked { 158 | fn deserialize(deserializer: D) -> Result 159 | where 160 | D: de::Deserializer<'de>, 161 | { 162 | struct Visitor; 163 | impl<'de> de::Visitor<'de> for Visitor { 164 | type Value = Checked; 165 | 166 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 167 | write!(f, "any of: {:?}", VALID_TYPES) 168 | } 169 | 170 | fn visit_str(self, value: &str) -> Result 171 | where 172 | E: de::Error, 173 | { 174 | use self::Type::*; 175 | use crate::validation::Checked::*; 176 | Ok(match value { 177 | "directional" => Valid(Directional), 178 | "point" => Valid(Point), 179 | "spot" => Valid(Spot), 180 | _ => Invalid, 181 | }) 182 | } 183 | } 184 | deserializer.deserialize_str(Visitor) 185 | } 186 | } 187 | 188 | impl ser::Serialize for Type { 189 | fn serialize(&self, serializer: S) -> Result 190 | where 191 | S: ser::Serializer, 192 | { 193 | serializer.serialize_str(match *self { 194 | Type::Directional => "directional", 195 | Type::Point => "point", 196 | Type::Spot => "spot", 197 | }) 198 | } 199 | } 200 | } 201 | 202 | #[cfg(feature = "KHR_materials_variants")] 203 | pub mod khr_materials_variants { 204 | use crate::validation::{Error, Validate}; 205 | use crate::{Path, Root}; 206 | use serde_derive::{Deserialize, Serialize}; 207 | 208 | #[derive(Clone, Debug, Deserialize, Serialize)] 209 | pub struct Variant { 210 | pub name: String, 211 | } 212 | 213 | impl Validate for Variant { 214 | fn validate(&self, root: &Root, path: P, report: &mut R) 215 | where 216 | P: Fn() -> Path, 217 | R: FnMut(&dyn Fn() -> Path, Error), 218 | { 219 | self.name.validate(root, || path().field("name"), report); 220 | } 221 | } 222 | } 223 | 224 | /// The root `Node`s of a scene. 225 | #[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)] 226 | pub struct Scene { 227 | #[cfg(feature = "extensions")] 228 | #[serde(default, flatten)] 229 | pub others: Map, 230 | } 231 | --------------------------------------------------------------------------------