easy_gltf/
lib.rs

1#![deny(missing_docs)]
2
3//! This crate is intended to load [glTF 2.0](https://www.khronos.org/gltf), a
4//! file format designed for the efficient transmission of 3D assets.
5//!
6//! It's base on [gltf](https://github.com/gltf-rs/gltf) crate but has an easy to use output.
7//!
8//! # Example
9//!
10//! ```
11//! let scenes = easy_gltf::load("tests/cube.glb").expect("Failed to load glTF");
12//! for scene in scenes {
13//!     println!(
14//!         "Cameras: #{}  Lights: #{}  Models: #{}",
15//!         scene.cameras.len(),
16//!         scene.lights.len(),
17//!         scene.models.len()
18//!     )
19//! }
20//! ```
21
22mod scene;
23mod utils;
24
25use std::error::Error;
26use std::path::Path;
27use utils::GltfData;
28
29pub use scene::*;
30
31/// Load scenes from path to a glTF 2.0.
32///
33/// Note: You can use this function with either a `Gltf` (standard `glTF`) or `Glb` (binary glTF).
34///
35/// # Example
36///
37/// ```
38/// let scenes = easy_gltf::load("tests/cube.glb").expect("Failed to load glTF");
39/// println!("Scenes: #{}", scenes.len()); // Output "Scenes: #1"
40/// let scene = &scenes[0]; // Retrieve the first and only scene
41/// println!("Cameras: #{}", scene.cameras.len());
42/// println!("Lights: #{}", scene.lights.len());
43/// println!("Models: #{}", scene.models.len());
44/// ```
45pub fn load<P>(path: P) -> Result<Vec<Scene>, Box<dyn Error + Send + Sync>>
46where
47    P: AsRef<Path>,
48{
49    // Run gltf
50    let (doc, buffers, _images) = gltf::import(&path)?;
51
52    // Init data and collection useful for conversion
53    let mut data = GltfData::new(buffers, &path);
54
55    // Convert gltf -> easy_gltf
56    let mut res = vec![];
57    for scene in doc.scenes() {
58        res.push(Scene::load(scene, &mut data));
59    }
60    Ok(res)
61}
62
63#[cfg(test)]
64mod tests {
65    use crate::model::Mode;
66    use crate::*;
67    use cgmath::*;
68
69    macro_rules! assert_delta {
70        ($x:expr, $y:expr, $d:expr) => {
71            if !($x - $y < $d || $y - $x < $d) {
72                panic!();
73            }
74        };
75    }
76
77    #[test]
78    fn check_cube_glb() {
79        let scenes = load("tests/cube.glb").unwrap();
80        assert_eq!(scenes.len(), 1);
81        let scene = &scenes[0];
82        assert_eq!(scene.cameras.len(), 1);
83        assert_eq!(scene.lights.len(), 3);
84        assert_eq!(scene.models.len(), 1);
85    }
86
87    #[test]
88    fn check_cube_glb_with_color() {
89        let scenes = load("tests/cube_color.glb").unwrap();
90        assert_eq!(scenes.len(), 1);
91        let scene = &scenes[0];
92        assert_eq!(scene.models.len(), 1);
93        assert!(scene.models[0].has_colors());
94        assert_eq!(scene.models[0].indices().unwrap().len(), 36);
95    }
96
97    #[test]
98    fn check_different_meshes() {
99        let scenes = load("tests/complete.glb").unwrap();
100        assert_eq!(scenes.len(), 1);
101        let scene = &scenes[0];
102        for model in scene.models.iter() {
103            match model.mode() {
104                Mode::Triangles | Mode::TriangleFan | Mode::TriangleStrip => {
105                    assert!(model.triangles().is_ok());
106                }
107                Mode::Lines | Mode::LineLoop | Mode::LineStrip => {
108                    assert!(model.lines().is_ok());
109                }
110                Mode::Points => {
111                    assert!(model.points().is_ok());
112                }
113            }
114        }
115    }
116
117    #[test]
118    fn check_cube_gltf() {
119        let _ = load("tests/cube_classic.gltf").unwrap();
120    }
121
122    #[test]
123    fn check_default_texture() {
124        let _ = load("tests/box_sparse.glb").unwrap();
125    }
126
127    #[test]
128    fn check_camera() {
129        let scenes = load("tests/cube.glb").unwrap();
130        let scene = &scenes[0];
131        let cam = &scene.cameras[0];
132        assert!((cam.position() - Vector3::new(7.3589, 4.9583, 6.9258)).magnitude() < 0.1);
133    }
134
135    #[test]
136    fn check_lights() {
137        let scenes = load("tests/cube.glb").unwrap();
138        let scene = &scenes[0];
139        for light in scene.lights.iter() {
140            match light {
141                Light::Directional {
142                    direction,
143                    color: _,
144                    intensity,
145                    ..
146                } => {
147                    assert!((direction - Vector3::new(0.6068, -0.7568, -0.2427)).magnitude() < 0.1);
148                    assert_delta!(intensity, 542., 0.01);
149                }
150                Light::Point {
151                    position,
152                    color: _,
153                    intensity,
154                    ..
155                } => {
156                    assert!((position - Vector3::new(4.0762, 5.9039, -1.0055)).magnitude() < 0.1);
157                    assert_delta!(intensity, 1000., 0.01);
158                }
159                Light::Spot {
160                    position,
161                    direction,
162                    color: _,
163                    intensity,
164                    inner_cone_angle: _,
165                    outer_cone_angle,
166                    ..
167                } => {
168                    assert!((position - Vector3::new(4.337, 15.541, -8.106)).magnitude() < 0.1);
169                    assert!(
170                        (direction - Vector3::new(-0.0959, -0.98623, 0.1346)).magnitude() < 0.1
171                    );
172                    assert_delta!(intensity, 42., 0.01);
173                    assert_delta!(outer_cone_angle, 40., 0.01);
174                }
175            }
176        }
177    }
178
179    #[test]
180    fn check_model() {
181        let scenes = load("tests/cube.glb").unwrap();
182        let scene = &scenes[0];
183        let model = &scene.models[0];
184        assert!(model.has_normals());
185        assert!(model.has_tex_coords());
186        assert!(model.has_tangents());
187        for t in model.triangles().unwrap().iter().flatten() {
188            let pos = t.position;
189            assert!(pos.x > -0.01 && pos.x < 1.01);
190            assert!(pos.y > -0.01 && pos.y < 1.01);
191            assert!(pos.z > -0.01 && pos.z < 1.01);
192
193            // Check that the tangent w component is 1 or -1
194            assert_eq!(t.tangent.w.abs(), 1.);
195        }
196    }
197
198    #[test]
199    fn check_material() {
200        let scenes = load("tests/head.glb").unwrap();
201        let scene = &scenes[0];
202        let mat = &scene.models[0].material;
203        assert!(mat.pbr.base_color_texture.is_some());
204        assert_eq!(mat.pbr.metallic_factor, 0.);
205    }
206
207    #[test]
208    fn check_invalid_path() {
209        assert!(load("tests/invalid.glb").is_err());
210    }
211}